Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 "use strict";
9 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
12 Cu.import("resource://gre/modules/Services.jsm");
13 Cu.import("resource://gre/modules/systemlibs.js");
14 Cu.import("resource://gre/modules/WifiCommand.jsm");
15 Cu.import("resource://gre/modules/WifiNetUtil.jsm");
16 Cu.import("resource://gre/modules/WifiP2pManager.jsm");
17 Cu.import("resource://gre/modules/WifiP2pWorkerObserver.jsm");
19 var DEBUG = false; // set to true to show debug messages.
21 const WIFIWORKER_CONTRACTID = "@mozilla.org/wifi/worker;1";
22 const WIFIWORKER_CID = Components.ID("{a14e8977-d259-433a-a88d-58dd44657e5b}");
24 const WIFIWORKER_WORKER = "resource://gre/modules/wifi_worker.js";
26 const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed";
27 const kMozSettingsChangedObserverTopic = "mozsettings-changed";
29 const MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
30 const MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
31 const MAX_RETRIES_ON_DHCP_FAILURE = 2;
33 // Settings DB path for wifi
34 const SETTINGS_WIFI_ENABLED = "wifi.enabled";
35 const SETTINGS_WIFI_DEBUG_ENABLED = "wifi.debugging.enabled";
36 // Settings DB path for Wifi tethering.
37 const SETTINGS_WIFI_TETHERING_ENABLED = "tethering.wifi.enabled";
38 const SETTINGS_WIFI_SSID = "tethering.wifi.ssid";
39 const SETTINGS_WIFI_SECURITY_TYPE = "tethering.wifi.security.type";
40 const SETTINGS_WIFI_SECURITY_PASSWORD = "tethering.wifi.security.password";
41 const SETTINGS_WIFI_IP = "tethering.wifi.ip";
42 const SETTINGS_WIFI_PREFIX = "tethering.wifi.prefix";
43 const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip";
44 const SETTINGS_WIFI_DHCPSERVER_ENDIP = "tethering.wifi.dhcpserver.endip";
45 const SETTINGS_WIFI_DNS1 = "tethering.wifi.dns1";
46 const SETTINGS_WIFI_DNS2 = "tethering.wifi.dns2";
48 // Settings DB path for USB tethering.
49 const SETTINGS_USB_DHCPSERVER_STARTIP = "tethering.usb.dhcpserver.startip";
50 const SETTINGS_USB_DHCPSERVER_ENDIP = "tethering.usb.dhcpserver.endip";
52 // Default value for WIFI tethering.
53 const DEFAULT_WIFI_IP = "192.168.1.1";
54 const DEFAULT_WIFI_PREFIX = "24";
55 const DEFAULT_WIFI_DHCPSERVER_STARTIP = "192.168.1.10";
56 const DEFAULT_WIFI_DHCPSERVER_ENDIP = "192.168.1.30";
57 const DEFAULT_WIFI_SSID = "FirefoxHotspot";
58 const DEFAULT_WIFI_SECURITY_TYPE = "open";
59 const DEFAULT_WIFI_SECURITY_PASSWORD = "1234567890";
60 const DEFAULT_DNS1 = "8.8.8.8";
61 const DEFAULT_DNS2 = "8.8.4.4";
63 // Default value for USB tethering.
64 const DEFAULT_USB_DHCPSERVER_STARTIP = "192.168.0.10";
65 const DEFAULT_USB_DHCPSERVER_ENDIP = "192.168.0.30";
67 const WIFI_FIRMWARE_AP = "AP";
68 const WIFI_FIRMWARE_STATION = "STA";
69 const WIFI_SECURITY_TYPE_NONE = "open";
70 const WIFI_SECURITY_TYPE_WPA_PSK = "wpa-psk";
71 const WIFI_SECURITY_TYPE_WPA2_PSK = "wpa2-psk";
73 const NETWORK_INTERFACE_UP = "up";
74 const NETWORK_INTERFACE_DOWN = "down";
76 const DEFAULT_WLAN_INTERFACE = "wlan0";
78 const DRIVER_READY_WAIT = 2000;
80 const SUPP_PROP = "init.svc.wpa_supplicant";
81 const WPA_SUPPLICANT = "wpa_supplicant";
82 const DHCP_PROP = "init.svc.dhcpcd";
83 const DHCP = "dhcpcd";
85 XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
86 "@mozilla.org/network/manager;1",
87 "nsINetworkManager");
89 XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
90 "@mozilla.org/network/service;1",
91 "nsINetworkService");
93 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
94 "@mozilla.org/settingsService;1",
95 "nsISettingsService");
97 // A note about errors and error handling in this file:
98 // The libraries that we use in this file are intended for C code. For
99 // C code, it is natural to return -1 for errors and 0 for success.
100 // Therefore, the code that interacts directly with the worker uses this
101 // convention (note: command functions do get boolean results since the
102 // command always succeeds and we do a string/boolean check for the
103 // expected results).
104 var WifiManager = (function() {
105 var manager = {};
107 function getStartupPrefs() {
108 return {
109 sdkVersion: parseInt(libcutils.property_get("ro.build.version.sdk"), 10),
110 unloadDriverEnabled: libcutils.property_get("ro.moz.wifi.unloaddriver") === "1",
111 schedScanRecovery: libcutils.property_get("ro.moz.wifi.sched_scan_recover") === "false" ? false : true,
112 driverDelay: libcutils.property_get("ro.moz.wifi.driverDelay"),
113 p2pSupported: libcutils.property_get("ro.moz.wifi.p2p_supported") === "1",
114 eapSimSupported: libcutils.property_get("ro.moz.wifi.eapsim_supported") === "1",
115 ifname: libcutils.property_get("wifi.interface")
116 };
117 }
119 let {sdkVersion, unloadDriverEnabled, schedScanRecovery,
120 driverDelay, p2pSupported, eapSimSupported, ifname} = getStartupPrefs();
122 let capabilities = {
123 eapSim: eapSimSupported
124 };
126 let wifiListener = {
127 onWaitEvent: function(event, iface) {
128 if (manager.ifname === iface && handleEvent(event)) {
129 waitForEvent(iface);
130 } else if (p2pSupported) {
131 if (WifiP2pManager.INTERFACE_NAME === iface) {
132 // If the connection is closed, wifi.c::wifi_wait_for_event()
133 // will still return 'CTRL-EVENT-TERMINATING - connection closed'
134 // rather than blocking. So when we see this special event string,
135 // just return immediately.
136 const TERMINATED_EVENT = 'CTRL-EVENT-TERMINATING - connection closed';
137 if (-1 !== event.indexOf(TERMINATED_EVENT)) {
138 return;
139 }
140 p2pManager.handleEvent(event);
141 waitForEvent(iface);
142 }
143 }
144 },
146 onCommand: function(event, iface) {
147 onmessageresult(event, iface);
148 }
149 }
151 manager.ifname = ifname;
152 manager.connectToSupplicant = false;
153 // Emulator build runs to here.
154 // The debug() should only be used after WifiManager.
155 if (!ifname) {
156 manager.ifname = DEFAULT_WLAN_INTERFACE;
157 }
158 manager.schedScanRecovery = schedScanRecovery;
159 manager.driverDelay = driverDelay ? parseInt(driverDelay, 10) : DRIVER_READY_WAIT;
161 // Regular Wifi stuff.
162 var netUtil = WifiNetUtil(controlMessage);
163 var wifiCommand = WifiCommand(controlMessage, manager.ifname);
165 // Wifi P2P stuff
166 var p2pManager;
167 if (p2pSupported) {
168 let p2pCommand = WifiCommand(controlMessage, WifiP2pManager.INTERFACE_NAME);
169 p2pManager = WifiP2pManager(p2pCommand, netUtil);
170 }
172 let wifiService = Cc["@mozilla.org/wifi/service;1"];
173 if (wifiService) {
174 wifiService = wifiService.getService(Ci.nsIWifiProxyService);
175 let interfaces = [manager.ifname];
176 if (p2pSupported) {
177 interfaces.push(WifiP2pManager.INTERFACE_NAME);
178 }
179 wifiService.start(wifiListener, interfaces, interfaces.length);
180 } else {
181 debug("No wifi service component available!");
182 }
184 // Callbacks to invoke when a reply arrives from the wifi service.
185 var controlCallbacks = Object.create(null);
186 var idgen = 0;
188 function controlMessage(obj, callback) {
189 var id = idgen++;
190 obj.id = id;
191 if (callback) {
192 controlCallbacks[id] = callback;
193 }
194 wifiService.sendCommand(obj, obj.iface);
195 }
197 let onmessageresult = function(data, iface) {
198 var id = data.id;
199 var callback = controlCallbacks[id];
200 if (callback) {
201 callback(data);
202 delete controlCallbacks[id];
203 }
204 }
206 // Polling the status worker
207 var recvErrors = 0;
209 function waitForEvent(iface) {
210 wifiService.waitForEvent(iface);
211 }
213 // Commands to the control worker.
215 var driverLoaded = false;
217 function loadDriver(callback) {
218 if (driverLoaded) {
219 callback(0);
220 return;
221 }
223 wifiCommand.loadDriver(function (status) {
224 driverLoaded = (status >= 0);
225 callback(status)
226 });
227 }
229 function unloadDriver(type, callback) {
230 if (!unloadDriverEnabled) {
231 // Unloading drivers is generally unnecessary and
232 // can trigger bugs in some drivers.
233 // On properly written drivers, bringing the interface
234 // down powers down the interface.
235 if (type === WIFI_FIRMWARE_STATION) {
236 notify("supplicantlost", { success: true });
237 }
238 callback(0);
239 return;
240 }
242 wifiCommand.unloadDriver(function(status) {
243 driverLoaded = (status < 0);
244 if (type === WIFI_FIRMWARE_STATION) {
245 notify("supplicantlost", { success: true });
246 }
247 callback(status);
248 });
249 }
251 // A note about background scanning:
252 // Normally, background scanning shouldn't be necessary as wpa_supplicant
253 // has the capability to automatically schedule its own scans at appropriate
254 // intervals. However, with some drivers, this appears to get stuck after
255 // three scans, so we enable the driver's background scanning to work around
256 // that when we're not connected to any network. This ensures that we'll
257 // automatically reconnect to networks if one falls out of range.
258 var reEnableBackgroundScan = false;
260 // NB: This is part of the internal API.
261 manager.backgroundScanEnabled = false;
262 function setBackgroundScan(enable, callback) {
263 var doEnable = (enable === "ON");
264 if (doEnable === manager.backgroundScanEnabled) {
265 callback(false, true);
266 return;
267 }
269 manager.backgroundScanEnabled = doEnable;
270 wifiCommand.setBackgroundScan(manager.backgroundScanEnabled, callback);
271 }
273 var scanModeActive = false;
275 function scan(forceActive, callback) {
276 if (forceActive && !scanModeActive) {
277 // Note: we ignore errors from doSetScanMode.
278 wifiCommand.doSetScanMode(true, function(ignore) {
279 setBackgroundScan("OFF", function(turned, ignore) {
280 reEnableBackgroundScan = turned;
281 manager.handlePreWifiScan();
282 wifiCommand.scan(function(ok) {
283 wifiCommand.doSetScanMode(false, function(ignore) {
284 // The result of scanCommand is the result of the actual SCAN
285 // request.
286 callback(ok);
287 });
288 });
289 });
290 });
291 return;
292 }
293 manager.handlePreWifiScan();
294 wifiCommand.scan(callback);
295 }
297 var debugEnabled = false;
299 function syncDebug() {
300 if (debugEnabled !== DEBUG) {
301 let wanted = DEBUG;
302 wifiCommand.setLogLevel(wanted ? "DEBUG" : "INFO", function(ok) {
303 if (ok)
304 debugEnabled = wanted;
305 });
306 if (p2pSupported && p2pManager) {
307 p2pManager.setDebug(DEBUG);
308 }
309 }
310 }
312 function getDebugEnabled(callback) {
313 wifiCommand.getLogLevel(function(level) {
314 if (level === null) {
315 debug("Unable to get wpa_supplicant's log level");
316 callback(false);
317 return;
318 }
320 var lines = level.split("\n");
321 for (let i = 0; i < lines.length; ++i) {
322 let match = /Current level: (.*)/.exec(lines[i]);
323 if (match) {
324 debugEnabled = match[1].toLowerCase() === "debug";
325 callback(true);
326 return;
327 }
328 }
330 // If we're here, we didn't get the current level.
331 callback(false);
332 });
333 }
335 function setScanMode(setActive, callback) {
336 scanModeActive = setActive;
337 wifiCommand.doSetScanMode(setActive, callback);
338 }
340 var httpProxyConfig = Object.create(null);
342 /**
343 * Given a network, configure http proxy when using wifi.
344 * @param network A network object to update http proxy
345 * @param info Info should have following field:
346 * - httpProxyHost ip address of http proxy.
347 * - httpProxyPort port of http proxy, set 0 to use default port 8080.
348 * @param callback callback function.
349 */
350 function configureHttpProxy(network, info, callback) {
351 if (!network)
352 return;
354 let networkKey = getNetworkKey(network);
356 if (!info || info.httpProxyHost === "") {
357 delete httpProxyConfig[networkKey];
358 } else {
359 httpProxyConfig[networkKey] = network;
360 httpProxyConfig[networkKey].httpProxyHost = info.httpProxyHost;
361 httpProxyConfig[networkKey].httpProxyPort = info.httpProxyPort;
362 }
364 callback(true);
365 }
367 function getHttpProxyNetwork(network) {
368 if (!network)
369 return null;
371 let networkKey = getNetworkKey(network);
372 return ((networkKey in httpProxyConfig) ? httpProxyConfig : null);
373 }
375 function setHttpProxy(network) {
376 if (!network)
377 return;
379 gNetworkService.setNetworkProxy(network);
380 }
382 var staticIpConfig = Object.create(null);
383 function setStaticIpMode(network, info, callback) {
384 let setNetworkKey = getNetworkKey(network);
385 let curNetworkKey = null;
386 let currentNetwork = Object.create(null);
387 currentNetwork.netId = manager.connectionInfo.id;
389 manager.getNetworkConfiguration(currentNetwork, function (){
390 curNetworkKey = getNetworkKey(currentNetwork);
392 // Add additional information to static ip configuration
393 // It is used to compatiable with information dhcp callback.
394 info.ipaddr = stringToIp(info.ipaddr_str);
395 info.gateway = stringToIp(info.gateway_str);
396 info.mask_str = makeMask(info.maskLength);
398 // Optional
399 info.dns1 = stringToIp("dns1_str" in info ? info.dns1_str : "");
400 info.dns2 = stringToIp("dns2_str" in info ? info.dns2_str : "");
401 info.proxy = stringToIp("proxy_str" in info ? info.proxy_str : "");
403 staticIpConfig[setNetworkKey] = info;
405 // If the ssid of current connection is the same as configured ssid
406 // It means we need update current connection to use static IP address.
407 if (setNetworkKey == curNetworkKey) {
408 // Use configureInterface directly doesn't work, the network iterface
409 // and routing table is changed but still cannot connect to network
410 // so the workaround here is disable interface the enable again to
411 // trigger network reconnect with static ip.
412 netUtil.disableInterface(manager.ifname, function (ok) {
413 netUtil.enableInterface(manager.ifname, function (ok) {
414 });
415 });
416 }
417 });
418 }
420 var dhcpInfo = null;
422 function runStaticIp(ifname, key) {
423 debug("Run static ip");
425 // Read static ip information from settings.
426 let staticIpInfo;
428 if (!(key in staticIpConfig))
429 return;
431 staticIpInfo = staticIpConfig[key];
433 // Stop dhcpd when use static IP
434 if (dhcpInfo != null) {
435 netUtil.stopDhcp(manager.ifname, function() {});
436 }
438 // Set ip, mask length, gateway, dns to network interface
439 netUtil.configureInterface( { ifname: ifname,
440 ipaddr: staticIpInfo.ipaddr,
441 mask: staticIpInfo.maskLength,
442 gateway: staticIpInfo.gateway,
443 dns1: staticIpInfo.dns1,
444 dns2: staticIpInfo.dns2 }, function (data) {
445 netUtil.runIpConfig(ifname, staticIpInfo, function(data) {
446 dhcpInfo = data.info;
447 notify("networkconnected", data);
448 });
449 });
450 }
452 var suppressEvents = false;
453 function notify(eventName, eventObject) {
454 if (suppressEvents)
455 return;
456 var handler = manager["on" + eventName];
457 if (handler) {
458 if (!eventObject)
459 eventObject = ({});
460 handler.call(eventObject);
461 }
462 }
464 function notifyStateChange(fields) {
465 // If we're already in the COMPLETED state, we might receive events from
466 // the supplicant that tell us that we're re-authenticating or reminding
467 // us that we're associated to a network. In those cases, we don't need to
468 // do anything, so just ignore them.
469 if (manager.state === "COMPLETED" &&
470 fields.state !== "DISCONNECTED" &&
471 fields.state !== "INTERFACE_DISABLED" &&
472 fields.state !== "INACTIVE" &&
473 fields.state !== "SCANNING") {
474 return false;
475 }
477 // Stop background scanning if we're trying to connect to a network.
478 if (manager.backgroundScanEnabled &&
479 (fields.state === "ASSOCIATING" ||
480 fields.state === "ASSOCIATED" ||
481 fields.state === "FOUR_WAY_HANDSHAKE" ||
482 fields.state === "GROUP_HANDSHAKE" ||
483 fields.state === "COMPLETED")) {
484 setBackgroundScan("OFF", function() {});
485 }
487 fields.prevState = manager.state;
488 // Detect wpa_supplicant's loop iterations.
489 manager.supplicantLoopDetection(fields.prevState, fields.state);
490 notify("statechange", fields);
492 // Don't update state when and after disabling.
493 if (manager.state === "DISABLING" ||
494 manager.state === "UNINITIALIZED") {
495 return false;
496 }
498 manager.state = fields.state;
499 return true;
500 }
502 function parseStatus(status) {
503 if (status === null) {
504 debug("Unable to get wpa supplicant's status");
505 return;
506 }
508 var ssid;
509 var bssid;
510 var state;
511 var ip_address;
512 var id;
514 var lines = status.split("\n");
515 for (let i = 0; i < lines.length; ++i) {
516 let [key, value] = lines[i].split("=");
517 switch (key) {
518 case "wpa_state":
519 state = value;
520 break;
521 case "ssid":
522 ssid = value;
523 break;
524 case "bssid":
525 bssid = value;
526 break;
527 case "ip_address":
528 ip_address = value;
529 break;
530 case "id":
531 id = value;
532 break;
533 }
534 }
536 if (bssid && ssid) {
537 manager.connectionInfo.bssid = bssid;
538 manager.connectionInfo.ssid = ssid;
539 manager.connectionInfo.id = id;
540 }
542 if (ip_address)
543 dhcpInfo = { ip_address: ip_address };
545 notifyStateChange({ state: state, fromStatus: true });
547 // If we parse the status and the supplicant has already entered the
548 // COMPLETED state, then we need to set up DHCP right away.
549 if (state === "COMPLETED")
550 onconnected();
551 }
553 // try to connect to the supplicant
554 var connectTries = 0;
555 var retryTimer = null;
556 function connectCallback(ok) {
557 if (ok === 0) {
558 // Tell the event worker to start waiting for events.
559 retryTimer = null;
560 connectTries = 0;
561 recvErrors = 0;
562 manager.connectToSupplicant = true;
563 didConnectSupplicant(function(){});
564 return;
565 }
566 if (connectTries++ < 5) {
567 // Try again in 1 seconds.
568 if (!retryTimer)
569 retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
571 retryTimer.initWithCallback(function(timer) {
572 wifiCommand.connectToSupplicant(connectCallback);
573 }, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
574 return;
575 }
577 retryTimer = null;
578 connectTries = 0;
579 notify("supplicantlost", { success: false });
580 }
582 manager.connectionDropped = function(callback) {
583 // Reset network interface when connection drop
584 netUtil.configureInterface( { ifname: manager.ifname,
585 ipaddr: 0,
586 mask: 0,
587 gateway: 0,
588 dns1: 0,
589 dns2: 0 }, function (data) {
590 });
592 // If we got disconnected, kill the DHCP client in preparation for
593 // reconnection.
594 netUtil.resetConnections(manager.ifname, function() {
595 netUtil.stopDhcp(manager.ifname, function() {
596 callback();
597 });
598 });
599 }
601 manager.start = function() {
602 debug("detected SDK version " + sdkVersion);
603 wifiCommand.connectToSupplicant(connectCallback);
604 }
606 function onconnected() {
607 // For now we do our own DHCP. In the future, this should be handed
608 // off to the Network Manager.
609 let currentNetwork = Object.create(null);
610 currentNetwork.netId = manager.connectionInfo.id;
612 manager.getNetworkConfiguration(currentNetwork, function (){
613 let key = getNetworkKey(currentNetwork);
614 if (staticIpConfig &&
615 (key in staticIpConfig) &&
616 staticIpConfig[key].enabled) {
617 debug("Run static ip");
618 runStaticIp(manager.ifname, key);
619 return;
620 }
621 netUtil.runDhcp(manager.ifname, function(data) {
622 dhcpInfo = data.info;
623 if (!dhcpInfo) {
624 if (++manager.dhcpFailuresCount >= MAX_RETRIES_ON_DHCP_FAILURE) {
625 manager.dhcpFailuresCount = 0;
626 notify("disconnected", {ssid: manager.connectionInfo.ssid});
627 return;
628 }
629 // NB: We have to call disconnect first. Otherwise, we only reauth with
630 // the existing AP and don't retrigger DHCP.
631 manager.disconnect(function() {
632 manager.reassociate(function(){});
633 });
634 return;
635 }
637 manager.dhcpFailuresCount = 0;
638 notify("networkconnected", data);
639 });
640 });
641 }
643 var supplicantStatesMap = (sdkVersion >= 15) ?
644 ["DISCONNECTED", "INTERFACE_DISABLED", "INACTIVE", "SCANNING",
645 "AUTHENTICATING", "ASSOCIATING", "ASSOCIATED", "FOUR_WAY_HANDSHAKE",
646 "GROUP_HANDSHAKE", "COMPLETED"]
647 :
648 ["DISCONNECTED", "INACTIVE", "SCANNING", "ASSOCIATING",
649 "ASSOCIATED", "FOUR_WAY_HANDSHAKE", "GROUP_HANDSHAKE",
650 "COMPLETED", "DORMANT", "UNINITIALIZED"];
652 var driverEventMap = { STOPPED: "driverstopped", STARTED: "driverstarted", HANGED: "driverhung" };
654 manager.getCurrentNetworkId = function (ssid, callback) {
655 manager.getConfiguredNetworks(function(networks) {
656 if (!networks) {
657 debug("Unable to get configured networks");
658 return callback(null);
659 }
660 for (let net in networks) {
661 let network = networks[net];
662 // Trying to get netId from
663 // 1. CURRENT network.
664 // 2. Trying to associate with SSID 'ssid' event
665 if (network.status === "CURRENT" ||
666 (ssid && ssid === dequote(network.ssid))) {
667 return callback(net);
668 }
669 }
670 callback(null);
671 });
672 }
674 // Handle events sent to us by the event worker.
675 function handleEvent(event) {
676 debug("Event coming in: " + event);
677 if (event.indexOf("CTRL-EVENT-") !== 0 && event.indexOf("WPS") !== 0) {
678 // Handle connection fail exception on WEP-128, while password length
679 // is not 5 nor 13 bytes.
680 if (event.indexOf("Association request to the driver failed") !== -1) {
681 notify("passwordmaybeincorrect");
682 if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
683 manager.authenticationFailuresCount = 0;
684 notify("disconnected", {ssid: manager.connectionInfo.ssid});
685 }
686 return true;
687 }
689 if (event.indexOf("WPA:") == 0 &&
690 event.indexOf("pre-shared key may be incorrect") != -1) {
691 notify("passwordmaybeincorrect");
692 }
694 // This is ugly, but we need to grab the SSID here. BSSID is not guaranteed
695 // to be provided, so don't grab BSSID here.
696 var match = /Trying to associate with.*SSID[ =]'(.*)'/.exec(event);
697 if (match) {
698 debug("Matched: " + match[1] + "\n");
699 manager.connectionInfo.ssid = match[1];
700 }
701 return true;
702 }
704 var space = event.indexOf(" ");
705 var eventData = event.substr(0, space + 1);
706 if (eventData.indexOf("CTRL-EVENT-STATE-CHANGE") === 0) {
707 // Parse the event data.
708 var fields = {};
709 var tokens = event.substr(space + 1).split(" ");
710 for (var n = 0; n < tokens.length; ++n) {
711 var kv = tokens[n].split("=");
712 if (kv.length === 2)
713 fields[kv[0]] = kv[1];
714 }
715 if (!("state" in fields))
716 return true;
717 fields.state = supplicantStatesMap[fields.state];
719 // The BSSID field is only valid in the ASSOCIATING and ASSOCIATED
720 // states, except when we "reauth", except this seems to depend on the
721 // driver, so simply check to make sure that we don't have a null BSSID.
722 if (fields.BSSID !== "00:00:00:00:00:00")
723 manager.connectionInfo.bssid = fields.BSSID;
725 if (notifyStateChange(fields) && fields.state === "COMPLETED") {
726 onconnected();
727 }
728 return true;
729 }
730 if (eventData.indexOf("CTRL-EVENT-DRIVER-STATE") === 0) {
731 var handlerName = driverEventMap[eventData];
732 if (handlerName)
733 notify(handlerName);
734 return true;
735 }
736 if (eventData.indexOf("CTRL-EVENT-TERMINATING") === 0) {
737 // As long the monitor socket is not closed and we haven't seen too many
738 // recv errors yet, we will keep going for a bit longer.
739 if (event.indexOf("connection closed") === -1 &&
740 event.indexOf("recv error") !== -1 && ++recvErrors < 10)
741 return true;
743 notifyStateChange({ state: "DISCONNECTED", BSSID: null, id: -1 });
745 // If the supplicant is terminated as commanded, the supplicant lost
746 // notification will be sent after driver unloaded. In such case, the
747 // manager state will be "DISABLING" or "UNINITIALIZED".
748 // So if supplicant terminated with incorrect manager state, implying
749 // unexpected condition, we should notify supplicant lost here.
750 if (manager.state !== "DISABLING" && manager.state !== "UNINITIALIZED") {
751 notify("supplicantlost", { success: true });
752 }
753 wifiCommand.closeSupplicantConnection(function() {
754 manager.connectToSupplicant = false;
755 });
756 return false;
757 }
758 if (eventData.indexOf("CTRL-EVENT-DISCONNECTED") === 0) {
759 var token = event.split(" ")[1];
760 var bssid = token.split("=")[1];
761 if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
762 manager.authenticationFailuresCount = 0;
763 notify("disconnected", {ssid: manager.connectionInfo.ssid});
764 }
765 manager.connectionInfo.bssid = null;
766 manager.connectionInfo.ssid = null;
767 manager.connectionInfo.id = -1;
768 return true;
769 }
770 // Association reject is triggered mostly on incorrect WEP key.
771 if (eventData.indexOf("CTRL-EVENT-ASSOC-REJECT") === 0) {
772 notify("passwordmaybeincorrect");
773 if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
774 manager.authenticationFailuresCount = 0;
775 notify("disconnected", {ssid: manager.connectionInfo.ssid});
776 }
777 return true;
778 }
779 if (eventData.indexOf("CTRL-EVENT-EAP-FAILURE") === 0) {
780 if (event.indexOf("EAP authentication failed") !== -1) {
781 notify("passwordmaybeincorrect");
782 }
783 return true;
784 }
785 if (eventData.indexOf("CTRL-EVENT-CONNECTED") === 0) {
786 // Format: CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]
787 var bssid = event.split(" ")[4];
789 var keyword = "id=";
790 var id = event.substr(event.indexOf(keyword) + keyword.length).split(" ")[0];
791 // Read current BSSID here, it will always being provided.
792 manager.connectionInfo.id = id;
793 manager.connectionInfo.bssid = bssid;
794 return true;
795 }
796 if (eventData.indexOf("CTRL-EVENT-SCAN-RESULTS") === 0) {
797 debug("Notifying of scan results available");
798 if (reEnableBackgroundScan) {
799 reEnableBackgroundScan = false;
800 setBackgroundScan("ON", function() {});
801 }
802 manager.handlePostWifiScan();
803 notify("scanresultsavailable");
804 return true;
805 }
806 if (eventData.indexOf("WPS-TIMEOUT") === 0) {
807 notifyStateChange({ state: "WPS_TIMEOUT", BSSID: null, id: -1 });
808 return true;
809 }
810 if (eventData.indexOf("WPS-FAIL") === 0) {
811 notifyStateChange({ state: "WPS_FAIL", BSSID: null, id: -1 });
812 return true;
813 }
814 if (eventData.indexOf("WPS-OVERLAP-DETECTED") === 0) {
815 notifyStateChange({ state: "WPS_OVERLAP_DETECTED", BSSID: null, id: -1 });
816 return true;
817 }
818 // Unknown event.
819 return true;
820 }
822 function didConnectSupplicant(callback) {
823 waitForEvent(manager.ifname);
825 // Load up the supplicant state.
826 getDebugEnabled(function(ok) {
827 syncDebug();
828 });
829 wifiCommand.status(function(status) {
830 parseStatus(status);
831 notify("supplicantconnection");
832 callback();
833 });
835 if (p2pSupported) {
836 manager.enableP2p(function(success) {});
837 }
838 }
840 function prepareForStartup(callback) {
841 let status = libcutils.property_get(DHCP_PROP + "_" + manager.ifname);
842 if (status !== "running") {
843 tryStopSupplicant();
844 return;
845 }
846 manager.connectionDropped(function() {
847 tryStopSupplicant();
848 });
850 // Ignore any errors and kill any currently-running supplicants. On some
851 // phones, stopSupplicant won't work for a supplicant that we didn't
852 // start, so we hand-roll it here.
853 function tryStopSupplicant () {
854 let status = libcutils.property_get(SUPP_PROP);
855 if (status !== "running") {
856 callback();
857 return;
858 }
859 suppressEvents = true;
860 wifiCommand.killSupplicant(function() {
861 netUtil.disableInterface(manager.ifname, function (ok) {
862 suppressEvents = false;
863 callback();
864 });
865 });
866 }
867 }
869 // Initial state.
870 manager.state = "UNINITIALIZED";
871 manager.tetheringState = "UNINITIALIZED";
872 manager.enabled = false;
873 manager.supplicantStarted = false;
874 manager.connectionInfo = { ssid: null, bssid: null, id: -1 };
875 manager.authenticationFailuresCount = 0;
876 manager.loopDetectionCount = 0;
877 manager.dhcpFailuresCount = 0;
879 var waitForDriverReadyTimer = null;
880 function cancelWaitForDriverReadyTimer() {
881 if (waitForDriverReadyTimer) {
882 waitForDriverReadyTimer.cancel();
883 waitForDriverReadyTimer = null;
884 }
885 };
886 function createWaitForDriverReadyTimer(onTimeout) {
887 waitForDriverReadyTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
888 waitForDriverReadyTimer.initWithCallback(onTimeout,
889 manager.driverDelay,
890 Ci.nsITimer.TYPE_ONE_SHOT);
891 };
893 // Public interface of the wifi service.
894 manager.setWifiEnabled = function(enabled, callback) {
895 if (enabled === manager.isWifiEnabled(manager.state)) {
896 callback("no change");
897 return;
898 }
900 if (enabled) {
901 manager.state = "INITIALIZING";
902 // Register as network interface.
903 WifiNetworkInterface.name = manager.ifname;
904 if (!WifiNetworkInterface.registered) {
905 gNetworkManager.registerNetworkInterface(WifiNetworkInterface);
906 WifiNetworkInterface.registered = true;
907 }
908 WifiNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
909 WifiNetworkInterface.ips = [];
910 WifiNetworkInterface.prefixLengths = [];
911 WifiNetworkInterface.gateways = [];
912 WifiNetworkInterface.dnses = [];
913 Services.obs.notifyObservers(WifiNetworkInterface,
914 kNetworkInterfaceStateChangedTopic,
915 null);
916 prepareForStartup(function() {
917 loadDriver(function (status) {
918 if (status < 0) {
919 callback(status);
920 manager.state = "UNINITIALIZED";
921 return;
922 }
923 // This command is mandatory for Nexus 4. But some devices like
924 // Galaxy S2 don't support it. Continue to start wpa_supplicant
925 // even if we fail to set wifi operation mode to station.
926 gNetworkService.setWifiOperationMode(manager.ifname,
927 WIFI_FIRMWARE_STATION,
928 function (status) {
930 function startSupplicantInternal() {
931 wifiCommand.startSupplicant(function (status) {
932 if (status < 0) {
933 unloadDriver(WIFI_FIRMWARE_STATION, function() {
934 callback(status);
935 });
936 manager.state = "UNINITIALIZED";
937 return;
938 }
940 manager.supplicantStarted = true;
941 netUtil.enableInterface(manager.ifname, function (ok) {
942 callback(ok ? 0 : -1);
943 });
944 });
945 }
947 function doStartSupplicant() {
948 cancelWaitForDriverReadyTimer();
950 if (!manager.connectToSupplicant) {
951 startSupplicantInternal();
952 return;
953 }
954 wifiCommand.closeSupplicantConnection(function () {
955 manager.connectToSupplicant = false;
956 // closeSupplicantConnection() will trigger onsupplicantlost
957 // and set manager.state to "UNINITIALIZED", we have to
958 // restore it here.
959 manager.state = "INITIALIZING";
960 startSupplicantInternal();
961 });
962 }
963 // Driver startup on certain platforms takes longer than it takes for us
964 // to return from loadDriver, so wait 2 seconds before starting
965 // the supplicant to give it a chance to start.
966 if (manager.driverDelay > 0) {
967 createWaitForDriverReadyTimer(doStartSupplicant);
968 } else {
969 doStartSupplicant();
970 }
971 });
972 });
973 });
974 } else {
975 manager.state = "DISABLING";
976 // Note these following calls ignore errors. If we fail to kill the
977 // supplicant gracefully, then we need to continue telling it to die
978 // until it does.
979 let doDisableWifi = function() {
980 wifiCommand.terminateSupplicant(function (ok) {
981 manager.connectionDropped(function () {
982 wifiCommand.stopSupplicant(function (status) {
983 manager.state = "UNINITIALIZED";
984 netUtil.disableInterface(manager.ifname, function (ok) {
985 unloadDriver(WIFI_FIRMWARE_STATION, callback);
986 });
987 });
988 });
989 });
990 }
992 if (p2pSupported) {
993 p2pManager.setEnabled(false, { onDisabled: doDisableWifi });
994 } else {
995 doDisableWifi();
996 }
997 }
998 }
1000 // Get wifi interface and load wifi driver when enable Ap mode.
1001 manager.setWifiApEnabled = function(enabled, configuration, callback) {
1002 if (enabled === manager.isWifiTetheringEnabled(manager.tetheringState)) {
1003 callback("no change");
1004 return;
1005 }
1007 if (enabled) {
1008 manager.tetheringState = "INITIALIZING";
1009 loadDriver(function (status) {
1010 if (status < 0) {
1011 callback();
1012 manager.tetheringState = "UNINITIALIZED";
1013 return;
1014 }
1016 function doStartWifiTethering() {
1017 cancelWaitForDriverReadyTimer();
1018 WifiNetworkInterface.name = manager.ifname;
1019 gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface,
1020 configuration, function(result) {
1021 if (result) {
1022 manager.tetheringState = "UNINITIALIZED";
1023 } else {
1024 manager.tetheringState = "COMPLETED";
1025 }
1026 // Pop out current request.
1027 callback();
1028 // Should we fire a dom event if we fail to set wifi tethering ?
1029 debug("Enable Wifi tethering result: " + (result ? result : "successfully"));
1030 });
1031 }
1033 // Driver startup on certain platforms takes longer than it takes
1034 // for us to return from loadDriver, so wait 2 seconds before
1035 // turning on Wifi tethering.
1036 createWaitForDriverReadyTimer(doStartWifiTethering);
1037 });
1038 } else {
1039 gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface,
1040 configuration, function(result) {
1041 // Should we fire a dom event if we fail to set wifi tethering ?
1042 debug("Disable Wifi tethering result: " + (result ? result : "successfully"));
1043 // Unload wifi driver even if we fail to control wifi tethering.
1044 unloadDriver(WIFI_FIRMWARE_AP, function(status) {
1045 if (status < 0) {
1046 debug("Fail to unload wifi driver");
1047 }
1048 manager.tetheringState = "UNINITIALIZED";
1049 callback();
1050 });
1051 });
1052 }
1053 }
1055 manager.disconnect = wifiCommand.disconnect;
1056 manager.reconnect = wifiCommand.reconnect;
1057 manager.reassociate = wifiCommand.reassociate;
1059 var networkConfigurationFields = [
1060 "ssid", "bssid", "psk", "wep_key0", "wep_key1", "wep_key2", "wep_key3",
1061 "wep_tx_keyidx", "priority", "key_mgmt", "scan_ssid", "disabled",
1062 "identity", "password", "auth_alg", "phase1", "phase2", "eap", "pin",
1063 "pcsc"
1064 ];
1066 manager.getNetworkConfiguration = function(config, callback) {
1067 var netId = config.netId;
1068 var done = 0;
1069 for (var n = 0; n < networkConfigurationFields.length; ++n) {
1070 let fieldName = networkConfigurationFields[n];
1071 wifiCommand.getNetworkVariable(netId, fieldName, function(value) {
1072 if (value !== null)
1073 config[fieldName] = value;
1074 if (++done == networkConfigurationFields.length)
1075 callback(config);
1076 });
1077 }
1078 }
1079 manager.setNetworkConfiguration = function(config, callback) {
1080 var netId = config.netId;
1081 var done = 0;
1082 var errors = 0;
1083 for (var n = 0; n < networkConfigurationFields.length; ++n) {
1084 let fieldName = networkConfigurationFields[n];
1085 if (!(fieldName in config) ||
1086 // These fields are special: We can't retrieve them from the
1087 // supplicant, and often we have a star in our config. In that case,
1088 // we need to avoid overwriting the correct password with a *.
1089 (fieldName === "password" ||
1090 fieldName === "wep_key0" ||
1091 fieldName === "psk") &&
1092 config[fieldName] === '*') {
1093 ++done;
1094 } else {
1095 wifiCommand.setNetworkVariable(netId, fieldName, config[fieldName], function(ok) {
1096 if (!ok)
1097 ++errors;
1098 if (++done == networkConfigurationFields.length)
1099 callback(errors == 0);
1100 });
1101 }
1102 }
1103 // If config didn't contain any of the fields we want, don't lose the error callback.
1104 if (done == networkConfigurationFields.length)
1105 callback(false);
1106 }
1107 manager.getConfiguredNetworks = function(callback) {
1108 wifiCommand.listNetworks(function (reply) {
1109 var networks = Object.create(null);
1110 var lines = reply ? reply.split("\n") : 0;
1111 if (lines.length <= 1) {
1112 // We need to make sure we call the callback even if there are no
1113 // configured networks.
1114 callback(networks);
1115 return;
1116 }
1118 var done = 0;
1119 var errors = 0;
1120 for (var n = 1; n < lines.length; ++n) {
1121 var result = lines[n].split("\t");
1122 var netId = result[0];
1123 var config = networks[netId] = { netId: netId };
1124 switch (result[3]) {
1125 case "[CURRENT]":
1126 config.status = "CURRENT";
1127 break;
1128 case "[DISABLED]":
1129 config.status = "DISABLED";
1130 break;
1131 default:
1132 config.status = "ENABLED";
1133 break;
1134 }
1135 manager.getNetworkConfiguration(config, function (ok) {
1136 if (!ok)
1137 ++errors;
1138 if (++done == lines.length - 1) {
1139 if (errors) {
1140 // If an error occured, delete the new netId.
1141 wifiCommand.removeNetwork(netId, function() {
1142 callback(null);
1143 });
1144 } else {
1145 callback(networks);
1146 }
1147 }
1148 });
1149 }
1150 });
1151 }
1152 manager.addNetwork = function(config, callback) {
1153 wifiCommand.addNetwork(function (netId) {
1154 config.netId = netId;
1155 manager.setNetworkConfiguration(config, function (ok) {
1156 if (!ok) {
1157 wifiCommand.removeNetwork(netId, function() { callback(false); });
1158 return;
1159 }
1161 callback(ok);
1162 });
1163 });
1164 }
1165 manager.updateNetwork = function(config, callback) {
1166 manager.setNetworkConfiguration(config, callback);
1167 }
1168 manager.removeNetwork = function(netId, callback) {
1169 wifiCommand.removeNetwork(netId, callback);
1170 }
1172 function stringToIp(string) {
1173 let ip = 0;
1174 let start, end = -1;
1175 for (let i = 0; i < 4; i++) {
1176 start = end + 1;
1177 end = string.indexOf(".", start);
1178 if (end == -1) {
1179 end = string.length;
1180 }
1181 let num = parseInt(string.slice(start, end), 10);
1182 if (isNaN(num)) {
1183 return 0;
1184 }
1185 ip |= num << (i * 8);
1186 }
1187 return ip;
1188 }
1190 function swap32(n) {
1191 return (((n >> 24) & 0xFF) << 0) |
1192 (((n >> 16) & 0xFF) << 8) |
1193 (((n >> 8) & 0xFF) << 16) |
1194 (((n >> 0) & 0xFF) << 24);
1195 }
1197 function ntohl(n) {
1198 return swap32(n);
1199 }
1201 function makeMask(len) {
1202 let mask = 0;
1203 for (let i = 0; i < len; ++i) {
1204 mask |= (0x80000000 >> i);
1205 }
1206 return ntohl(mask);
1207 }
1209 manager.saveConfig = function(callback) {
1210 wifiCommand.saveConfig(callback);
1211 }
1212 manager.enableNetwork = function(netId, disableOthers, callback) {
1213 if (p2pSupported) {
1214 // We have to stop wifi direct scan before associating to an AP.
1215 // Otherwise we will get a "REJECT" wpa supplicant event.
1216 p2pManager.setScanEnabled(false, function(success) {});
1217 }
1218 wifiCommand.enableNetwork(netId, disableOthers, callback);
1219 }
1220 manager.disableNetwork = function(netId, callback) {
1221 wifiCommand.disableNetwork(netId, callback);
1222 }
1223 manager.getMacAddress = wifiCommand.getMacAddress;
1224 manager.getScanResults = wifiCommand.scanResults;
1225 manager.setScanMode = function(mode, callback) {
1226 setScanMode(mode === "active", callback); // Use our own version.
1227 }
1228 manager.setBackgroundScan = setBackgroundScan; // Use our own version.
1229 manager.scan = scan; // Use our own version.
1230 manager.wpsPbc = wifiCommand.wpsPbc;
1231 manager.wpsPin = wifiCommand.wpsPin;
1232 manager.wpsCancel = wifiCommand.wpsCancel;
1233 manager.setPowerMode = (sdkVersion >= 16)
1234 ? wifiCommand.setPowerModeJB
1235 : wifiCommand.setPowerModeICS;
1236 manager.getHttpProxyNetwork = getHttpProxyNetwork;
1237 manager.setHttpProxy = setHttpProxy;
1238 manager.configureHttpProxy = configureHttpProxy;
1239 manager.setSuspendOptimizations = (sdkVersion >= 16)
1240 ? wifiCommand.setSuspendOptimizationsJB
1241 : wifiCommand.setSuspendOptimizationsICS;
1242 manager.setStaticIpMode = setStaticIpMode;
1243 manager.getRssiApprox = wifiCommand.getRssiApprox;
1244 manager.getLinkSpeed = wifiCommand.getLinkSpeed;
1245 manager.getDhcpInfo = function() { return dhcpInfo; }
1246 manager.getConnectionInfo = (sdkVersion >= 15)
1247 ? wifiCommand.getConnectionInfoICS
1248 : wifiCommand.getConnectionInfoGB;
1250 manager.isHandShakeState = function(state) {
1251 switch (state) {
1252 case "AUTHENTICATING":
1253 case "ASSOCIATING":
1254 case "ASSOCIATED":
1255 case "FOUR_WAY_HANDSHAKE":
1256 case "GROUP_HANDSHAKE":
1257 return true;
1258 case "DORMANT":
1259 case "COMPLETED":
1260 case "DISCONNECTED":
1261 case "INTERFACE_DISABLED":
1262 case "INACTIVE":
1263 case "SCANNING":
1264 case "UNINITIALIZED":
1265 case "INVALID":
1266 case "CONNECTED":
1267 default:
1268 return false;
1269 }
1270 }
1272 manager.isWifiEnabled = function(state) {
1273 switch (state) {
1274 case "UNINITIALIZED":
1275 case "DISABLING":
1276 return false;
1277 default:
1278 return true;
1279 }
1280 }
1282 manager.isWifiTetheringEnabled = function(state) {
1283 switch (state) {
1284 case "UNINITIALIZED":
1285 return false;
1286 default:
1287 return true;
1288 }
1289 }
1291 manager.syncDebug = syncDebug;
1292 manager.stateOrdinal = function(state) {
1293 return supplicantStatesMap.indexOf(state);
1294 }
1295 manager.supplicantLoopDetection = function(prevState, state) {
1296 var isPrevStateInHandShake = manager.isHandShakeState(prevState);
1297 var isStateInHandShake = manager.isHandShakeState(state);
1299 if (isPrevStateInHandShake) {
1300 if (isStateInHandShake) {
1301 // Increase the count only if we are in the loop.
1302 if (manager.stateOrdinal(state) > manager.stateOrdinal(prevState)) {
1303 manager.loopDetectionCount++;
1304 }
1305 if (manager.loopDetectionCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
1306 notify("disconnected", {ssid: manager.connectionInfo.ssid});
1307 manager.loopDetectionCount = 0;
1308 }
1309 }
1310 } else {
1311 // From others state to HandShake state. Reset the count.
1312 if (isStateInHandShake) {
1313 manager.loopDetectionCount = 0;
1314 }
1315 }
1316 }
1318 manager.handlePreWifiScan = function() {
1319 if (p2pSupported) {
1320 // Before doing regular wifi scan, we have to disable wifi direct
1321 // scan first. Otherwise we will never get the scan result.
1322 p2pManager.blockScan();
1323 }
1324 };
1326 manager.handlePostWifiScan = function() {
1327 if (p2pSupported) {
1328 // After regular wifi scanning, we should restore the restricted
1329 // wifi direct scan.
1330 p2pManager.unblockScan();
1331 }
1332 };
1334 //
1335 // Public APIs for P2P.
1336 //
1338 manager.p2pSupported = function() {
1339 return p2pSupported;
1340 };
1342 manager.getP2pManager = function() {
1343 return p2pManager;
1344 };
1346 manager.enableP2p = function(callback) {
1347 p2pManager.setEnabled(true, {
1348 onSupplicantConnected: function() {
1349 waitForEvent(WifiP2pManager.INTERFACE_NAME);
1350 },
1352 onEnabled: function(success) {
1353 callback(success);
1354 }
1355 });
1356 };
1358 manager.getCapabilities = function() {
1359 return capabilities;
1360 }
1362 return manager;
1363 })();
1365 // Get unique key for a network, now the key is created by escape(SSID)+Security.
1366 // So networks of same SSID but different security mode can be identified.
1367 function getNetworkKey(network)
1368 {
1369 var ssid = "",
1370 encryption = "OPEN";
1372 if ("security" in network) {
1373 // manager network object, represents an AP
1374 // object structure
1375 // {
1376 // .ssid : SSID of AP
1377 // .security[] : "WPA-PSK" for WPA-PSK
1378 // "WPA-EAP" for WPA-EAP
1379 // "WEP" for WEP
1380 // "" for OPEN
1381 // other keys
1382 // }
1384 var security = network.security;
1385 ssid = network.ssid;
1387 for (let j = 0; j < security.length; j++) {
1388 if (security[j] === "WPA-PSK") {
1389 encryption = "WPA-PSK";
1390 break;
1391 } else if (security[j] === "WPA-EAP") {
1392 encryption = "WPA-EAP";
1393 break;
1394 } else if (security[j] === "WEP") {
1395 encryption = "WEP";
1396 break;
1397 }
1398 }
1399 } else if ("key_mgmt" in network) {
1400 // configure network object, represents a network
1401 // object structure
1402 // {
1403 // .ssid : SSID of network, quoted
1404 // .key_mgmt : Encryption type
1405 // "WPA-PSK" for WPA-PSK
1406 // "WPA-EAP" for WPA-EAP
1407 // "NONE" for WEP/OPEN
1408 // .auth_alg : Encryption algorithm(WEP mode only)
1409 // "OPEN_SHARED" for WEP
1410 // other keys
1411 // }
1412 var key_mgmt = network.key_mgmt,
1413 auth_alg = network.auth_alg;
1414 ssid = dequote(network.ssid);
1416 if (key_mgmt == "WPA-PSK") {
1417 encryption = "WPA-PSK";
1418 } else if (key_mgmt == "WPA-EAP") {
1419 encryption = "WPA-EAP";
1420 } else if (key_mgmt == "NONE" && auth_alg === "OPEN SHARED") {
1421 encryption = "WEP";
1422 }
1423 }
1425 // ssid here must be dequoted, and it's safer to esacpe it.
1426 // encryption won't be empty and always be assigned one of the followings :
1427 // "OPEN"/"WEP"/"WPA-PSK"/"WPA-EAP".
1428 // So for a invalid network object, the returned key will be "OPEN".
1429 return escape(ssid) + encryption;
1430 }
1432 function getKeyManagement(flags) {
1433 var types = [];
1434 if (!flags)
1435 return types;
1437 if (/\[WPA2?-PSK/.test(flags))
1438 types.push("WPA-PSK");
1439 if (/\[WPA2?-EAP/.test(flags))
1440 types.push("WPA-EAP");
1441 if (/\[WEP/.test(flags))
1442 types.push("WEP");
1443 return types;
1444 }
1446 function getCapabilities(flags) {
1447 var types = [];
1448 if (!flags)
1449 return types;
1451 if (/\[WPS/.test(flags))
1452 types.push("WPS");
1453 return types;
1454 }
1456 // These constants shamelessly ripped from WifiManager.java
1457 // strength is the value returned by scan_results. It is nominally in dB. We
1458 // transform it into a percentage for clients looking to simply show a
1459 // relative indication of the strength of a network.
1460 const MIN_RSSI = -100;
1461 const MAX_RSSI = -55;
1463 function calculateSignal(strength) {
1464 // Some wifi drivers represent their signal strengths as 8-bit integers, so
1465 // in order to avoid negative numbers, they add 256 to the actual values.
1466 // While we don't *know* that this is the case here, we make an educated
1467 // guess.
1468 if (strength > 0)
1469 strength -= 256;
1471 if (strength <= MIN_RSSI)
1472 return 0;
1473 if (strength >= MAX_RSSI)
1474 return 100;
1475 return Math.floor(((strength - MIN_RSSI) / (MAX_RSSI - MIN_RSSI)) * 100);
1476 }
1478 function Network(ssid, security, password, capabilities) {
1479 this.ssid = ssid;
1480 this.security = security;
1482 if (typeof password !== "undefined")
1483 this.password = password;
1484 if (capabilities !== undefined)
1485 this.capabilities = capabilities;
1486 // TODO connected here as well?
1488 this.__exposedProps__ = Network.api;
1489 }
1491 Network.api = {
1492 ssid: "r",
1493 security: "r",
1494 capabilities: "r",
1495 known: "r",
1497 password: "rw",
1498 keyManagement: "rw",
1499 psk: "rw",
1500 identity: "rw",
1501 wep: "rw",
1502 hidden: "rw",
1503 eap: "rw",
1504 pin: "rw",
1505 phase1: "rw",
1506 phase2: "rw"
1507 };
1509 // Note: We never use ScanResult.prototype, so the fact that it's unrelated to
1510 // Network.prototype is OK.
1511 function ScanResult(ssid, bssid, flags, signal) {
1512 Network.call(this, ssid, getKeyManagement(flags), undefined,
1513 getCapabilities(flags));
1514 this.bssid = bssid;
1515 this.signalStrength = signal;
1516 this.relSignalStrength = calculateSignal(Number(signal));
1518 this.__exposedProps__ = ScanResult.api;
1519 }
1521 // XXX This should probably live in the DOM-facing side, but it's hard to do
1522 // there, so we stick this here.
1523 ScanResult.api = {
1524 bssid: "r",
1525 signalStrength: "r",
1526 relSignalStrength: "r",
1527 connected: "r"
1528 };
1530 for (let i in Network.api) {
1531 ScanResult.api[i] = Network.api[i];
1532 }
1534 function quote(s) {
1535 return '"' + s + '"';
1536 }
1538 function dequote(s) {
1539 if (s[0] != '"' || s[s.length - 1] != '"')
1540 throw "Invalid argument, not a quoted string: " + s;
1541 return s.substr(1, s.length - 2);
1542 }
1544 function isWepHexKey(s) {
1545 if (s.length != 10 && s.length != 26 && s.length != 58)
1546 return false;
1547 return !/[^a-fA-F0-9]/.test(s);
1548 }
1551 let WifiNetworkInterface = {
1553 QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
1555 registered: false,
1557 // nsINetworkInterface
1559 NETWORK_STATE_UNKNOWN: Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN,
1560 NETWORK_STATE_CONNECTING: Ci.nsINetworkInterface.CONNECTING,
1561 NETWORK_STATE_CONNECTED: Ci.nsINetworkInterface.CONNECTED,
1562 NETWORK_STATE_DISCONNECTING: Ci.nsINetworkInterface.DISCONNECTING,
1563 NETWORK_STATE_DISCONNECTED: Ci.nsINetworkInterface.DISCONNECTED,
1565 state: Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN,
1567 NETWORK_TYPE_WIFI: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
1568 NETWORK_TYPE_MOBILE: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
1569 NETWORK_TYPE_MOBILE_MMS: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS,
1570 NETWORK_TYPE_MOBILE_SUPL: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL,
1572 type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
1574 name: null,
1576 ips: [],
1578 prefixLengths: [],
1580 dnses: [],
1582 gateways: [],
1584 httpProxyHost: null,
1586 httpProxyPort: null,
1588 getAddresses: function (ips, prefixLengths) {
1589 ips.value = this.ips.slice();
1590 prefixLengths.value = this.prefixLengths.slice();
1592 return this.ips.length;
1593 },
1595 getGateways: function (count) {
1596 if (count) {
1597 count.value = this.gateways.length;
1598 }
1599 return this.gateways.slice();
1600 },
1602 getDnses: function (count) {
1603 if (count) {
1604 count.value = this.dnses.length;
1605 }
1606 return this.dnses.slice();
1607 }
1608 };
1610 function WifiScanResult() {}
1612 // TODO Make the difference between a DOM-based network object and our
1613 // networks objects much clearer.
1614 let netToDOM;
1615 let netFromDOM;
1617 function WifiWorker() {
1618 var self = this;
1620 this._mm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
1621 .getService(Ci.nsIMessageListenerManager);
1622 const messages = ["WifiManager:getNetworks", "WifiManager:getKnownNetworks",
1623 "WifiManager:associate", "WifiManager:forget",
1624 "WifiManager:wps", "WifiManager:getState",
1625 "WifiManager:setPowerSavingMode",
1626 "WifiManager:setHttpProxy",
1627 "WifiManager:setStaticIpMode",
1628 "child-process-shutdown"];
1630 messages.forEach((function(msgName) {
1631 this._mm.addMessageListener(msgName, this);
1632 }).bind(this));
1634 Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
1636 this.wantScanResults = [];
1638 this._allowWpaEap = false;
1639 this._needToEnableNetworks = false;
1640 this._highestPriority = -1;
1642 // Networks is a map from SSID -> a scan result.
1643 this.networks = Object.create(null);
1645 // ConfiguredNetworks is a map from SSID -> our view of a network. It only
1646 // lists networks known to the wpa_supplicant. The SSID field (and other
1647 // fields) are quoted for ease of use with WifiManager commands.
1648 // Note that we don't have to worry about escaping embedded quotes since in
1649 // all cases, the supplicant will take the last quotation that we pass it as
1650 // the end of the string.
1651 this.configuredNetworks = Object.create(null);
1652 this._addingNetworks = Object.create(null);
1654 this.currentNetwork = null;
1655 this.ipAddress = "";
1656 this.macAddress = null;
1658 this._lastConnectionInfo = null;
1659 this._connectionInfoTimer = null;
1660 this._reconnectOnDisconnect = false;
1662 // Create p2pObserver and assign to p2pManager.
1663 if (WifiManager.p2pSupported()) {
1664 this._p2pObserver = WifiP2pWorkerObserver(WifiManager.getP2pManager());
1665 WifiManager.getP2pManager().setObserver(this._p2pObserver);
1667 // Add DOM message observerd by p2pObserver to the message listener as well.
1668 this._p2pObserver.getObservedDOMMessages().forEach((function(msgName) {
1669 this._mm.addMessageListener(msgName, this);
1670 }).bind(this));
1671 }
1673 // Users of instances of nsITimer should keep a reference to the timer until
1674 // it is no longer needed in order to assure the timer is fired.
1675 this._callbackTimer = null;
1677 // XXX On some phones (Otoro and Unagi) the wifi driver doesn't play nicely
1678 // with the automatic scans that wpa_supplicant does (it appears that the
1679 // driver forgets that it's returned scan results and then refuses to try to
1680 // rescan. In order to detect this case we start a timer when we enter the
1681 // SCANNING state and reset it whenever we either get scan results or leave
1682 // the SCANNING state. If the timer fires, we assume that we are stuck and
1683 // forceably try to unstick the supplican, also turning on background
1684 // scanning to avoid having to constantly poke the supplicant.
1686 // How long we wait is controlled by the SCAN_STUCK_WAIT constant.
1687 const SCAN_STUCK_WAIT = 12000;
1688 this._scanStuckTimer = null;
1689 this._turnOnBackgroundScan = false;
1691 function startScanStuckTimer() {
1692 if (WifiManager.schedScanRecovery) {
1693 self._scanStuckTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1694 self._scanStuckTimer.initWithCallback(scanIsStuck, SCAN_STUCK_WAIT,
1695 Ci.nsITimer.TYPE_ONE_SHOT);
1696 }
1697 }
1699 function scanIsStuck() {
1700 // Uh-oh, we've waited too long for scan results. Disconnect (which
1701 // guarantees that we leave the SCANNING state and tells wpa_supplicant to
1702 // wait for our next command) ensure that background scanning is on and
1703 // then try again.
1704 debug("Determined that scanning is stuck, turning on background scanning!");
1705 WifiManager.handlePostWifiScan();
1706 WifiManager.disconnect(function(ok) {});
1707 self._turnOnBackgroundScan = true;
1708 }
1710 // A list of requests to turn wifi on or off.
1711 this._stateRequests = [];
1713 // Given a connection status network, takes a network from
1714 // self.configuredNetworks and prepares it for the DOM.
1715 netToDOM = function(net) {
1716 var ssid = dequote(net.ssid);
1717 var security = (net.key_mgmt === "NONE" && net.wep_key0) ? ["WEP"] :
1718 (net.key_mgmt && net.key_mgmt !== "NONE") ? [net.key_mgmt] :
1719 [];
1720 var password;
1721 if (("psk" in net && net.psk) ||
1722 ("password" in net && net.password) ||
1723 ("wep_key0" in net && net.wep_key0)) {
1724 password = "*";
1725 }
1727 var pub = new Network(ssid, security, password);
1728 if (net.identity)
1729 pub.identity = dequote(net.identity);
1730 if (net.netId)
1731 pub.known = true;
1732 if (net.scan_ssid === 1)
1733 pub.hidden = true;
1734 return pub;
1735 };
1737 netFromDOM = function(net, configured) {
1738 // Takes a network from the DOM and makes it suitable for insertion into
1739 // self.configuredNetworks (that is calling addNetwork will do the right
1740 // thing).
1741 // NB: Modifies net in place: safe since we don't share objects between
1742 // the dom and the chrome code.
1744 // Things that are useful for the UI but not to us.
1745 delete net.bssid;
1746 delete net.signalStrength;
1747 delete net.relSignalStrength;
1748 delete net.security;
1749 delete net.capabilities;
1751 if (!configured)
1752 configured = {};
1754 net.ssid = quote(net.ssid);
1756 let wep = false;
1757 if ("keyManagement" in net) {
1758 if (net.keyManagement === "WEP") {
1759 wep = true;
1760 net.keyManagement = "NONE";
1761 }
1763 configured.key_mgmt = net.key_mgmt = net.keyManagement; // WPA2-PSK, WPA-PSK, etc.
1764 delete net.keyManagement;
1765 } else {
1766 configured.key_mgmt = net.key_mgmt = "NONE";
1767 }
1769 if (net.hidden) {
1770 configured.scan_ssid = net.scan_ssid = 1;
1771 delete net.hidden;
1772 }
1774 function checkAssign(name, checkStar) {
1775 if (name in net) {
1776 let value = net[name];
1777 if (!value || (checkStar && value === '*')) {
1778 if (name in configured)
1779 net[name] = configured[name];
1780 else
1781 delete net[name];
1782 } else {
1783 configured[name] = net[name] = quote(value);
1784 }
1785 }
1786 }
1788 checkAssign("psk", true);
1789 checkAssign("identity", false);
1790 checkAssign("password", true);
1791 if (wep && net.wep && net.wep != '*') {
1792 configured.wep_key0 = net.wep_key0 = isWepHexKey(net.wep) ? net.wep : quote(net.wep);
1793 configured.auth_alg = net.auth_alg = "OPEN SHARED";
1794 }
1796 if ("pin" in net) {
1797 net.pin = quote(net.pin);
1798 }
1800 if ("phase1" in net)
1801 net.phase1 = quote(net.phase1);
1803 if ("phase2" in net)
1804 net.phase2 = quote(net.phase2);
1806 return net;
1807 };
1809 WifiManager.onsupplicantconnection = function() {
1810 debug("Connected to supplicant");
1811 WifiManager.enabled = true;
1812 self._reloadConfiguredNetworks(function(ok) {
1813 // Prime this.networks.
1814 if (!ok)
1815 return;
1817 self.waitForScan(function firstScan() {});
1818 // The select network command we used in associate() disables others networks.
1819 // Enable them here to make sure wpa_supplicant helps to connect to known
1820 // network automatically.
1821 self._enableAllNetworks();
1822 WifiManager.saveConfig(function() {})
1823 });
1825 try {
1826 self._allowWpaEap = WifiManager.getCapabilities().eapSim;
1827 } catch (e) {
1828 self._allowWpaEap = false;
1829 }
1831 // Notify everybody, even if they didn't ask us to come up.
1832 WifiManager.getMacAddress(function (mac) {
1833 self.macAddress = mac;
1834 debug("Got mac: " + mac);
1835 self._fireEvent("wifiUp", { macAddress: mac });
1836 self.requestDone();
1837 });
1839 if (WifiManager.state === "SCANNING")
1840 startScanStuckTimer();
1841 };
1843 WifiManager.onsupplicantlost = function() {
1844 WifiManager.enabled = WifiManager.supplicantStarted = false;
1845 WifiManager.state = "UNINITIALIZED";
1846 debug("Supplicant died!");
1848 // Notify everybody, even if they didn't ask us to come up.
1849 self._fireEvent("wifiDown", {});
1850 self.requestDone();
1851 };
1853 WifiManager.onpasswordmaybeincorrect = function() {
1854 WifiManager.authenticationFailuresCount++;
1855 };
1857 WifiManager.ondisconnected = function() {
1858 // We may fail to establish the connection, re-enable the
1859 // rest of our networks.
1860 if (self._needToEnableNetworks) {
1861 self._enableAllNetworks();
1862 self._needToEnableNetworks = false;
1863 }
1865 WifiManager.getCurrentNetworkId(this.ssid, function(netId) {
1866 // Trying to get netId from current network.
1867 if (!netId &&
1868 self.currentNetwork &&
1869 typeof self.currentNetwork.netId !== "undefined") {
1870 netId = self.currentNetwork.netId;
1871 }
1872 if (netId) {
1873 WifiManager.disableNetwork(netId, function() {});
1874 }
1875 });
1876 self._fireEvent("onconnectingfailed", {network: self.currentNetwork});
1877 }
1879 WifiManager.onstatechange = function() {
1880 debug("State change: " + this.prevState + " -> " + this.state);
1882 if (self._connectionInfoTimer &&
1883 this.state !== "CONNECTED" &&
1884 this.state !== "COMPLETED") {
1885 self._stopConnectionInfoTimer();
1886 }
1888 if (this.state !== "SCANNING" &&
1889 self._scanStuckTimer) {
1890 self._scanStuckTimer.cancel();
1891 self._scanStuckTimer = null;
1892 }
1894 switch (this.state) {
1895 case "DORMANT":
1896 // The dormant state is a bad state to be in since we won't
1897 // automatically connect. Try to knock us out of it. We only
1898 // hit this state when we've failed to run DHCP, so trying
1899 // again isn't the worst thing we can do. Eventually, we'll
1900 // need to detect if we're looping in this state and bail out.
1901 WifiManager.reconnect(function(){});
1902 break;
1903 case "ASSOCIATING":
1904 // id has not yet been filled in, so we can only report the ssid and
1905 // bssid.
1906 self.currentNetwork =
1907 { bssid: WifiManager.connectionInfo.bssid,
1908 ssid: quote(WifiManager.connectionInfo.ssid) };
1909 self._fireEvent("onconnecting", { network: netToDOM(self.currentNetwork) });
1910 break;
1911 case "ASSOCIATED":
1912 if (!self.currentNetwork) {
1913 self.currentNetwork =
1914 { bssid: WifiManager.connectionInfo.bssid,
1915 ssid: quote(WifiManager.connectionInfo.ssid) };
1916 }
1918 self.currentNetwork.netId = this.id;
1919 WifiManager.getNetworkConfiguration(self.currentNetwork, function (){});
1920 break;
1921 case "COMPLETED":
1922 // Now that we've successfully completed the connection, re-enable the
1923 // rest of our networks.
1924 // XXX Need to do this eventually if the user entered an incorrect
1925 // password. For now, we require user interaction to break the loop and
1926 // select a better network!
1927 if (self._needToEnableNetworks) {
1928 self._enableAllNetworks();
1929 self._needToEnableNetworks = false;
1930 }
1932 // We get the ASSOCIATED event when we've associated but not connected, so
1933 // wait until the handshake is complete.
1934 if (this.fromStatus || !self.currentNetwork) {
1935 // In this case, we connected to an already-connected wpa_supplicant,
1936 // because of that we need to gather information about the current
1937 // network here.
1938 self.currentNetwork = { ssid: quote(WifiManager.connectionInfo.ssid),
1939 netId: WifiManager.connectionInfo.id };
1940 WifiManager.getNetworkConfiguration(self.currentNetwork, function(){});
1941 }
1943 // Update http proxy when connected to network.
1944 let netConnect = WifiManager.getHttpProxyNetwork(self.currentNetwork);
1945 if (netConnect)
1946 WifiManager.setHttpProxy(netConnect);
1948 // The full authentication process is completed, reset the count.
1949 WifiManager.authenticationFailuresCount = 0;
1950 WifiManager.loopDetectionCount = 0;
1951 self._startConnectionInfoTimer();
1952 self._fireEvent("onassociate", { network: netToDOM(self.currentNetwork) });
1953 break;
1954 case "CONNECTED":
1955 // BSSID is read after connected, update it.
1956 self.currentNetwork.bssid = WifiManager.connectionInfo.bssid;
1957 break;
1958 case "DISCONNECTED":
1959 // wpa_supplicant may give us a "DISCONNECTED" event even if
1960 // we are already in "DISCONNECTED" state.
1961 if ((WifiNetworkInterface.state ===
1962 Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED) &&
1963 (this.prevState === "INITIALIZING" ||
1964 this.prevState === "DISCONNECTED" ||
1965 this.prevState === "INTERFACE_DISABLED" ||
1966 this.prevState === "INACTIVE" ||
1967 this.prevState === "UNINITIALIZED")) {
1968 return;
1969 }
1971 self._fireEvent("ondisconnect", {});
1973 // When disconnected, clear the http proxy setting if it exists.
1974 // Temporarily set http proxy to empty and restore user setting after setHttpProxy.
1975 let netDisconnect = WifiManager.getHttpProxyNetwork(self.currentNetwork);
1976 if (netDisconnect) {
1977 let prehttpProxyHostSetting = netDisconnect.httpProxyHost;
1978 let prehttpProxyPortSetting = netDisconnect.httpProxyPort;
1980 netDisconnect.httpProxyHost = "";
1981 netDisconnect.httpProxyPort = 0;
1983 WifiManager.setHttpProxy(netDisconnect);
1985 netDisconnect.httpProxyHost = prehttpProxyHostSetting;
1986 netDisconnect.httpProxyPort = prehttpProxyPortSetting;
1987 }
1989 self.currentNetwork = null;
1990 self.ipAddress = "";
1992 if (self._turnOnBackgroundScan) {
1993 self._turnOnBackgroundScan = false;
1994 WifiManager.setBackgroundScan("ON", function(did_something, ok) {
1995 WifiManager.reassociate(function() {});
1996 });
1997 }
1999 WifiManager.connectionDropped(function() {
2000 // We've disconnected from a network because of a call to forgetNetwork.
2001 // Reconnect to the next available network (if any).
2002 if (self._reconnectOnDisconnect) {
2003 self._reconnectOnDisconnect = false;
2004 WifiManager.reconnect(function(){});
2005 }
2006 });
2008 WifiNetworkInterface.state =
2009 Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
2010 WifiNetworkInterface.ips = [];
2011 WifiNetworkInterface.prefixLengths = [];
2012 WifiNetworkInterface.gateways = [];
2013 WifiNetworkInterface.dnses = [];
2014 Services.obs.notifyObservers(WifiNetworkInterface,
2015 kNetworkInterfaceStateChangedTopic,
2016 null);
2018 break;
2019 case "WPS_TIMEOUT":
2020 self._fireEvent("onwpstimeout", {});
2021 break;
2022 case "WPS_FAIL":
2023 self._fireEvent("onwpsfail", {});
2024 break;
2025 case "WPS_OVERLAP_DETECTED":
2026 self._fireEvent("onwpsoverlap", {});
2027 break;
2028 case "SCANNING":
2029 // If we're already scanning in the background, we don't need to worry
2030 // about getting stuck while scanning.
2031 if (!WifiManager.backgroundScanEnabled && WifiManager.enabled)
2032 startScanStuckTimer();
2033 break;
2034 }
2035 };
2037 WifiManager.onnetworkconnected = function() {
2038 if (!this.info || !this.info.ipaddr_str) {
2039 debug("Network information is invalid.");
2040 return;
2041 }
2043 let maskLength =
2044 netHelpers.getMaskLength(netHelpers.stringToIP(this.info.mask_str));
2045 if (!maskLength) {
2046 maskLength = 32; // max prefix for IPv4.
2047 }
2048 WifiNetworkInterface.state =
2049 Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
2050 WifiNetworkInterface.ips = [this.info.ipaddr_str];
2051 WifiNetworkInterface.prefixLengths = [maskLength];
2052 WifiNetworkInterface.gateways = [this.info.gateway_str];
2053 if (typeof this.info.dns1_str == "string" &&
2054 this.info.dns1_str.length) {
2055 WifiNetworkInterface.dnses.push(this.info.dns1_str);
2056 }
2057 if (typeof this.info.dns2_str == "string" &&
2058 this.info.dns2_str.length) {
2059 WifiNetworkInterface.dnses.push(this.info.dns2_str);
2060 }
2061 Services.obs.notifyObservers(WifiNetworkInterface,
2062 kNetworkInterfaceStateChangedTopic,
2063 null);
2065 self.ipAddress = this.info.ipaddr_str;
2067 // We start the connection information timer when we associate, but
2068 // don't have our IP address until here. Make sure that we fire a new
2069 // connectionInformation event with the IP address the next time the
2070 // timer fires.
2071 self._lastConnectionInfo = null;
2072 self._fireEvent("onconnect", { network: netToDOM(self.currentNetwork) });
2073 };
2075 WifiManager.onscanresultsavailable = function() {
2076 if (self._scanStuckTimer) {
2077 // We got scan results! We must not be stuck for now, try again.
2078 self._scanStuckTimer.cancel();
2079 self._scanStuckTimer.initWithCallback(scanIsStuck, SCAN_STUCK_WAIT,
2080 Ci.nsITimer.TYPE_ONE_SHOT);
2081 }
2083 if (self.wantScanResults.length === 0) {
2084 debug("Scan results available, but we don't need them");
2085 return;
2086 }
2088 debug("Scan results are available! Asking for them.");
2089 WifiManager.getScanResults(function(r) {
2090 // Failure.
2091 if (!r) {
2092 self.wantScanResults.forEach(function(callback) { callback(null) });
2093 self.wantScanResults = [];
2094 return;
2095 }
2097 // Now that we have scan results, there's no more need to continue
2098 // scanning. Ignore any errors from this command.
2099 WifiManager.setScanMode("inactive", function() {});
2100 let lines = r.split("\n");
2101 // NB: Skip the header line.
2102 self.networksArray = [];
2103 for (let i = 1; i < lines.length; ++i) {
2104 // bssid / frequency / signal level / flags / ssid
2105 var match = /([\S]+)\s+([\S]+)\s+([\S]+)\s+(\[[\S]+\])?\s(.*)/.exec(lines[i]);
2107 if (match && match[5]) {
2108 let ssid = match[5],
2109 bssid = match[1],
2110 signalLevel = match[3],
2111 flags = match[4];
2113 // Skip ad-hoc networks which aren't supported (bug 811635).
2114 if (flags && flags.indexOf("[IBSS]") >= 0)
2115 continue;
2117 // If this is the first time that we've seen this SSID in the scan
2118 // results, add it to the list along with any other information.
2119 // Also, we use the highest signal strength that we see.
2120 let network = new ScanResult(ssid, bssid, flags, signalLevel);
2122 let networkKey = getNetworkKey(network);
2123 let eapIndex = -1;
2124 if (networkKey in self.configuredNetworks) {
2125 let known = self.configuredNetworks[networkKey];
2126 network.known = true;
2128 if ("identity" in known && known.identity)
2129 network.identity = dequote(known.identity);
2131 // Note: we don't hand out passwords here! The * marks that there
2132 // is a password that we're hiding.
2133 if (("psk" in known && known.psk) ||
2134 ("password" in known && known.password) ||
2135 ("wep_key0" in known && known.wep_key0)) {
2136 network.password = "*";
2137 }
2138 } else if (!self._allowWpaEap &&
2139 (eapIndex = network.security.indexOf("WPA-EAP")) >= 0) {
2140 // Don't offer to connect to WPA-EAP networks unless one has been
2141 // configured through other means (e.g. it was added directly to
2142 // wpa_supplicant.conf). Here, we have an unknown WPA-EAP network,
2143 // so we ignore it entirely if it only supports WPA-EAP, otherwise
2144 // we take EAP out of the list and offer the rest of the
2145 // security.
2146 if (network.security.length === 1)
2147 continue;
2149 network.security.splice(eapIndex, 1);
2150 }
2152 self.networksArray.push(network);
2153 if (network.bssid === WifiManager.connectionInfo.bssid)
2154 network.connected = true;
2156 let signal = calculateSignal(Number(match[3]));
2157 if (signal > network.relSignalStrength)
2158 network.relSignalStrength = signal;
2159 } else if (!match) {
2160 debug("Match didn't find anything for: " + lines[i]);
2161 }
2162 }
2164 self.wantScanResults.forEach(function(callback) { callback(self.networksArray) });
2165 self.wantScanResults = [];
2166 });
2167 };
2169 // Read the 'wifi.enabled' setting in order to start with a known
2170 // value at boot time. The handle() will be called after reading.
2171 //
2172 // nsISettingsServiceCallback implementation.
2173 var initWifiEnabledCb = {
2174 handle: function handle(aName, aResult) {
2175 if (aName !== SETTINGS_WIFI_ENABLED)
2176 return;
2177 if (aResult === null)
2178 aResult = true;
2179 self.handleWifiEnabled(aResult);
2180 },
2181 handleError: function handleError(aErrorMessage) {
2182 debug("Error reading the 'wifi.enabled' setting. Default to wifi on.");
2183 self.handleWifiEnabled(true);
2184 }
2185 };
2187 var initWifiDebuggingEnabledCb = {
2188 handle: function handle(aName, aResult) {
2189 if (aName !== SETTINGS_WIFI_DEBUG_ENABLED)
2190 return;
2191 if (aResult === null)
2192 aResult = false;
2193 DEBUG = aResult;
2194 updateDebug();
2195 },
2196 handleError: function handleError(aErrorMessage) {
2197 debug("Error reading the 'wifi.debugging.enabled' setting. Default to debugging off.");
2198 DEBUG = false;
2199 updateDebug();
2200 }
2201 };
2203 this.initTetheringSettings();
2205 let lock = gSettingsService.createLock();
2206 lock.get(SETTINGS_WIFI_ENABLED, initWifiEnabledCb);
2207 lock.get(SETTINGS_WIFI_DEBUG_ENABLED, initWifiDebuggingEnabledCb);
2209 lock.get(SETTINGS_WIFI_SSID, this);
2210 lock.get(SETTINGS_WIFI_SECURITY_TYPE, this);
2211 lock.get(SETTINGS_WIFI_SECURITY_PASSWORD, this);
2212 lock.get(SETTINGS_WIFI_IP, this);
2213 lock.get(SETTINGS_WIFI_PREFIX, this);
2214 lock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
2215 lock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
2216 lock.get(SETTINGS_WIFI_DNS1, this);
2217 lock.get(SETTINGS_WIFI_DNS2, this);
2218 lock.get(SETTINGS_WIFI_TETHERING_ENABLED, this);
2220 lock.get(SETTINGS_USB_DHCPSERVER_STARTIP, this);
2221 lock.get(SETTINGS_USB_DHCPSERVER_ENDIP, this);
2223 this._wifiTetheringSettingsToRead = [SETTINGS_WIFI_SSID,
2224 SETTINGS_WIFI_SECURITY_TYPE,
2225 SETTINGS_WIFI_SECURITY_PASSWORD,
2226 SETTINGS_WIFI_IP,
2227 SETTINGS_WIFI_PREFIX,
2228 SETTINGS_WIFI_DHCPSERVER_STARTIP,
2229 SETTINGS_WIFI_DHCPSERVER_ENDIP,
2230 SETTINGS_WIFI_DNS1,
2231 SETTINGS_WIFI_DNS2,
2232 SETTINGS_WIFI_TETHERING_ENABLED,
2233 SETTINGS_USB_DHCPSERVER_STARTIP,
2234 SETTINGS_USB_DHCPSERVER_ENDIP];
2235 }
2237 function translateState(state) {
2238 switch (state) {
2239 case "INTERFACE_DISABLED":
2240 case "INACTIVE":
2241 case "SCANNING":
2242 case "DISCONNECTED":
2243 default:
2244 return "disconnected";
2246 case "AUTHENTICATING":
2247 case "ASSOCIATING":
2248 case "ASSOCIATED":
2249 case "FOUR_WAY_HANDSHAKE":
2250 case "GROUP_HANDSHAKE":
2251 return "connecting";
2253 case "COMPLETED":
2254 return WifiManager.getDhcpInfo() ? "connected" : "associated";
2255 }
2256 }
2258 WifiWorker.prototype = {
2259 classID: WIFIWORKER_CID,
2260 classInfo: XPCOMUtils.generateCI({classID: WIFIWORKER_CID,
2261 contractID: WIFIWORKER_CONTRACTID,
2262 classDescription: "WifiWorker",
2263 interfaces: [Ci.nsIWorkerHolder,
2264 Ci.nsIWifi,
2265 Ci.nsIObserver]}),
2267 QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
2268 Ci.nsIWifi,
2269 Ci.nsISettingsServiceCallback]),
2271 disconnectedByWifi: false,
2273 disconnectedByWifiTethering: false,
2275 _wifiTetheringSettingsToRead: [],
2277 _oldWifiTetheringEnabledState: null,
2279 tetheringSettings: {},
2281 initTetheringSettings: function initTetheringSettings() {
2282 this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = null;
2283 this.tetheringSettings[SETTINGS_WIFI_SSID] = DEFAULT_WIFI_SSID;
2284 this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE] = DEFAULT_WIFI_SECURITY_TYPE;
2285 this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD] = DEFAULT_WIFI_SECURITY_PASSWORD;
2286 this.tetheringSettings[SETTINGS_WIFI_IP] = DEFAULT_WIFI_IP;
2287 this.tetheringSettings[SETTINGS_WIFI_PREFIX] = DEFAULT_WIFI_PREFIX;
2288 this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
2289 this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP;
2290 this.tetheringSettings[SETTINGS_WIFI_DNS1] = DEFAULT_DNS1;
2291 this.tetheringSettings[SETTINGS_WIFI_DNS2] = DEFAULT_DNS2;
2293 this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP] = DEFAULT_USB_DHCPSERVER_STARTIP;
2294 this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP] = DEFAULT_USB_DHCPSERVER_ENDIP;
2295 },
2297 // Internal methods.
2298 waitForScan: function(callback) {
2299 this.wantScanResults.push(callback);
2300 },
2302 // In order to select a specific network, we disable the rest of the
2303 // networks known to us. However, in general, we want the supplicant to
2304 // connect to which ever network it thinks is best, so when we select the
2305 // proper network (or fail to), we need to re-enable the rest.
2306 _enableAllNetworks: function() {
2307 for each (let net in this.configuredNetworks) {
2308 WifiManager.enableNetwork(net.netId, false, function(ok) {
2309 net.disabled = ok ? 1 : 0;
2310 });
2311 }
2312 },
2314 _startConnectionInfoTimer: function() {
2315 if (this._connectionInfoTimer)
2316 return;
2318 var self = this;
2319 function getConnectionInformation() {
2320 WifiManager.getConnectionInfo(function(connInfo) {
2321 // See comments in calculateSignal for information about this.
2322 if (!connInfo) {
2323 self._lastConnectionInfo = null;
2324 return;
2325 }
2327 let { rssi, linkspeed } = connInfo;
2328 if (rssi > 0)
2329 rssi -= 256;
2330 if (rssi <= MIN_RSSI)
2331 rssi = MIN_RSSI;
2332 else if (rssi >= MAX_RSSI)
2333 rssi = MAX_RSSI;
2335 let info = { signalStrength: rssi,
2336 relSignalStrength: calculateSignal(rssi),
2337 linkSpeed: linkspeed,
2338 ipAddress: self.ipAddress };
2339 let last = self._lastConnectionInfo;
2341 // Only fire the event if the link speed changed or the signal
2342 // strength changed by more than 10%.
2343 function tensPlace(percent) ((percent / 10) | 0)
2345 if (last && last.linkSpeed === info.linkSpeed &&
2346 tensPlace(last.relSignalStrength) === tensPlace(info.relSignalStrength)) {
2347 return;
2348 }
2350 self._lastConnectionInfo = info;
2351 debug("Firing connectionInfoUpdate: " + uneval(info));
2352 self._fireEvent("connectionInfoUpdate", info);
2353 });
2354 }
2356 // Prime our _lastConnectionInfo immediately and fire the event at the
2357 // same time.
2358 getConnectionInformation();
2360 // Now, set up the timer for regular updates.
2361 this._connectionInfoTimer =
2362 Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
2363 this._connectionInfoTimer.init(getConnectionInformation, 5000,
2364 Ci.nsITimer.TYPE_REPEATING_SLACK);
2365 },
2367 _stopConnectionInfoTimer: function() {
2368 if (!this._connectionInfoTimer)
2369 return;
2371 this._connectionInfoTimer.cancel();
2372 this._connectionInfoTimer = null;
2373 this._lastConnectionInfo = null;
2374 },
2376 _reloadConfiguredNetworks: function(callback) {
2377 WifiManager.getConfiguredNetworks((function(networks) {
2378 if (!networks) {
2379 debug("Unable to get configured networks");
2380 callback(false);
2381 return;
2382 }
2384 this._highestPriority = -1;
2386 // Convert between netId-based and ssid-based indexing.
2387 for (let net in networks) {
2388 let network = networks[net];
2389 delete networks[net];
2391 if (!network.ssid) {
2392 WifiManager.removeNetwork(network.netId, function() {});
2393 continue;
2394 }
2396 if (network.priority && network.priority > this._highestPriority)
2397 this._highestPriority = network.priority;
2399 let networkKey = getNetworkKey(network);
2400 // Accept latest config of same network(same SSID and same security).
2401 if (networks[networkKey]) {
2402 WifiManager.removeNetwork(networks[networkKey].netId, function() {});
2403 }
2404 networks[networkKey] = network;
2405 }
2407 this.configuredNetworks = networks;
2408 callback(true);
2409 }).bind(this));
2410 },
2412 // Important side effect: calls WifiManager.saveConfig.
2413 _reprioritizeNetworks: function(callback) {
2414 // First, sort the networks in orer of their priority.
2415 var ordered = Object.getOwnPropertyNames(this.configuredNetworks);
2416 let self = this;
2417 ordered.sort(function(a, b) {
2418 var neta = self.configuredNetworks[a],
2419 netb = self.configuredNetworks[b];
2421 // Sort unsorted networks to the end of the list.
2422 if (isNaN(neta.priority))
2423 return isNaN(netb.priority) ? 0 : 1;
2424 if (isNaN(netb.priority))
2425 return -1;
2426 return netb.priority - neta.priority;
2427 });
2429 // Skip unsorted networks.
2430 let newPriority = 0, i;
2431 for (i = ordered.length - 1; i >= 0; --i) {
2432 if (!isNaN(this.configuredNetworks[ordered[i]].priority))
2433 break;
2434 }
2436 // No networks we care about?
2437 if (i < 0) {
2438 WifiManager.saveConfig(callback);
2439 return;
2440 }
2442 // Now assign priorities from 0 to length, starting with the smallest
2443 // priority and heading towards the highest (note the dependency between
2444 // total and i here).
2445 let done = 0, errors = 0, total = i + 1;
2446 for (; i >= 0; --i) {
2447 let network = this.configuredNetworks[ordered[i]];
2448 network.priority = newPriority++;
2450 // Note: networkUpdated declared below since it happens logically after
2451 // this loop.
2452 WifiManager.updateNetwork(network, networkUpdated);
2453 }
2455 function networkUpdated(ok) {
2456 if (!ok)
2457 ++errors;
2458 if (++done === total) {
2459 if (errors > 0) {
2460 callback(false);
2461 return;
2462 }
2464 WifiManager.saveConfig(function(ok) {
2465 if (!ok) {
2466 callback(false);
2467 return;
2468 }
2470 self._reloadConfiguredNetworks(function(ok) {
2471 callback(ok);
2472 });
2473 });
2474 }
2475 }
2476 },
2478 // nsIWifi
2480 _domManagers: [],
2481 _fireEvent: function(message, data) {
2482 this._domManagers.forEach(function(manager) {
2483 // Note: We should never have a dead message manager here because we
2484 // observe our child message managers shutting down, below.
2485 manager.sendAsyncMessage("WifiManager:" + message, data);
2486 });
2487 },
2489 _sendMessage: function(message, success, data, msg) {
2490 try {
2491 msg.manager.sendAsyncMessage(message + (success ? ":OK" : ":NO"),
2492 { data: data, rid: msg.rid, mid: msg.mid });
2493 } catch (e) {
2494 debug("sendAsyncMessage error : " + e);
2495 }
2496 this._splicePendingRequest(msg);
2497 },
2499 _domRequest: [],
2501 _splicePendingRequest: function(msg) {
2502 for (let i = 0; i < this._domRequest.length; i++) {
2503 if (this._domRequest[i].msg === msg) {
2504 this._domRequest.splice(i, 1);
2505 return;
2506 }
2507 }
2508 },
2510 _clearPendingRequest: function() {
2511 if (this._domRequest.length === 0) return;
2512 this._domRequest.forEach((function(req) {
2513 this._sendMessage(req.name + ":Return", false, "Wifi is disabled", req.msg);
2514 }).bind(this));
2515 },
2517 receiveMessage: function MessageManager_receiveMessage(aMessage) {
2518 let msg = aMessage.data || {};
2519 msg.manager = aMessage.target;
2521 if (WifiManager.p2pSupported()) {
2522 // If p2pObserver returns something truthy, return it!
2523 // Otherwise, continue to do the rest of tasks.
2524 var p2pRet = this._p2pObserver.onDOMMessage(aMessage);
2525 if (p2pRet) {
2526 return p2pRet;
2527 }
2528 }
2530 // Note: By the time we receive child-process-shutdown, the child process
2531 // has already forgotten its permissions so we do this before the
2532 // permissions check.
2533 if (aMessage.name === "child-process-shutdown") {
2534 let i;
2535 if ((i = this._domManagers.indexOf(msg.manager)) != -1) {
2536 this._domManagers.splice(i, 1);
2537 }
2538 for (i = this._domRequest.length - 1; i >= 0; i--) {
2539 if (this._domRequest[i].msg.manager === msg.manager) {
2540 this._domRequest.splice(i, 1);
2541 }
2542 }
2543 return;
2544 }
2546 if (!aMessage.target.assertPermission("wifi-manage")) {
2547 return;
2548 }
2550 // We are interested in DOMRequests only.
2551 if (aMessage.name != "WifiManager:getState") {
2552 this._domRequest.push({name: aMessage.name, msg:msg});
2553 }
2555 switch (aMessage.name) {
2556 case "WifiManager:getNetworks":
2557 this.getNetworks(msg);
2558 break;
2559 case "WifiManager:getKnownNetworks":
2560 this.getKnownNetworks(msg);
2561 break;
2562 case "WifiManager:associate":
2563 this.associate(msg);
2564 break;
2565 case "WifiManager:forget":
2566 this.forget(msg);
2567 break;
2568 case "WifiManager:wps":
2569 this.wps(msg);
2570 break;
2571 case "WifiManager:setPowerSavingMode":
2572 this.setPowerSavingMode(msg);
2573 break;
2574 case "WifiManager:setHttpProxy":
2575 this.setHttpProxy(msg);
2576 break;
2577 case "WifiManager:setStaticIpMode":
2578 this.setStaticIpMode(msg);
2579 break;
2580 case "WifiManager:getState": {
2581 let i;
2582 if ((i = this._domManagers.indexOf(msg.manager)) === -1) {
2583 this._domManagers.push(msg.manager);
2584 }
2586 let net = this.currentNetwork ? netToDOM(this.currentNetwork) : null;
2587 return { network: net,
2588 connectionInfo: this._lastConnectionInfo,
2589 enabled: WifiManager.enabled,
2590 status: translateState(WifiManager.state),
2591 macAddress: this.macAddress };
2592 }
2593 }
2594 },
2596 getNetworks: function(msg) {
2597 const message = "WifiManager:getNetworks:Return";
2598 if (!WifiManager.enabled) {
2599 this._sendMessage(message, false, "Wifi is disabled", msg);
2600 return;
2601 }
2603 let sent = false;
2604 let callback = (function (networks) {
2605 if (sent)
2606 return;
2607 sent = true;
2608 this._sendMessage(message, networks !== null, networks, msg);
2609 }).bind(this);
2610 this.waitForScan(callback);
2612 WifiManager.scan(true, (function(ok) {
2613 // If the scan command succeeded, we're done.
2614 if (ok)
2615 return;
2617 // Avoid sending multiple responses.
2618 if (sent)
2619 return;
2621 // Otherwise, let the client know that it failed, it's responsible for
2622 // trying again in a few seconds.
2623 sent = true;
2624 this._sendMessage(message, false, "ScanFailed", msg);
2625 }).bind(this));
2626 },
2628 getWifiScanResults: function(callback) {
2629 var count = 0;
2630 var timer = null;
2631 var self = this;
2633 self.waitForScan(waitForScanCallback);
2634 doScan();
2635 function doScan() {
2636 WifiManager.scan(true, (function (ok) {
2637 if (!ok) {
2638 if (!timer) {
2639 count = 0;
2640 timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
2641 }
2643 if (count++ >= 3) {
2644 timer = null;
2645 self.wantScanResults.splice(self.wantScanResults.indexOf(waitForScanCallback), 1);
2646 callback.onfailure();
2647 return;
2648 }
2650 // Else it's still running, continue waiting.
2651 timer.initWithCallback(doScan, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
2652 return;
2653 }
2654 }).bind(this));
2655 }
2657 function waitForScanCallback(networks) {
2658 if (networks === null) {
2659 callback.onfailure();
2660 return;
2661 }
2663 var wifiScanResults = new Array();
2664 var net;
2665 for (let net in networks) {
2666 let value = networks[net];
2667 wifiScanResults.push(transformResult(value));
2668 }
2669 callback.onready(wifiScanResults.length, wifiScanResults);
2670 }
2672 function transformResult(element) {
2673 var result = new WifiScanResult();
2674 result.connected = false;
2675 for (let id in element) {
2676 if (id === "__exposedProps__") {
2677 continue;
2678 }
2679 if (id === "security") {
2680 result[id] = 0;
2681 var security = element[id];
2682 for (let j = 0; j < security.length; j++) {
2683 if (security[j] === "WPA-PSK") {
2684 result[id] |= Ci.nsIWifiScanResult.WPA_PSK;
2685 } else if (security[j] === "WPA-EAP") {
2686 result[id] |= Ci.nsIWifiScanResult.WPA_EAP;
2687 } else if (security[j] === "WEP") {
2688 result[id] |= Ci.nsIWifiScanResult.WEP;
2689 } else {
2690 result[id] = 0;
2691 }
2692 }
2693 } else {
2694 result[id] = element[id];
2695 }
2696 }
2697 return result;
2698 }
2699 },
2701 getKnownNetworks: function(msg) {
2702 const message = "WifiManager:getKnownNetworks:Return";
2703 if (!WifiManager.enabled) {
2704 this._sendMessage(message, false, "Wifi is disabled", msg);
2705 return;
2706 }
2708 this._reloadConfiguredNetworks((function(ok) {
2709 if (!ok) {
2710 this._sendMessage(message, false, "Failed", msg);
2711 return;
2712 }
2714 var networks = [];
2715 for (let networkKey in this.configuredNetworks) {
2716 networks.push(netToDOM(this.configuredNetworks[networkKey]));
2717 }
2719 this._sendMessage(message, true, networks, msg);
2720 }).bind(this));
2721 },
2723 _setWifiEnabledCallback: function(status) {
2724 if (status !== 0) {
2725 this.requestDone();
2726 return;
2727 }
2729 // If we're enabling ourselves, then wait until we've connected to the
2730 // supplicant to notify. If we're disabling, we take care of this in
2731 // supplicantlost.
2732 if (WifiManager.supplicantStarted)
2733 WifiManager.start();
2734 },
2736 setWifiEnabled: function(enabled, callback) {
2737 // Reply error to pending requests.
2738 if (!enabled) {
2739 this._clearPendingRequest();
2740 }
2742 WifiManager.setWifiEnabled(enabled, callback);
2743 },
2745 // requestDone() must be called to before callback complete(or error)
2746 // so next queue in the request quene can be executed.
2747 queueRequest: function(data, callback) {
2748 if (!callback) {
2749 throw "Try to enqueue a request without callback";
2750 }
2752 let optimizeCommandList = ["setWifiEnabled", "setWifiApEnabled"];
2753 if (optimizeCommandList.indexOf(data.command) != -1) {
2754 this._stateRequests = this._stateRequests.filter(function(element) {
2755 return element.data.command !== data.command;
2756 });
2757 }
2759 this._stateRequests.push({
2760 data: data,
2761 callback: callback
2762 });
2764 this.nextRequest();
2765 },
2767 getWifiTetheringParameters: function getWifiTetheringParameters(enable) {
2768 let ssid;
2769 let securityType;
2770 let securityId;
2771 let interfaceIp;
2772 let prefix;
2773 let wifiDhcpStartIp;
2774 let wifiDhcpEndIp;
2775 let usbDhcpStartIp;
2776 let usbDhcpEndIp;
2777 let dns1;
2778 let dns2;
2780 ssid = this.tetheringSettings[SETTINGS_WIFI_SSID];
2781 securityType = this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE];
2782 securityId = this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD];
2783 interfaceIp = this.tetheringSettings[SETTINGS_WIFI_IP];
2784 prefix = this.tetheringSettings[SETTINGS_WIFI_PREFIX];
2785 wifiDhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
2786 wifiDhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
2787 usbDhcpStartIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP];
2788 usbDhcpEndIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP];
2789 dns1 = this.tetheringSettings[SETTINGS_WIFI_DNS1];
2790 dns2 = this.tetheringSettings[SETTINGS_WIFI_DNS2];
2792 // Check the format to prevent netd from crash.
2793 if (!ssid || ssid == "") {
2794 debug("Invalid SSID value.");
2795 return null;
2796 }
2797 if (securityType != WIFI_SECURITY_TYPE_NONE &&
2798 securityType != WIFI_SECURITY_TYPE_WPA_PSK &&
2799 securityType != WIFI_SECURITY_TYPE_WPA2_PSK) {
2801 debug("Invalid security type.");
2802 return null;
2803 }
2804 if (securityType != WIFI_SECURITY_TYPE_NONE && !securityId) {
2805 debug("Invalid security password.");
2806 return null;
2807 }
2808 // Using the default values here until application supports these settings.
2809 if (interfaceIp == "" || prefix == "" ||
2810 wifiDhcpStartIp == "" || wifiDhcpEndIp == "" ||
2811 usbDhcpStartIp == "" || usbDhcpEndIp == "") {
2812 debug("Invalid subnet information.");
2813 return null;
2814 }
2816 return {
2817 ssid: ssid,
2818 security: securityType,
2819 key: securityId,
2820 ip: interfaceIp,
2821 prefix: prefix,
2822 wifiStartIp: wifiDhcpStartIp,
2823 wifiEndIp: wifiDhcpEndIp,
2824 usbStartIp: usbDhcpStartIp,
2825 usbEndIp: usbDhcpEndIp,
2826 dns1: dns1,
2827 dns2: dns2,
2828 enable: enable,
2829 mode: enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION,
2830 link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
2831 };
2832 },
2834 setWifiApEnabled: function(enabled, callback) {
2835 let configuration = this.getWifiTetheringParameters(enabled);
2837 if (!configuration) {
2838 this.requestDone();
2839 debug("Invalid Wifi Tethering configuration.");
2840 return;
2841 }
2843 WifiManager.setWifiApEnabled(enabled, configuration, callback);
2844 },
2846 associate: function(msg) {
2847 const MAX_PRIORITY = 9999;
2848 const message = "WifiManager:associate:Return";
2849 let network = msg.data;
2851 let privnet = network;
2852 let dontConnect = privnet.dontConnect;
2853 delete privnet.dontConnect;
2855 if (!WifiManager.enabled) {
2856 this._sendMessage(message, false, "Wifi is disabled", msg);
2857 return;
2858 }
2860 let self = this;
2861 function networkReady() {
2862 // saveConfig now before we disable most of the other networks.
2863 function selectAndConnect() {
2864 WifiManager.enableNetwork(privnet.netId, true, function (ok) {
2865 if (ok)
2866 self._needToEnableNetworks = true;
2867 if (WifiManager.state === "DISCONNECTED" ||
2868 WifiManager.state === "SCANNING") {
2869 WifiManager.reconnect(function (ok) {
2870 self._sendMessage(message, ok, ok, msg);
2871 });
2872 } else {
2873 self._sendMessage(message, ok, ok, msg);
2874 }
2875 });
2876 }
2878 var selectAndConnectOrReturn = dontConnect ?
2879 function() {
2880 self._sendMessage(message, true, "Wifi has been recorded", msg);
2881 } : selectAndConnect;
2882 if (self._highestPriority >= MAX_PRIORITY) {
2883 self._reprioritizeNetworks(selectAndConnectOrReturn);
2884 } else {
2885 WifiManager.saveConfig(selectAndConnectOrReturn);
2886 }
2887 }
2889 let ssid = privnet.ssid;
2890 let networkKey = getNetworkKey(privnet);
2891 let configured;
2893 if (networkKey in this._addingNetworks) {
2894 this._sendMessage(message, false, "Racing associates");
2895 return;
2896 }
2898 if (networkKey in this.configuredNetworks)
2899 configured = this.configuredNetworks[networkKey];
2901 netFromDOM(privnet, configured);
2903 privnet.priority = ++this._highestPriority;
2904 if (configured) {
2905 privnet.netId = configured.netId;
2906 WifiManager.updateNetwork(privnet, (function(ok) {
2907 if (!ok) {
2908 this._sendMessage(message, false, "Network is misconfigured", msg);
2909 return;
2910 }
2912 networkReady();
2913 }).bind(this));
2914 } else {
2915 // networkReady, above, calls saveConfig. We want to remember the new
2916 // network as being enabled, which isn't the default, so we explicitly
2917 // set it to being "enabled" before we add it and save the
2918 // configuration.
2919 privnet.disabled = 0;
2920 this._addingNetworks[networkKey] = privnet;
2921 WifiManager.addNetwork(privnet, (function(ok) {
2922 delete this._addingNetworks[networkKey];
2924 if (!ok) {
2925 this._sendMessage(message, false, "Network is misconfigured", msg);
2926 return;
2927 }
2929 this.configuredNetworks[networkKey] = privnet;
2930 networkReady();
2931 }).bind(this));
2932 }
2933 },
2935 forget: function(msg) {
2936 const message = "WifiManager:forget:Return";
2937 let network = msg.data;
2938 if (!WifiManager.enabled) {
2939 this._sendMessage(message, false, "Wifi is disabled", msg);
2940 return;
2941 }
2943 this._reloadConfiguredNetworks((function(ok) {
2944 // Give it a chance to remove the network even if reload is failed.
2945 if (!ok) {
2946 debug("Warning !!! Failed to reload the configured networks");
2947 }
2949 let ssid = network.ssid;
2950 let networkKey = getNetworkKey(network);
2951 if (!(networkKey in this.configuredNetworks)) {
2952 this._sendMessage(message, false, "Trying to forget an unknown network", msg);
2953 return;
2954 }
2956 let self = this;
2957 let configured = this.configuredNetworks[networkKey];
2958 this._reconnectOnDisconnect = (this.currentNetwork &&
2959 (this.currentNetwork.ssid === ssid));
2960 WifiManager.removeNetwork(configured.netId, function(ok) {
2961 if (!ok) {
2962 self._sendMessage(message, false, "Unable to remove the network", msg);
2963 self._reconnectOnDisconnect = false;
2964 return;
2965 }
2967 WifiManager.saveConfig(function() {
2968 self._reloadConfiguredNetworks(function() {
2969 self._sendMessage(message, true, true, msg);
2970 });
2971 });
2972 });
2973 }).bind(this));
2974 },
2976 wps: function(msg) {
2977 const message = "WifiManager:wps:Return";
2978 let self = this;
2979 let detail = msg.data;
2980 if (detail.method === "pbc") {
2981 WifiManager.wpsPbc(WifiManager.ifname, function(ok) {
2982 if (ok)
2983 self._sendMessage(message, true, true, msg);
2984 else
2985 self._sendMessage(message, false, "WPS PBC failed", msg);
2986 });
2987 } else if (detail.method === "pin") {
2988 WifiManager.wpsPin(detail, function(pin) {
2989 if (pin)
2990 self._sendMessage(message, true, pin, msg);
2991 else
2992 self._sendMessage(message, false, "WPS PIN failed", msg);
2993 });
2994 } else if (detail.method === "cancel") {
2995 WifiManager.wpsCancel(function(ok) {
2996 if (ok)
2997 self._sendMessage(message, true, true, msg);
2998 else
2999 self._sendMessage(message, false, "WPS Cancel failed", msg);
3000 });
3001 } else {
3002 self._sendMessage(message, false, "Invalid WPS method=" + detail.method,
3003 msg);
3004 }
3005 },
3007 setPowerSavingMode: function(msg) {
3008 const message = "WifiManager:setPowerSavingMode:Return";
3009 let self = this;
3010 let enabled = msg.data;
3011 let mode = enabled ? "AUTO" : "ACTIVE";
3013 // Some wifi drivers may not implement this command. Set power mode
3014 // even if suspend optimization command failed.
3015 WifiManager.setSuspendOptimizations(enabled, function(ok) {
3016 WifiManager.setPowerMode(mode, function(ok) {
3017 if (ok) {
3018 self._sendMessage(message, true, true, msg);
3019 } else {
3020 self._sendMessage(message, false, "Set power saving mode failed", msg);
3021 }
3022 });
3023 });
3024 },
3026 setHttpProxy: function(msg) {
3027 const message = "WifiManager:setHttpProxy:Return";
3028 let self = this;
3029 let network = msg.data.network;
3030 let info = msg.data.info;
3032 WifiManager.configureHttpProxy(network, info, function(ok) {
3033 if (ok) {
3034 // If configured network is current connected network
3035 // need update http proxy immediately.
3036 let setNetworkKey = getNetworkKey(network);
3037 let curNetworkKey = self.currentNetwork ? getNetworkKey(self.currentNetwork) : null;
3038 if (setNetworkKey === curNetworkKey)
3039 WifiManager.setHttpProxy(network);
3041 self._sendMessage(message, true, true, msg);
3042 } else {
3043 self._sendMessage(message, false, "Set http proxy failed", msg);
3044 }
3045 });
3046 },
3048 setStaticIpMode: function(msg) {
3049 const message = "WifiManager:setStaticMode:Return";
3050 let self = this;
3051 let network = msg.data.network;
3052 let info = msg.data.info;
3054 // To compatiable with DHCP returned info structure, do translation here
3055 info.ipaddr_str = info.ipaddr;
3056 info.proxy_str = info.proxy;
3057 info.gateway_str = info.gateway;
3058 info.dns1_str = info.dns1;
3059 info.dns2_str = info.dns2;
3061 WifiManager.setStaticIpMode(network, info, function(ok) {
3062 if (ok) {
3063 self._sendMessage(message, true, true, msg);
3064 } else {
3065 self._sendMessage(message, false, "Set static ip mode failed", msg);
3066 }
3067 });
3068 },
3070 // This is a bit ugly, but works. In particular, this depends on the fact
3071 // that RadioManager never actually tries to get the worker from us.
3072 get worker() { throw "Not implemented"; },
3074 shutdown: function() {
3075 debug("shutting down ...");
3076 this.queueRequest({command: "setWifiEnabled", value: false}, function(data) {
3077 this.setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
3078 }.bind(this));
3079 },
3081 requestProcessing: false, // Hold while dequeue and execution a request.
3082 // Released upon the request is fully executed,
3083 // i.e, mostly after callback is done.
3084 requestDone: function requestDone() {
3085 this.requestProcessing = false;
3086 this.nextRequest();
3087 },
3089 nextRequest: function nextRequest() {
3090 // No request to process
3091 if (this._stateRequests.length === 0) {
3092 return;
3093 }
3095 // Handling request, wait for it.
3096 if (this.requestProcessing) {
3097 return;
3098 }
3100 // Hold processing lock
3101 this.requestProcessing = true;
3103 // Find next valid request
3104 let request = this._stateRequests.shift();
3106 request.callback(request.data);
3107 },
3109 notifyTetheringOn: function notifyTetheringOn() {
3110 // It's really sad that we don't have an API to notify the wifi
3111 // hotspot status. Toggle settings to let gaia know that wifi hotspot
3112 // is enabled.
3113 let self = this;
3114 this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = true;
3115 this._oldWifiTetheringEnabledState = true;
3116 gSettingsService.createLock().set(
3117 SETTINGS_WIFI_TETHERING_ENABLED,
3118 true,
3119 {
3120 handle: function(aName, aResult) {
3121 self.requestDone();
3122 },
3123 handleError: function(aErrorMessage) {
3124 self.requestDone();
3125 }
3126 },
3127 "fromInternalSetting");
3128 },
3130 notifyTetheringOff: function notifyTetheringOff() {
3131 // It's really sad that we don't have an API to notify the wifi
3132 // hotspot status. Toggle settings to let gaia know that wifi hotspot
3133 // is disabled.
3134 let self = this;
3135 this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
3136 this._oldWifiTetheringEnabledState = false;
3137 gSettingsService.createLock().set(
3138 SETTINGS_WIFI_TETHERING_ENABLED,
3139 false,
3140 {
3141 handle: function(aName, aResult) {
3142 self.requestDone();
3143 },
3144 handleError: function(aErrorMessage) {
3145 self.requestDone();
3146 }
3147 },
3148 "fromInternalSetting");
3149 },
3151 handleWifiEnabled: function(enabled) {
3152 // Make sure Wifi hotspot is idle before switching to Wifi mode.
3153 if (enabled) {
3154 this.queueRequest({command: "setWifiApEnabled", value: false}, function(data) {
3155 if (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
3156 WifiManager.isWifiTetheringEnabled(WifiManager.tetheringState)) {
3157 this.disconnectedByWifi = true;
3158 this.setWifiApEnabled(false, this.notifyTetheringOff.bind(this));
3159 } else {
3160 this.requestDone();
3161 }
3162 }.bind(this));
3163 }
3165 this.queueRequest({command: "setWifiEnabled", value: enabled}, function(data) {
3166 this.setWifiEnabled(enabled, this._setWifiEnabledCallback.bind(this));
3167 }.bind(this));
3169 if (!enabled) {
3170 this.queueRequest({command: "setWifiApEnabled", value: true}, function(data) {
3171 if (this.disconnectedByWifi) {
3172 this.setWifiApEnabled(true, this.notifyTetheringOn.bind(this));
3173 } else {
3174 this.requestDone();
3175 }
3176 this.disconnectedByWifi = false;
3177 }.bind(this));
3178 }
3179 },
3181 handleWifiTetheringEnabled: function(enabled) {
3182 // Make sure Wifi is idle before switching to Wifi hotspot mode.
3183 if (enabled) {
3184 this.queueRequest({command: "setWifiEnabled", value: false}, function(data) {
3185 if (WifiManager.isWifiEnabled(WifiManager.state)) {
3186 this.disconnectedByWifiTethering = true;
3187 this.setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
3188 } else {
3189 this.requestDone();
3190 }
3191 }.bind(this));
3192 }
3194 this.queueRequest({command: "setWifiApEnabled", value: enabled}, function(data) {
3195 this.setWifiApEnabled(enabled, this.requestDone.bind(this));
3196 }.bind(this));
3198 if (!enabled) {
3199 this.queueRequest({command: "setWifiEnabled", value: true}, function(data) {
3200 if (this.disconnectedByWifiTethering) {
3201 this.setWifiEnabled(true, this._setWifiEnabledCallback.bind(this));
3202 } else {
3203 this.requestDone();
3204 }
3205 this.disconnectedByWifiTethering = false;
3206 }.bind(this));
3207 }
3208 },
3210 // nsIObserver implementation
3211 observe: function observe(subject, topic, data) {
3212 // Note that this function gets called for any and all settings changes,
3213 // so we need to carefully check if we have the one we're interested in.
3214 // The string we're interested in will be a JSON string that looks like:
3215 // {"key":"wifi.enabled","value":"true"}.
3216 if (topic !== kMozSettingsChangedObserverTopic) {
3217 return;
3218 }
3220 let setting = JSON.parse(data);
3221 // To avoid WifiWorker setting the wifi again, don't need to deal with
3222 // the "mozsettings-changed" event fired from internal setting.
3223 if (setting.message && setting.message === "fromInternalSetting") {
3224 return;
3225 }
3227 this.handle(setting.key, setting.value);
3228 },
3230 handle: function handle(aName, aResult) {
3231 switch(aName) {
3232 case SETTINGS_WIFI_ENABLED:
3233 this.handleWifiEnabled(aResult)
3234 break;
3235 case SETTINGS_WIFI_DEBUG_ENABLED:
3236 if (aResult === null)
3237 aResult = false;
3238 DEBUG = aResult;
3239 updateDebug();
3240 break;
3241 case SETTINGS_WIFI_TETHERING_ENABLED:
3242 this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
3243 // Fall through!
3244 case SETTINGS_WIFI_SSID:
3245 case SETTINGS_WIFI_SECURITY_TYPE:
3246 case SETTINGS_WIFI_SECURITY_PASSWORD:
3247 case SETTINGS_WIFI_IP:
3248 case SETTINGS_WIFI_PREFIX:
3249 case SETTINGS_WIFI_DHCPSERVER_STARTIP:
3250 case SETTINGS_WIFI_DHCPSERVER_ENDIP:
3251 case SETTINGS_WIFI_DNS1:
3252 case SETTINGS_WIFI_DNS2:
3253 case SETTINGS_USB_DHCPSERVER_STARTIP:
3254 case SETTINGS_USB_DHCPSERVER_ENDIP:
3255 if (aResult !== null) {
3256 this.tetheringSettings[aName] = aResult;
3257 }
3258 debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
3259 let index = this._wifiTetheringSettingsToRead.indexOf(aName);
3261 if (index != -1) {
3262 this._wifiTetheringSettingsToRead.splice(index, 1);
3263 }
3265 if (this._wifiTetheringSettingsToRead.length) {
3266 debug("We haven't read completely the wifi Tethering data from settings db.");
3267 break;
3268 }
3270 if (this._oldWifiTetheringEnabledState === this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
3271 debug("No changes for SETTINGS_WIFI_TETHERING_ENABLED flag. Nothing to do.");
3272 break;
3273 }
3275 if (this._oldWifiTetheringEnabledState === null &&
3276 !this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
3277 debug("Do nothing when initial settings for SETTINGS_WIFI_TETHERING_ENABLED flag is false.");
3278 break;
3279 }
3281 this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
3282 this.handleWifiTetheringEnabled(aResult)
3283 break;
3284 };
3285 },
3287 handleError: function handleError(aErrorMessage) {
3288 debug("There was an error while reading Tethering settings.");
3289 this.tetheringSettings = {};
3290 this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
3291 },
3292 };
3294 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WifiWorker]);
3296 let debug;
3297 function updateDebug() {
3298 if (DEBUG) {
3299 debug = function (s) {
3300 dump("-*- WifiWorker component: " + s + "\n");
3301 };
3302 } else {
3303 debug = function (s) {};
3304 }
3305 WifiManager.syncDebug();
3306 }
3307 updateDebug();