Bumping manifests a=b2g-bump
[gecko.git] / dom / wifi / WifiWorker.js
blobf7ad75df87d6a31c9dc0c28a7eca56f80958f0f4
1 /* -*- indent-tabs-mode: nil; js-indent-level: 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/FileUtils.jsm");
15 Cu.import("resource://gre/modules/WifiCommand.jsm");
16 Cu.import("resource://gre/modules/WifiNetUtil.jsm");
17 Cu.import("resource://gre/modules/WifiP2pManager.jsm");
18 Cu.import("resource://gre/modules/WifiP2pWorkerObserver.jsm");
20 var DEBUG = false; // set to true to show debug messages.
22 const WIFIWORKER_CONTRACTID = "@mozilla.org/wifi/worker;1";
23 const WIFIWORKER_CID        = Components.ID("{a14e8977-d259-433a-a88d-58dd44657e5b}");
25 const WIFIWORKER_WORKER     = "resource://gre/modules/wifi_worker.js";
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     security: ["OPEN", "WEP", "WPA-PSK", "WPA-EAP"],
124     eapMethod: ["PEAP", "TTLS"],
125     eapPhase2: ["MSCHAPV2"],
126     certificate: ["SERVER"]
127   };
128   if (eapSimSupported) {
129     capabilities.eapMethod.unshift("SIM");
130   }
132   let wifiListener = {
133     onWaitEvent: function(event, iface) {
134       if (manager.ifname === iface && handleEvent(event)) {
135         waitForEvent(iface);
136       } else if (p2pSupported) {
137         if (WifiP2pManager.INTERFACE_NAME === iface) {
138           // If the connection is closed, wifi.c::wifi_wait_for_event()
139           // will still return 'CTRL-EVENT-TERMINATING  - connection closed'
140           // rather than blocking. So when we see this special event string,
141           // just return immediately.
142           const TERMINATED_EVENT = 'CTRL-EVENT-TERMINATING  - connection closed';
143           if (-1 !== event.indexOf(TERMINATED_EVENT)) {
144             return;
145           }
146           p2pManager.handleEvent(event);
147           waitForEvent(iface);
148         }
149       }
150     },
152     onCommand: function(event, iface) {
153       onmessageresult(event, iface);
154     }
155   }
157   manager.ifname = ifname;
158   manager.connectToSupplicant = false;
159   // Emulator build runs to here.
160   // The debug() should only be used after WifiManager.
161   if (!ifname) {
162     manager.ifname = DEFAULT_WLAN_INTERFACE;
163   }
164   manager.schedScanRecovery = schedScanRecovery;
165   manager.driverDelay = driverDelay ? parseInt(driverDelay, 10) : DRIVER_READY_WAIT;
167   // Regular Wifi stuff.
168   var netUtil = WifiNetUtil(controlMessage);
169   var wifiCommand = WifiCommand(controlMessage, manager.ifname, sdkVersion);
171   // Wifi P2P stuff
172   var p2pManager;
173   if (p2pSupported) {
174     let p2pCommand = WifiCommand(controlMessage, WifiP2pManager.INTERFACE_NAME, sdkVersion);
175     p2pManager = WifiP2pManager(p2pCommand, netUtil);
176   }
178   let wifiService = Cc["@mozilla.org/wifi/service;1"];
179   if (wifiService) {
180     wifiService = wifiService.getService(Ci.nsIWifiProxyService);
181     let interfaces = [manager.ifname];
182     if (p2pSupported) {
183       interfaces.push(WifiP2pManager.INTERFACE_NAME);
184     }
185     wifiService.start(wifiListener, interfaces, interfaces.length);
186   } else {
187     debug("No wifi service component available!");
188   }
190   // Callbacks to invoke when a reply arrives from the wifi service.
191   var controlCallbacks = Object.create(null);
192   var idgen = 0;
194   function controlMessage(obj, callback) {
195     var id = idgen++;
196     obj.id = id;
197     if (callback) {
198       controlCallbacks[id] = callback;
199     }
200     wifiService.sendCommand(obj, obj.iface);
201   }
203   let onmessageresult = function(data, iface) {
204     var id = data.id;
205     var callback = controlCallbacks[id];
206     if (callback) {
207       callback(data);
208       delete controlCallbacks[id];
209     }
210   }
212   // Polling the status worker
213   var recvErrors = 0;
215   function waitForEvent(iface) {
216     wifiService.waitForEvent(iface);
217   }
219   // Commands to the control worker.
221   var driverLoaded = false;
223   function loadDriver(callback) {
224     if (driverLoaded) {
225       callback(0);
226       return;
227     }
229     wifiCommand.loadDriver(function (status) {
230       driverLoaded = (status >= 0);
231       callback(status)
232     });
233   }
235   function unloadDriver(type, callback) {
236     if (!unloadDriverEnabled) {
237       // Unloading drivers is generally unnecessary and
238       // can trigger bugs in some drivers.
239       // On properly written drivers, bringing the interface
240       // down powers down the interface.
241       if (type === WIFI_FIRMWARE_STATION) {
242         notify("supplicantlost", { success: true });
243       }
244       callback(0);
245       return;
246     }
248     wifiCommand.unloadDriver(function(status) {
249       driverLoaded = (status < 0);
250       if (type === WIFI_FIRMWARE_STATION) {
251         notify("supplicantlost", { success: true });
252       }
253       callback(status);
254     });
255   }
257   // A note about background scanning:
258   // Normally, background scanning shouldn't be necessary as wpa_supplicant
259   // has the capability to automatically schedule its own scans at appropriate
260   // intervals. However, with some drivers, this appears to get stuck after
261   // three scans, so we enable the driver's background scanning to work around
262   // that when we're not connected to any network. This ensures that we'll
263   // automatically reconnect to networks if one falls out of range.
264   var reEnableBackgroundScan = false;
266   // NB: This is part of the internal API.
267   manager.backgroundScanEnabled = false;
268   function setBackgroundScan(enable, callback) {
269     var doEnable = (enable === "ON");
270     if (doEnable === manager.backgroundScanEnabled) {
271       callback(false, true);
272       return;
273     }
275     manager.backgroundScanEnabled = doEnable;
276     wifiCommand.setBackgroundScan(manager.backgroundScanEnabled, callback);
277   }
279   var scanModeActive = false;
281   function scan(forceActive, callback) {
282     if (forceActive && !scanModeActive) {
283       // Note: we ignore errors from doSetScanMode.
284       wifiCommand.doSetScanMode(true, function(ignore) {
285         setBackgroundScan("OFF", function(turned, ignore) {
286           reEnableBackgroundScan = turned;
287           manager.handlePreWifiScan();
288           wifiCommand.scan(function(ok) {
289             wifiCommand.doSetScanMode(false, function(ignore) {
290               // The result of scanCommand is the result of the actual SCAN
291               // request.
292               callback(ok);
293             });
294           });
295         });
296       });
297       return;
298     }
299     manager.handlePreWifiScan();
300     wifiCommand.scan(callback);
301   }
303   var debugEnabled = false;
305   function syncDebug() {
306     if (debugEnabled !== DEBUG) {
307       let wanted = DEBUG;
308       wifiCommand.setLogLevel(wanted ? "DEBUG" : "INFO", function(ok) {
309         if (ok)
310           debugEnabled = wanted;
311       });
312       if (p2pSupported && p2pManager) {
313         p2pManager.setDebug(DEBUG);
314       }
315     }
316   }
318   function getDebugEnabled(callback) {
319     wifiCommand.getLogLevel(function(level) {
320       if (level === null) {
321         debug("Unable to get wpa_supplicant's log level");
322         callback(false);
323         return;
324       }
326       var lines = level.split("\n");
327       for (let i = 0; i < lines.length; ++i) {
328         let match = /Current level: (.*)/.exec(lines[i]);
329         if (match) {
330           debugEnabled = match[1].toLowerCase() === "debug";
331           callback(true);
332           return;
333         }
334       }
336       // If we're here, we didn't get the current level.
337       callback(false);
338     });
339   }
341   function setScanMode(setActive, callback) {
342     scanModeActive = setActive;
343     wifiCommand.doSetScanMode(setActive, callback);
344   }
346   var httpProxyConfig = Object.create(null);
348   /**
349    * Given a network, configure http proxy when using wifi.
350    * @param network A network object to update http proxy
351    * @param info Info should have following field:
352    *        - httpProxyHost ip address of http proxy.
353    *        - httpProxyPort port of http proxy, set 0 to use default port 8080.
354    * @param callback callback function.
355    */
356   function configureHttpProxy(network, info, callback) {
357     if (!network)
358       return;
360     let networkKey = getNetworkKey(network);
362     if (!info || info.httpProxyHost === "") {
363       delete httpProxyConfig[networkKey];
364     } else {
365       httpProxyConfig[networkKey] = network;
366       httpProxyConfig[networkKey].httpProxyHost = info.httpProxyHost;
367       httpProxyConfig[networkKey].httpProxyPort = info.httpProxyPort;
368     }
370     callback(true);
371   }
373   function getHttpProxyNetwork(network) {
374     if (!network)
375       return null;
377     let networkKey = getNetworkKey(network);
378     return ((networkKey in httpProxyConfig) ? httpProxyConfig : null);
379   }
381   function setHttpProxy(network) {
382     if (!network)
383       return;
385     gNetworkService.setNetworkProxy(network);
386   }
388   var staticIpConfig = Object.create(null);
389   function setStaticIpMode(network, info, callback) {
390     let setNetworkKey = getNetworkKey(network);
391     let curNetworkKey = null;
392     let currentNetwork = Object.create(null);
393     currentNetwork.netId = manager.connectionInfo.id;
395     manager.getNetworkConfiguration(currentNetwork, function (){
396       curNetworkKey = getNetworkKey(currentNetwork);
398       // Add additional information to static ip configuration
399       // It is used to compatiable with information dhcp callback.
400       info.ipaddr = netHelpers.stringToIP(info.ipaddr_str);
401       info.gateway = netHelpers.stringToIP(info.gateway_str);
402       info.mask_str = netHelpers.ipToString(netHelpers.makeMask(info.maskLength));
404       // Optional
405       info.dns1 = netHelpers.stringToIP(info.dns1_str);
406       info.dns2 = netHelpers.stringToIP(info.dns2_str);
407       info.proxy = netHelpers.stringToIP(info.proxy_str);
409       staticIpConfig[setNetworkKey] = info;
411       // If the ssid of current connection is the same as configured ssid
412       // It means we need update current connection to use static IP address.
413       if (setNetworkKey == curNetworkKey) {
414         // Use configureInterface directly doesn't work, the network iterface
415         // and routing table is changed but still cannot connect to network
416         // so the workaround here is disable interface the enable again to
417         // trigger network reconnect with static ip.
418         netUtil.disableInterface(manager.ifname, function (ok) {
419           netUtil.enableInterface(manager.ifname, function (ok) {
420           });
421         });
422       }
423     });
424   }
426   var dhcpInfo = null;
428   function runStaticIp(ifname, key) {
429     debug("Run static ip");
431     // Read static ip information from settings.
432     let staticIpInfo;
434     if (!(key in staticIpConfig))
435       return;
437     staticIpInfo = staticIpConfig[key];
439     // Stop dhcpd when use static IP
440     if (dhcpInfo != null) {
441       netUtil.stopDhcp(manager.ifname, function() {});
442     }
444     // Set ip, mask length, gateway, dns to network interface
445     netUtil.configureInterface( { ifname: ifname,
446                                   ipaddr: staticIpInfo.ipaddr,
447                                   mask: staticIpInfo.maskLength,
448                                   gateway: staticIpInfo.gateway,
449                                   dns1: staticIpInfo.dns1,
450                                   dns2: staticIpInfo.dns2 }, function (data) {
451       netUtil.runIpConfig(ifname, staticIpInfo, function(data) {
452         dhcpInfo = data.info;
453         notify("networkconnected", data);
454       });
455     });
456   }
458   var suppressEvents = false;
459   function notify(eventName, eventObject) {
460     if (suppressEvents)
461       return;
462     var handler = manager["on" + eventName];
463     if (handler) {
464       if (!eventObject)
465         eventObject = ({});
466       handler.call(eventObject);
467     }
468   }
470   function notifyStateChange(fields) {
471     // If we're already in the COMPLETED state, we might receive events from
472     // the supplicant that tell us that we're re-authenticating or reminding
473     // us that we're associated to a network. In those cases, we don't need to
474     // do anything, so just ignore them.
475     if (manager.state === "COMPLETED" &&
476         fields.state !== "DISCONNECTED" &&
477         fields.state !== "INTERFACE_DISABLED" &&
478         fields.state !== "INACTIVE" &&
479         fields.state !== "SCANNING") {
480       return false;
481     }
483     // Stop background scanning if we're trying to connect to a network.
484     if (manager.backgroundScanEnabled &&
485         (fields.state === "ASSOCIATING" ||
486          fields.state === "ASSOCIATED" ||
487          fields.state === "FOUR_WAY_HANDSHAKE" ||
488          fields.state === "GROUP_HANDSHAKE" ||
489          fields.state === "COMPLETED")) {
490       setBackgroundScan("OFF", function() {});
491     }
493     fields.prevState = manager.state;
494     // Detect wpa_supplicant's loop iterations.
495     manager.supplicantLoopDetection(fields.prevState, fields.state);
496     notify("statechange", fields);
498     // Don't update state when and after disabling.
499     if (manager.state === "DISABLING" ||
500         manager.state === "UNINITIALIZED") {
501       return false;
502     }
504     manager.state = fields.state;
505     return true;
506   }
508   function parseStatus(status) {
509     if (status === null) {
510       debug("Unable to get wpa supplicant's status");
511       return;
512     }
514     var ssid;
515     var bssid;
516     var state;
517     var ip_address;
518     var id;
520     var lines = status.split("\n");
521     for (let i = 0; i < lines.length; ++i) {
522       let [key, value] = lines[i].split("=");
523       switch (key) {
524         case "wpa_state":
525           state = value;
526           break;
527         case "ssid":
528           ssid = value;
529           break;
530         case "bssid":
531           bssid = value;
532           break;
533         case "ip_address":
534           ip_address = value;
535           break;
536         case "id":
537           id = value;
538           break;
539       }
540     }
542     if (bssid && ssid) {
543       manager.connectionInfo.bssid = bssid;
544       manager.connectionInfo.ssid = ssid;
545       manager.connectionInfo.id = id;
546     }
548     if (ip_address)
549       dhcpInfo = { ip_address: ip_address };
551     notifyStateChange({ state: state, fromStatus: true });
553     // If we parse the status and the supplicant has already entered the
554     // COMPLETED state, then we need to set up DHCP right away.
555     if (state === "COMPLETED")
556       onconnected();
557   }
559   // try to connect to the supplicant
560   var connectTries = 0;
561   var retryTimer = null;
562   function connectCallback(ok) {
563     if (ok === 0) {
564       // Tell the event worker to start waiting for events.
565       retryTimer = null;
566       connectTries = 0;
567       recvErrors = 0;
568       manager.connectToSupplicant = true;
569       didConnectSupplicant(function(){});
570       return;
571     }
572     if (connectTries++ < 5) {
573       // Try again in 1 seconds.
574       if (!retryTimer)
575         retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
577       retryTimer.initWithCallback(function(timer) {
578         wifiCommand.connectToSupplicant(connectCallback);
579       }, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
580       return;
581     }
583     retryTimer = null;
584     connectTries = 0;
585     notify("supplicantlost", { success: false });
586   }
588   manager.connectionDropped = function(callback) {
589     // Reset network interface when connection drop
590     netUtil.configureInterface( { ifname: manager.ifname,
591                                   ipaddr: 0,
592                                   mask: 0,
593                                   gateway: 0,
594                                   dns1: 0,
595                                   dns2: 0 }, function (data) {
596     });
598     // If we got disconnected, kill the DHCP client in preparation for
599     // reconnection.
600     netUtil.resetConnections(manager.ifname, function() {
601       netUtil.stopDhcp(manager.ifname, function() {
602         callback();
603       });
604     });
605   }
607   manager.start = function() {
608     debug("detected SDK version " + sdkVersion);
609     wifiCommand.connectToSupplicant(connectCallback);
610   }
612   function onconnected() {
613     // For now we do our own DHCP. In the future, this should be handed
614     // off to the Network Manager.
615     let currentNetwork = Object.create(null);
616     currentNetwork.netId = manager.connectionInfo.id;
618     manager.getNetworkConfiguration(currentNetwork, function (){
619       let key = getNetworkKey(currentNetwork);
620       if (staticIpConfig  &&
621           (key in staticIpConfig) &&
622           staticIpConfig[key].enabled) {
623           debug("Run static ip");
624           runStaticIp(manager.ifname, key);
625           return;
626       }
627       netUtil.runDhcp(manager.ifname, function(data) {
628         dhcpInfo = data.info;
629         if (!dhcpInfo) {
630           if (++manager.dhcpFailuresCount >= MAX_RETRIES_ON_DHCP_FAILURE) {
631             manager.dhcpFailuresCount = 0;
632             notify("disconnected", {connectionInfo: manager.connectionInfo});
633             return;
634           }
635           // NB: We have to call disconnect first. Otherwise, we only reauth with
636           // the existing AP and don't retrigger DHCP.
637           manager.disconnect(function() {
638             manager.reassociate(function(){});
639           });
640           return;
641         }
643         manager.dhcpFailuresCount = 0;
644         notify("networkconnected", data);
645       });
646     });
647   }
649   var supplicantStatesMap = (sdkVersion >= 15) ?
650     ["DISCONNECTED", "INTERFACE_DISABLED", "INACTIVE", "SCANNING",
651      "AUTHENTICATING", "ASSOCIATING", "ASSOCIATED", "FOUR_WAY_HANDSHAKE",
652      "GROUP_HANDSHAKE", "COMPLETED"]
653     :
654     ["DISCONNECTED", "INACTIVE", "SCANNING", "ASSOCIATING",
655      "ASSOCIATED", "FOUR_WAY_HANDSHAKE", "GROUP_HANDSHAKE",
656      "COMPLETED", "DORMANT", "UNINITIALIZED"];
658   var driverEventMap = { STOPPED: "driverstopped", STARTED: "driverstarted", HANGED: "driverhung" };
660   manager.getNetworkId = function (ssid, callback) {
661     manager.getConfiguredNetworks(function(networks) {
662       if (!networks) {
663         debug("Unable to get configured networks");
664         return callback(null);
665       }
666       for (let net in networks) {
667         let network = networks[net];
668         // Trying to get netId from
669         // 1. network matching SSID if SSID is provided.
670         // 2. current network if no SSID is provided, it's not guaranteed that
671         //    current network matches requested SSID.
672         if ((!ssid && network.status === "CURRENT") ||
673             (ssid && network.ssid && ssid === dequote(network.ssid))) {
674           return callback(net);
675         }
676       }
677       callback(null);
678     });
679   }
681   function handleWpaEapEvents(event) {
682     if (event.indexOf("CTRL-EVENT-EAP-FAILURE") !== -1) {
683       if (event.indexOf("EAP authentication failed") !== -1) {
684         notify("passwordmaybeincorrect");
685         if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
686           manager.authenticationFailuresCount = 0;
687           notify("disconnected", {connectionInfo: manager.connectionInfo});
688         }
689       }
690       return true;
691     }
692     if (event.indexOf("CTRL-EVENT-EAP-TLS-CERT-ERROR") !== -1) {
693       // Cert Error
694       notify("passwordmaybeincorrect");
695       if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
696         manager.authenticationFailuresCount = 0;
697         notify("disconnected", {connectionInfo: manager.connectionInfo});
698       }
699       return true;
700     }
701     if (event.indexOf("CTRL-EVENT-EAP-STARTED") !== -1) {
702       notifyStateChange({ state: "AUTHENTICATING" });
703       return true;
704     }
705     return true;
706   }
708   // Handle events sent to us by the event worker.
709   function handleEvent(event) {
710     debug("Event coming in: " + event);
711     if (event.indexOf("CTRL-EVENT-") !== 0 && event.indexOf("WPS") !== 0) {
712       // Handle connection fail exception on WEP-128, while password length
713       // is not 5 nor 13 bytes.
714       if (event.indexOf("Association request to the driver failed") !== -1) {
715         notify("passwordmaybeincorrect");
716         if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
717           manager.authenticationFailuresCount = 0;
718           notify("disconnected", {connectionInfo: manager.connectionInfo});
719         }
720         return true;
721       }
723       if (event.indexOf("WPA:") == 0 &&
724           event.indexOf("pre-shared key may be incorrect") != -1) {
725         notify("passwordmaybeincorrect");
726       }
728       // This is ugly, but we need to grab the SSID here. BSSID is not guaranteed
729       // to be provided, so don't grab BSSID here.
730       var match = /Trying to associate with.*SSID[ =]'(.*)'/.exec(event);
731       if (match) {
732         debug("Matched: " + match[1] + "\n");
733         manager.connectionInfo.ssid = match[1];
734       }
735       return true;
736     }
738     var space = event.indexOf(" ");
739     var eventData = event.substr(0, space + 1);
740     if (eventData.indexOf("CTRL-EVENT-STATE-CHANGE") === 0) {
741       // Parse the event data.
742       var fields = {};
743       var tokens = event.substr(space + 1).split(" ");
744       for (var n = 0; n < tokens.length; ++n) {
745         var kv = tokens[n].split("=");
746         if (kv.length === 2)
747           fields[kv[0]] = kv[1];
748       }
749       if (!("state" in fields))
750         return true;
751       fields.state = supplicantStatesMap[fields.state];
753       // The BSSID field is only valid in the ASSOCIATING and ASSOCIATED
754       // states, except when we "reauth", except this seems to depend on the
755       // driver, so simply check to make sure that we don't have a null BSSID.
756       if (fields.BSSID !== "00:00:00:00:00:00")
757         manager.connectionInfo.bssid = fields.BSSID;
759       if (notifyStateChange(fields) && fields.state === "COMPLETED") {
760         onconnected();
761       }
762       return true;
763     }
764     if (eventData.indexOf("CTRL-EVENT-DRIVER-STATE") === 0) {
765       var handlerName = driverEventMap[eventData];
766       if (handlerName)
767         notify(handlerName);
768       return true;
769     }
770     if (eventData.indexOf("CTRL-EVENT-TERMINATING") === 0) {
771       // As long the monitor socket is not closed and we haven't seen too many
772       // recv errors yet, we will keep going for a bit longer.
773       if (event.indexOf("connection closed") === -1 &&
774           event.indexOf("recv error") !== -1 && ++recvErrors < 10)
775         return true;
777       notifyStateChange({ state: "DISCONNECTED", BSSID: null, id: -1 });
779       // If the supplicant is terminated as commanded, the supplicant lost
780       // notification will be sent after driver unloaded. In such case, the
781       // manager state will be "DISABLING" or "UNINITIALIZED".
782       // So if supplicant terminated with incorrect manager state, implying
783       // unexpected condition, we should notify supplicant lost here.
784       if (manager.state !== "DISABLING" && manager.state !== "UNINITIALIZED") {
785         notify("supplicantlost", { success: true });
786       }
788       if (manager.stopSupplicantCallback) {
789         cancelWaitForTerminateEventTimer();
790         // It's possible that the terminating event triggered by timer comes
791         // earlier than the event from wpa_supplicant. Since
792         // stopSupplicantCallback contains async. callbacks, swap it to local
793         // to prevent calling the callback twice.
794         let stopSupplicantCallback = manager.stopSupplicantCallback.bind(manager);
795         manager.stopSupplicantCallback = null;
796         stopSupplicantCallback();
797         stopSupplicantCallback = null;
798       }
799       return false;
800     }
801     if (eventData.indexOf("CTRL-EVENT-DISCONNECTED") === 0) {
802       var token = event.split(" ")[1];
803       var bssid = token.split("=")[1];
804       if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
805         manager.authenticationFailuresCount = 0;
806         notify("disconnected", {connectionInfo: manager.connectionInfo});
807       }
808       manager.connectionInfo.bssid = null;
809       manager.connectionInfo.ssid = null;
810       manager.connectionInfo.id = -1;
811       return true;
812     }
813     if (eventData.indexOf("CTRL-EVENT-CONNECTED") === 0) {
814       // Format: CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]
815       var bssid = event.split(" ")[4];
817       var keyword = "id=";
818       var id = event.substr(event.indexOf(keyword) + keyword.length).split(" ")[0];
819       // Read current BSSID here, it will always being provided.
820       manager.connectionInfo.id = id;
821       manager.connectionInfo.bssid = bssid;
822       return true;
823     }
824     if (eventData.indexOf("CTRL-EVENT-SCAN-RESULTS") === 0) {
825       debug("Notifying of scan results available");
826       if (reEnableBackgroundScan) {
827         reEnableBackgroundScan = false;
828         setBackgroundScan("ON", function() {});
829       }
830       manager.handlePostWifiScan();
831       notify("scanresultsavailable");
832       return true;
833     }
834     if (eventData.indexOf("CTRL-EVENT-EAP") === 0) {
835       return handleWpaEapEvents(event);
836     }
837     if (eventData.indexOf("WPS-TIMEOUT") === 0) {
838       notifyStateChange({ state: "WPS_TIMEOUT", BSSID: null, id: -1 });
839       return true;
840     }
841     if (eventData.indexOf("WPS-FAIL") === 0) {
842       notifyStateChange({ state: "WPS_FAIL", BSSID: null, id: -1 });
843       return true;
844     }
845     if (eventData.indexOf("WPS-OVERLAP-DETECTED") === 0) {
846       notifyStateChange({ state: "WPS_OVERLAP_DETECTED", BSSID: null, id: -1 });
847       return true;
848     }
849     // Unknown event.
850     return true;
851   }
853   function didConnectSupplicant(callback) {
854     waitForEvent(manager.ifname);
856     // Load up the supplicant state.
857     getDebugEnabled(function(ok) {
858       syncDebug();
859     });
860     wifiCommand.status(function(status) {
861       parseStatus(status);
862       notify("supplicantconnection");
863       callback();
864     });
866     if (p2pSupported) {
867       manager.enableP2p(function(success) {});
868     }
869   }
871   function prepareForStartup(callback) {
872     let status = libcutils.property_get(DHCP_PROP + "_" + manager.ifname);
873     if (status !== "running") {
874       tryStopSupplicant();
875       return;
876     }
877     manager.connectionDropped(function() {
878       tryStopSupplicant();
879     });
881     // Ignore any errors and kill any currently-running supplicants. On some
882     // phones, stopSupplicant won't work for a supplicant that we didn't
883     // start, so we hand-roll it here.
884     function tryStopSupplicant () {
885       let status = libcutils.property_get(SUPP_PROP);
886       if (status !== "running") {
887         callback();
888         return;
889       }
890       suppressEvents = true;
891       wifiCommand.killSupplicant(function() {
892         netUtil.disableInterface(manager.ifname, function (ok) {
893           suppressEvents = false;
894           callback();
895         });
896       });
897     }
898   }
900   // Initial state.
901   manager.state = "UNINITIALIZED";
902   manager.tetheringState = "UNINITIALIZED";
903   manager.supplicantStarted = false;
904   manager.connectionInfo = { ssid: null, bssid: null, id: -1 };
905   manager.authenticationFailuresCount = 0;
906   manager.loopDetectionCount = 0;
907   manager.dhcpFailuresCount = 0;
908   manager.stopSupplicantCallback = null;
910   manager.__defineGetter__("enabled", function() {
911     switch (manager.state) {
912       case "UNINITIALIZED":
913       case "INITIALIZING":
914       case "DISABLING":
915         return false;
916       default:
917         return true;
918     }
919   });
921   var waitForTerminateEventTimer = null;
922   function cancelWaitForTerminateEventTimer() {
923     if (waitForTerminateEventTimer) {
924       waitForTerminateEventTimer.cancel();
925       waitForTerminateEventTimer = null;
926     }
927   };
928   function createWaitForTerminateEventTimer(onTimeout) {
929     if (waitForTerminateEventTimer) {
930       return;
931     }
932     waitForTerminateEventTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
933     waitForTerminateEventTimer.initWithCallback(onTimeout,
934                                                 4000,
935                                                 Ci.nsITimer.TYPE_ONE_SHOT);
936   };
938   var waitForDriverReadyTimer = null;
939   function cancelWaitForDriverReadyTimer() {
940     if (waitForDriverReadyTimer) {
941       waitForDriverReadyTimer.cancel();
942       waitForDriverReadyTimer = null;
943     }
944   };
945   function createWaitForDriverReadyTimer(onTimeout) {
946     waitForDriverReadyTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
947     waitForDriverReadyTimer.initWithCallback(onTimeout,
948                                              manager.driverDelay,
949                                              Ci.nsITimer.TYPE_ONE_SHOT);
950   };
952   // Public interface of the wifi service.
953   manager.setWifiEnabled = function(enabled, callback) {
954     if (enabled === manager.isWifiEnabled(manager.state)) {
955       callback("no change");
956       return;
957     }
959     if (enabled) {
960       manager.state = "INITIALIZING";
961       // Register as network interface.
962       WifiNetworkInterface.name = manager.ifname;
963       if (!WifiNetworkInterface.registered) {
964         gNetworkManager.registerNetworkInterface(WifiNetworkInterface);
965         WifiNetworkInterface.registered = true;
966       }
967       WifiNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
968       WifiNetworkInterface.ips = [];
969       WifiNetworkInterface.prefixLengths = [];
970       WifiNetworkInterface.gateways = [];
971       WifiNetworkInterface.dnses = [];
972       gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
974       prepareForStartup(function() {
975         loadDriver(function (status) {
976           if (status < 0) {
977             callback(status);
978             manager.state = "UNINITIALIZED";
979             return;
980           }
981           // This command is mandatory for Nexus 4. But some devices like
982           // Galaxy S2 don't support it. Continue to start wpa_supplicant
983           // even if we fail to set wifi operation mode to station.
984           gNetworkService.setWifiOperationMode(manager.ifname,
985                                                WIFI_FIRMWARE_STATION,
986                                                function (status) {
988             function startSupplicantInternal() {
989               wifiCommand.startSupplicant(function (status) {
990                 if (status < 0) {
991                   unloadDriver(WIFI_FIRMWARE_STATION, function() {
992                     callback(status);
993                   });
994                   manager.state = "UNINITIALIZED";
995                   return;
996                 }
998                 manager.supplicantStarted = true;
999                 netUtil.enableInterface(manager.ifname, function (ok) {
1000                   callback(ok ? 0 : -1);
1001                 });
1002               });
1003             }
1005             function doStartSupplicant() {
1006               cancelWaitForDriverReadyTimer();
1008               if (!manager.connectToSupplicant) {
1009                 startSupplicantInternal();
1010                 return;
1011               }
1012               wifiCommand.closeSupplicantConnection(function () {
1013                 manager.connectToSupplicant = false;
1014                 // closeSupplicantConnection() will trigger onsupplicantlost
1015                 // and set manager.state to "UNINITIALIZED", we have to
1016                 // restore it here.
1017                 manager.state = "INITIALIZING";
1018                 startSupplicantInternal();
1019               });
1020             }
1021             // Driver startup on certain platforms takes longer than it takes for us
1022             // to return from loadDriver, so wait 2 seconds before starting
1023             // the supplicant to give it a chance to start.
1024             if (manager.driverDelay > 0) {
1025               createWaitForDriverReadyTimer(doStartSupplicant);
1026             } else {
1027               doStartSupplicant();
1028             }
1029           });
1030         });
1031       });
1032     } else {
1033       manager.state = "DISABLING";
1034       // Note these following calls ignore errors. If we fail to kill the
1035       // supplicant gracefully, then we need to continue telling it to die
1036       // until it does.
1037       let doDisableWifi = function() {
1038         manager.stopSupplicantCallback = (function () {
1039           wifiCommand.stopSupplicant(function (status) {
1040             wifiCommand.closeSupplicantConnection(function() {
1041               manager.connectToSupplicant = false;
1042               manager.state = "UNINITIALIZED";
1043               netUtil.disableInterface(manager.ifname, function (ok) {
1044                 unloadDriver(WIFI_FIRMWARE_STATION, callback);
1045               });
1046             });
1047           });
1048         }).bind(this);
1050         let terminateEventCallback = (function() {
1051           handleEvent("CTRL-EVENT-TERMINATING");
1052         }).bind(this);
1053         createWaitForTerminateEventTimer(terminateEventCallback);
1055         // We are going to terminate the connection between wpa_supplicant.
1056         // Stop the polling timer immediately to prevent connection info update
1057         // command blocking in control thread until socket timeout.
1058         notify("stopconnectioninfotimer");
1060         wifiCommand.terminateSupplicant(function (ok) {
1061           manager.connectionDropped(function () {
1062           });
1063         });
1064       }
1066       if (p2pSupported) {
1067         p2pManager.setEnabled(false, { onDisabled: doDisableWifi });
1068       } else {
1069         doDisableWifi();
1070       }
1071     }
1072   }
1074   var wifiHotspotStatusTimer = null;
1075   function cancelWifiHotspotStatusTimer() {
1076     if (wifiHotspotStatusTimer) {
1077       wifiHotspotStatusTimer.cancel();
1078       wifiHotspotStatusTimer = null;
1079     }
1080   }
1082   function createWifiHotspotStatusTimer(onTimeout) {
1083     wifiHotspotStatusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1084     wifiHotspotStatusTimer.init(onTimeout, 5000, Ci.nsITimer.TYPE_REPEATING_SLACK);
1085   }
1087   // Get wifi interface and load wifi driver when enable Ap mode.
1088   manager.setWifiApEnabled = function(enabled, configuration, callback) {
1089     if (enabled === manager.isWifiTetheringEnabled(manager.tetheringState)) {
1090       callback("no change");
1091       return;
1092     }
1094     if (enabled) {
1095       manager.tetheringState = "INITIALIZING";
1096       loadDriver(function (status) {
1097         if (status < 0) {
1098           callback();
1099           manager.tetheringState = "UNINITIALIZED";
1100           if (wifiHotspotStatusTimer) {
1101             cancelWifiHotspotStatusTimer();
1102             wifiCommand.closeHostapdConnection(function(result) {
1103             });
1104           }
1105           return;
1106         }
1108         function getWifiHotspotStatus() {
1109           wifiCommand.hostapdGetStations(function(result) {
1110             notify("stationinfoupdate", {station: result});
1111           });
1112         }
1114         function doStartWifiTethering() {
1115           cancelWaitForDriverReadyTimer();
1116           WifiNetworkInterface.name = libcutils.property_get("wifi.tethering.interface", manager.ifname);
1117           gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface,
1118                                            configuration, function(result) {
1119             if (result) {
1120               manager.tetheringState = "UNINITIALIZED";
1121             } else {
1122               manager.tetheringState = "COMPLETED";
1123               wifiCommand.connectToHostapd(function(result) {
1124                 if (result) {
1125                   return;
1126                 }
1127                 // Create a timer to track the connection status.
1128                 createWifiHotspotStatusTimer(getWifiHotspotStatus);
1129               });
1130             }
1131             // Pop out current request.
1132             callback();
1133             // Should we fire a dom event if we fail to set wifi tethering  ?
1134             debug("Enable Wifi tethering result: " + (result ? result : "successfully"));
1135           });
1136         }
1138         // Driver startup on certain platforms takes longer than it takes
1139         // for us to return from loadDriver, so wait 2 seconds before
1140         // turning on Wifi tethering.
1141         createWaitForDriverReadyTimer(doStartWifiTethering);
1142       });
1143     } else {
1144       cancelWifiHotspotStatusTimer();
1145       gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface,
1146                                        configuration, function(result) {
1147         // Should we fire a dom event if we fail to set wifi tethering  ?
1148         debug("Disable Wifi tethering result: " + (result ? result : "successfully"));
1149         // Unload wifi driver even if we fail to control wifi tethering.
1150         unloadDriver(WIFI_FIRMWARE_AP, function(status) {
1151           if (status < 0) {
1152             debug("Fail to unload wifi driver");
1153           }
1154           manager.tetheringState = "UNINITIALIZED";
1155           callback();
1156         });
1157       });
1158     }
1159   }
1161   manager.disconnect = wifiCommand.disconnect;
1162   manager.reconnect = wifiCommand.reconnect;
1163   manager.reassociate = wifiCommand.reassociate;
1165   var networkConfigurationFields = [
1166     {name: "ssid",          type: "string"},
1167     {name: "bssid",         type: "string"},
1168     {name: "psk",           type: "string"},
1169     {name: "wep_key0",      type: "string"},
1170     {name: "wep_key1",      type: "string"},
1171     {name: "wep_key2",      type: "string"},
1172     {name: "wep_key3",      type: "string"},
1173     {name: "wep_tx_keyidx", type: "integer"},
1174     {name: "priority",      type: "integer"},
1175     {name: "key_mgmt",      type: "string"},
1176     {name: "scan_ssid",     type: "string"},
1177     {name: "disabled",      type: "integer"},
1178     {name: "identity",      type: "string"},
1179     {name: "password",      type: "string"},
1180     {name: "auth_alg",      type: "string"},
1181     {name: "phase1",        type: "string"},
1182     {name: "phase2",        type: "string"},
1183     {name: "eap",           type: "string"},
1184     {name: "pin",           type: "string"},
1185     {name: "pcsc",          type: "string"},
1186     {name: "ca_cert",       type: "string"},
1187     {name: "subject_match", type: "string"}
1188   ];
1190   manager.getNetworkConfiguration = function(config, callback) {
1191     var netId = config.netId;
1192     var done = 0;
1193     for (var n = 0; n < networkConfigurationFields.length; ++n) {
1194       let fieldName = networkConfigurationFields[n].name;
1195       let fieldType = networkConfigurationFields[n].type;
1196       wifiCommand.getNetworkVariable(netId, fieldName, function(value) {
1197         if (value !== null) {
1198           if (fieldType === "integer") {
1199             config[fieldName] = parseInt(value, 10);
1200           } else if ( fieldName == "ssid" && value[0] != '"' ) {
1201             // SET_NETWORK will set a quoted ssid to wpa_supplicant.
1202             // But if ssid contains non-ascii char, it will be converted into utf-8.
1203             // For example: "Testçš„wifi" --> 54657374e79a8477696669
1204             // When GET_NETWORK receive a un-quoted utf-8 ssid, it must be decoded and quoted.
1205             config[fieldName] = quote(decodeURIComponent(value.replace(/[0-9a-f]{2}/g, '%$&')));
1206           } else {
1207             // value is string type by default.
1208             config[fieldName] = value;
1209           }
1210         }
1211         if (++done == networkConfigurationFields.length)
1212           callback(config);
1213       });
1214     }
1215   }
1216   manager.setNetworkConfiguration = function(config, callback) {
1217     var netId = config.netId;
1218     var done = 0;
1219     var errors = 0;
1221     function hasValidProperty(name) {
1222       return ((name in config) &&
1223                config[name] != null &&
1224                (["password", "wep_key0", "psk"].indexOf(name) === -1 ||
1225                 config[name] !== '*'));
1226     }
1228     for (var n = 0; n < networkConfigurationFields.length; ++n) {
1229       let fieldName = networkConfigurationFields[n].name;
1230       if (!hasValidProperty(fieldName)) {
1231         ++done;
1232       } else {
1233         wifiCommand.setNetworkVariable(netId, fieldName, config[fieldName], function(ok) {
1234           if (!ok)
1235             ++errors;
1236           if (++done == networkConfigurationFields.length)
1237             callback(errors == 0);
1238         });
1239       }
1240     }
1241     // If config didn't contain any of the fields we want, don't lose the error callback.
1242     if (done == networkConfigurationFields.length)
1243       callback(false);
1244   }
1245   manager.getConfiguredNetworks = function(callback) {
1246     wifiCommand.listNetworks(function (reply) {
1247       var networks = Object.create(null);
1248       var lines = reply ? reply.split("\n") : 0;
1249       if (lines.length <= 1) {
1250         // We need to make sure we call the callback even if there are no
1251         // configured networks.
1252         callback(networks);
1253         return;
1254       }
1256       var done = 0;
1257       var errors = 0;
1258       for (var n = 1; n < lines.length; ++n) {
1259         var result = lines[n].split("\t");
1260         var netId = parseInt(result[0], 10);
1261         var config = networks[netId] = { netId: netId };
1262         switch (result[3]) {
1263         case "[CURRENT]":
1264           config.status = "CURRENT";
1265           break;
1266         case "[DISABLED]":
1267           config.status = "DISABLED";
1268           break;
1269         default:
1270           config.status = "ENABLED";
1271           break;
1272         }
1273         manager.getNetworkConfiguration(config, function (ok) {
1274             if (!ok)
1275               ++errors;
1276             if (++done == lines.length - 1) {
1277               if (errors) {
1278                 // If an error occured, delete the new netId.
1279                 wifiCommand.removeNetwork(netId, function() {
1280                   callback(null);
1281                 });
1282               } else {
1283                 callback(networks);
1284               }
1285             }
1286         });
1287       }
1288     });
1289   }
1290   manager.addNetwork = function(config, callback) {
1291     wifiCommand.addNetwork(function (netId) {
1292       config.netId = netId;
1293       manager.setNetworkConfiguration(config, function (ok) {
1294         if (!ok) {
1295           wifiCommand.removeNetwork(netId, function() { callback(false); });
1296           return;
1297         }
1299         callback(ok);
1300       });
1301     });
1302   }
1303   manager.updateNetwork = function(config, callback) {
1304     manager.setNetworkConfiguration(config, callback);
1305   }
1306   manager.removeNetwork = function(netId, callback) {
1307     wifiCommand.removeNetwork(netId, callback);
1308   }
1310   manager.saveConfig = function(callback) {
1311     wifiCommand.saveConfig(callback);
1312   }
1313   manager.enableNetwork = function(netId, disableOthers, callback) {
1314     if (p2pSupported) {
1315       // We have to stop wifi direct scan before associating to an AP.
1316       // Otherwise we will get a "REJECT" wpa supplicant event.
1317       p2pManager.setScanEnabled(false, function(success) {});
1318     }
1319     wifiCommand.enableNetwork(netId, disableOthers, callback);
1320   }
1321   manager.disableNetwork = function(netId, callback) {
1322     wifiCommand.disableNetwork(netId, callback);
1323   }
1324   manager.getMacAddress = wifiCommand.getMacAddress;
1325   manager.getScanResults = wifiCommand.scanResults;
1326   manager.setScanMode = function(mode, callback) {
1327     setScanMode(mode === "active", callback); // Use our own version.
1328   }
1329   manager.setBackgroundScan = setBackgroundScan; // Use our own version.
1330   manager.scan = scan; // Use our own version.
1331   manager.wpsPbc = wifiCommand.wpsPbc;
1332   manager.wpsPin = wifiCommand.wpsPin;
1333   manager.wpsCancel = wifiCommand.wpsCancel;
1334   manager.setPowerMode = (sdkVersion >= 16)
1335                          ? wifiCommand.setPowerModeJB
1336                          : wifiCommand.setPowerModeICS;
1337   manager.getHttpProxyNetwork = getHttpProxyNetwork;
1338   manager.setHttpProxy = setHttpProxy;
1339   manager.configureHttpProxy = configureHttpProxy;
1340   manager.setSuspendOptimizations = (sdkVersion >= 16)
1341                                    ? wifiCommand.setSuspendOptimizationsJB
1342                                    : wifiCommand.setSuspendOptimizationsICS;
1343   manager.setStaticIpMode = setStaticIpMode;
1344   manager.getRssiApprox = wifiCommand.getRssiApprox;
1345   manager.getLinkSpeed = wifiCommand.getLinkSpeed;
1346   manager.getDhcpInfo = function() { return dhcpInfo; }
1347   manager.getConnectionInfo = (sdkVersion >= 15)
1348                               ? wifiCommand.getConnectionInfoICS
1349                               : wifiCommand.getConnectionInfoGB;
1351   manager.isHandShakeState = function(state) {
1352     switch (state) {
1353       case "AUTHENTICATING":
1354       case "ASSOCIATING":
1355       case "ASSOCIATED":
1356       case "FOUR_WAY_HANDSHAKE":
1357       case "GROUP_HANDSHAKE":
1358         return true;
1359       case "DORMANT":
1360       case "COMPLETED":
1361       case "DISCONNECTED":
1362       case "INTERFACE_DISABLED":
1363       case "INACTIVE":
1364       case "SCANNING":
1365       case "UNINITIALIZED":
1366       case "INVALID":
1367       case "CONNECTED":
1368       default:
1369         return false;
1370     }
1371   }
1373   manager.isWifiEnabled = function(state) {
1374     switch (state) {
1375       case "UNINITIALIZED":
1376       case "DISABLING":
1377         return false;
1378       default:
1379         return true;
1380     }
1381   }
1383   manager.isWifiTetheringEnabled = function(state) {
1384     switch (state) {
1385       case "UNINITIALIZED":
1386         return false;
1387       default:
1388         return true;
1389     }
1390   }
1392   manager.syncDebug = syncDebug;
1393   manager.stateOrdinal = function(state) {
1394     return supplicantStatesMap.indexOf(state);
1395   }
1396   manager.supplicantLoopDetection = function(prevState, state) {
1397     var isPrevStateInHandShake = manager.isHandShakeState(prevState);
1398     var isStateInHandShake = manager.isHandShakeState(state);
1400     if (isPrevStateInHandShake) {
1401       if (isStateInHandShake) {
1402         // Increase the count only if we are in the loop.
1403         if (manager.stateOrdinal(state) > manager.stateOrdinal(prevState)) {
1404           manager.loopDetectionCount++;
1405         }
1406         if (manager.loopDetectionCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
1407           notify("disconnected", {connectionInfo: manager.connectionInfo});
1408           manager.loopDetectionCount = 0;
1409         }
1410       }
1411     } else {
1412       // From others state to HandShake state. Reset the count.
1413       if (isStateInHandShake) {
1414         manager.loopDetectionCount = 0;
1415       }
1416     }
1417   }
1419   manager.handlePreWifiScan = function() {
1420     if (p2pSupported) {
1421       // Before doing regular wifi scan, we have to disable wifi direct
1422       // scan first. Otherwise we will never get the scan result.
1423       p2pManager.blockScan();
1424     }
1425   };
1427   manager.handlePostWifiScan = function() {
1428     if (p2pSupported) {
1429       // After regular wifi scanning, we should restore the restricted
1430       // wifi direct scan.
1431       p2pManager.unblockScan();
1432     }
1433   };
1435   //
1436   // Public APIs for P2P.
1437   //
1439   manager.p2pSupported = function() {
1440     return p2pSupported;
1441   };
1443   manager.getP2pManager = function() {
1444     return p2pManager;
1445   };
1447   manager.enableP2p = function(callback) {
1448     p2pManager.setEnabled(true, {
1449       onSupplicantConnected: function() {
1450         waitForEvent(WifiP2pManager.INTERFACE_NAME);
1451       },
1453       onEnabled: function(success) {
1454         callback(success);
1455       }
1456     });
1457   };
1459   manager.getCapabilities = function() {
1460     return capabilities;
1461   }
1463   // Cert Services
1464   let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"];
1465   if (wifiCertService) {
1466     wifiCertService = wifiCertService.getService(Ci.nsIWifiCertService);
1467     wifiCertService.start(wifiListener);
1468   } else {
1469     debug("No wifi CA service component available");
1470   }
1472   manager.importCert = function(caInfo, callback) {
1473     var id = idgen++;
1474     if (callback) {
1475       controlCallbacks[id] = callback;
1476     }
1478     wifiCertService.importCert(id, caInfo.certBlob, caInfo.certPassword,
1479                                caInfo.certNickname);
1480   }
1482   manager.deleteCert = function(caInfo, callback) {
1483     var id = idgen++;
1484     if (callback) {
1485       controlCallbacks[id] = callback;
1486     }
1488     wifiCertService.deleteCert(id, caInfo.certNickname);
1489   }
1491   return manager;
1492 })();
1494 // Get unique key for a network, now the key is created by escape(SSID)+Security.
1495 // So networks of same SSID but different security mode can be identified.
1496 function getNetworkKey(network)
1498   var ssid = "",
1499       encryption = "OPEN";
1501   if ("security" in network) {
1502     // manager network object, represents an AP
1503     // object structure
1504     // {
1505     //   .ssid           : SSID of AP
1506     //   .security[]     : "WPA-PSK" for WPA-PSK
1507     //                     "WPA-EAP" for WPA-EAP
1508     //                     "WEP" for WEP
1509     //                     "" for OPEN
1510     //   other keys
1511     // }
1513     var security = network.security;
1514     ssid = network.ssid;
1516     for (let j = 0; j < security.length; j++) {
1517       if (security[j] === "WPA-PSK") {
1518         encryption = "WPA-PSK";
1519         break;
1520       } else if (security[j] === "WPA-EAP") {
1521         encryption = "WPA-EAP";
1522         break;
1523       } else if (security[j] === "WEP") {
1524         encryption = "WEP";
1525         break;
1526       }
1527     }
1528   } else if ("key_mgmt" in network) {
1529     // configure network object, represents a network
1530     // object structure
1531     // {
1532     //   .ssid           : SSID of network, quoted
1533     //   .key_mgmt       : Encryption type
1534     //                     "WPA-PSK" for WPA-PSK
1535     //                     "WPA-EAP" for WPA-EAP
1536     //                     "NONE" for WEP/OPEN
1537     //   .auth_alg       : Encryption algorithm(WEP mode only)
1538     //                     "OPEN_SHARED" for WEP
1539     //   other keys
1540     // }
1541     var key_mgmt = network.key_mgmt,
1542         auth_alg = network.auth_alg;
1543     ssid = dequote(network.ssid);
1545     if (key_mgmt == "WPA-PSK") {
1546       encryption = "WPA-PSK";
1547     } else if (key_mgmt.indexOf("WPA-EAP") != -1) {
1548       encryption = "WPA-EAP";
1549     } else if (key_mgmt == "NONE" && auth_alg === "OPEN SHARED") {
1550       encryption = "WEP";
1551     }
1552   }
1554   // ssid here must be dequoted, and it's safer to esacpe it.
1555   // encryption won't be empty and always be assigned one of the followings :
1556   // "OPEN"/"WEP"/"WPA-PSK"/"WPA-EAP".
1557   // So for a invalid network object, the returned key will be "OPEN".
1558   return escape(ssid) + encryption;
1561 function getKeyManagement(flags) {
1562   var types = [];
1563   if (!flags)
1564     return types;
1566   if (/\[WPA2?-PSK/.test(flags))
1567     types.push("WPA-PSK");
1568   if (/\[WPA2?-EAP/.test(flags))
1569     types.push("WPA-EAP");
1570   if (/\[WEP/.test(flags))
1571     types.push("WEP");
1572   return types;
1575 function getCapabilities(flags) {
1576   var types = [];
1577   if (!flags)
1578     return types;
1580   if (/\[WPS/.test(flags))
1581     types.push("WPS");
1582   return types;
1585 // These constants shamelessly ripped from WifiManager.java
1586 // strength is the value returned by scan_results. It is nominally in dB. We
1587 // transform it into a percentage for clients looking to simply show a
1588 // relative indication of the strength of a network.
1589 const MIN_RSSI = -100;
1590 const MAX_RSSI = -55;
1592 function calculateSignal(strength) {
1593   // Some wifi drivers represent their signal strengths as 8-bit integers, so
1594   // in order to avoid negative numbers, they add 256 to the actual values.
1595   // While we don't *know* that this is the case here, we make an educated
1596   // guess.
1597   if (strength > 0)
1598     strength -= 256;
1600   if (strength <= MIN_RSSI)
1601     return 0;
1602   if (strength >= MAX_RSSI)
1603     return 100;
1604   return Math.floor(((strength - MIN_RSSI) / (MAX_RSSI - MIN_RSSI)) * 100);
1607 function Network(ssid, security, password, capabilities) {
1608   this.ssid = ssid;
1609   this.security = security;
1611   if (typeof password !== "undefined")
1612     this.password = password;
1613   if (capabilities !== undefined)
1614     this.capabilities = capabilities;
1615   // TODO connected here as well?
1617   this.__exposedProps__ = Network.api;
1620 Network.api = {
1621   ssid: "r",
1622   security: "r",
1623   capabilities: "r",
1624   known: "r",
1626   password: "rw",
1627   keyManagement: "rw",
1628   psk: "rw",
1629   identity: "rw",
1630   wep: "rw",
1631   hidden: "rw",
1632   eap: "rw",
1633   pin: "rw",
1634   phase1: "rw",
1635   phase2: "rw",
1636   serverCertificate: "rw"
1639 // Note: We never use ScanResult.prototype, so the fact that it's unrelated to
1640 // Network.prototype is OK.
1641 function ScanResult(ssid, bssid, flags, signal) {
1642   Network.call(this, ssid, getKeyManagement(flags), undefined,
1643                getCapabilities(flags));
1644   this.bssid = bssid;
1645   this.signalStrength = signal;
1646   this.relSignalStrength = calculateSignal(Number(signal));
1648   this.__exposedProps__ = ScanResult.api;
1651 // XXX This should probably live in the DOM-facing side, but it's hard to do
1652 // there, so we stick this here.
1653 ScanResult.api = {
1654   bssid: "r",
1655   signalStrength: "r",
1656   relSignalStrength: "r",
1657   connected: "r"
1660 for (let i in Network.api) {
1661   ScanResult.api[i] = Network.api[i];
1664 function quote(s) {
1665   return '"' + s + '"';
1668 function dequote(s) {
1669   if (s[0] != '"' || s[s.length - 1] != '"')
1670     throw "Invalid argument, not a quoted string: " + s;
1671   return s.substr(1, s.length - 2);
1674 function isWepHexKey(s) {
1675   if (s.length != 10 && s.length != 26 && s.length != 58)
1676     return false;
1677   return !/[^a-fA-F0-9]/.test(s);
1681 let WifiNetworkInterface = {
1683   QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
1685   registered: false,
1687   // nsINetworkInterface
1689   NETWORK_STATE_UNKNOWN:       Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN,
1690   NETWORK_STATE_CONNECTING:    Ci.nsINetworkInterface.CONNECTING,
1691   NETWORK_STATE_CONNECTED:     Ci.nsINetworkInterface.CONNECTED,
1692   NETWORK_STATE_DISCONNECTING: Ci.nsINetworkInterface.DISCONNECTING,
1693   NETWORK_STATE_DISCONNECTED:  Ci.nsINetworkInterface.DISCONNECTED,
1695   state: Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN,
1697   NETWORK_TYPE_WIFI:        Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
1698   NETWORK_TYPE_MOBILE:      Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
1699   NETWORK_TYPE_MOBILE_MMS:  Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS,
1700   NETWORK_TYPE_MOBILE_SUPL: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL,
1702   type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
1704   name: null,
1706   ips: [],
1708   prefixLengths: [],
1710   dnses: [],
1712   gateways: [],
1714   httpProxyHost: null,
1716   httpProxyPort: null,
1718   getAddresses: function (ips, prefixLengths) {
1719     ips.value = this.ips.slice();
1720     prefixLengths.value = this.prefixLengths.slice();
1722     return this.ips.length;
1723   },
1725   getGateways: function (count) {
1726     if (count) {
1727       count.value = this.gateways.length;
1728     }
1729     return this.gateways.slice();
1730   },
1732   getDnses: function (count) {
1733     if (count) {
1734       count.value = this.dnses.length;
1735     }
1736     return this.dnses.slice();
1737   }
1740 function WifiScanResult() {}
1742 // TODO Make the difference between a DOM-based network object and our
1743 // networks objects much clearer.
1744 let netToDOM;
1745 let netFromDOM;
1747 function WifiWorker() {
1748   var self = this;
1750   this._mm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
1751                .getService(Ci.nsIMessageListenerManager);
1752   const messages = ["WifiManager:getNetworks", "WifiManager:getKnownNetworks",
1753                     "WifiManager:associate", "WifiManager:forget",
1754                     "WifiManager:wps", "WifiManager:getState",
1755                     "WifiManager:setPowerSavingMode",
1756                     "WifiManager:setHttpProxy",
1757                     "WifiManager:setStaticIpMode",
1758                     "WifiManager:importCert",
1759                     "WifiManager:getImportedCerts",
1760                     "WifiManager:deleteCert",
1761                     "WifiManager:setWifiEnabled",
1762                     "WifiManager:setWifiTethering",
1763                     "child-process-shutdown"];
1765   messages.forEach((function(msgName) {
1766     this._mm.addMessageListener(msgName, this);
1767   }).bind(this));
1769   Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
1770   Services.obs.addObserver(this, "xpcom-shutdown", false);
1772   this.wantScanResults = [];
1774   this._needToEnableNetworks = false;
1775   this._highestPriority = -1;
1777   // Networks is a map from SSID -> a scan result.
1778   this.networks = Object.create(null);
1780   // ConfiguredNetworks is a map from SSID -> our view of a network. It only
1781   // lists networks known to the wpa_supplicant. The SSID field (and other
1782   // fields) are quoted for ease of use with WifiManager commands.
1783   // Note that we don't have to worry about escaping embedded quotes since in
1784   // all cases, the supplicant will take the last quotation that we pass it as
1785   // the end of the string.
1786   this.configuredNetworks = Object.create(null);
1787   this._addingNetworks = Object.create(null);
1789   this.currentNetwork = null;
1790   this.ipAddress = "";
1791   this.macAddress = null;
1793   this._lastConnectionInfo = null;
1794   this._connectionInfoTimer = null;
1795   this._reconnectOnDisconnect = false;
1797   // Create p2pObserver and assign to p2pManager.
1798   if (WifiManager.p2pSupported()) {
1799     this._p2pObserver = WifiP2pWorkerObserver(WifiManager.getP2pManager());
1800     WifiManager.getP2pManager().setObserver(this._p2pObserver);
1802     // Add DOM message observerd by p2pObserver to the message listener as well.
1803     this._p2pObserver.getObservedDOMMessages().forEach((function(msgName) {
1804       this._mm.addMessageListener(msgName, this);
1805     }).bind(this));
1806   }
1808   // Users of instances of nsITimer should keep a reference to the timer until
1809   // it is no longer needed in order to assure the timer is fired.
1810   this._callbackTimer = null;
1812   // XXX On some phones (Otoro and Unagi) the wifi driver doesn't play nicely
1813   // with the automatic scans that wpa_supplicant does (it appears that the
1814   // driver forgets that it's returned scan results and then refuses to try to
1815   // rescan. In order to detect this case we start a timer when we enter the
1816   // SCANNING state and reset it whenever we either get scan results or leave
1817   // the SCANNING state. If the timer fires, we assume that we are stuck and
1818   // forceably try to unstick the supplican, also turning on background
1819   // scanning to avoid having to constantly poke the supplicant.
1821   // How long we wait is controlled by the SCAN_STUCK_WAIT constant.
1822   const SCAN_STUCK_WAIT = 12000;
1823   this._scanStuckTimer = null;
1824   this._turnOnBackgroundScan = false;
1826   function startScanStuckTimer() {
1827     if (WifiManager.schedScanRecovery) {
1828       self._scanStuckTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1829       self._scanStuckTimer.initWithCallback(scanIsStuck, SCAN_STUCK_WAIT,
1830                                             Ci.nsITimer.TYPE_ONE_SHOT);
1831     }
1832   }
1834   function scanIsStuck() {
1835     // Uh-oh, we've waited too long for scan results. Disconnect (which
1836     // guarantees that we leave the SCANNING state and tells wpa_supplicant to
1837     // wait for our next command) ensure that background scanning is on and
1838     // then try again.
1839     debug("Determined that scanning is stuck, turning on background scanning!");
1840     WifiManager.handlePostWifiScan();
1841     WifiManager.disconnect(function(ok) {});
1842     self._turnOnBackgroundScan = true;
1843   }
1845   // A list of requests to turn wifi on or off.
1846   this._stateRequests = [];
1848   // Given a connection status network, takes a network from
1849   // self.configuredNetworks and prepares it for the DOM.
1850   netToDOM = function(net) {
1851     var ssid = dequote(net.ssid);
1852     var security = (net.key_mgmt === "NONE" && net.wep_key0) ? ["WEP"] :
1853                    (net.key_mgmt && net.key_mgmt !== "NONE") ? [net.key_mgmt.split(" ")[0]] :
1854                    [];
1855     var password;
1856     if (("psk" in net && net.psk) ||
1857         ("password" in net && net.password) ||
1858         ("wep_key0" in net && net.wep_key0)) {
1859       password = "*";
1860     }
1862     var pub = new Network(ssid, security, password);
1863     if (net.identity)
1864       pub.identity = dequote(net.identity);
1865     if (net.netId)
1866       pub.known = true;
1867     if (net.scan_ssid === 1)
1868       pub.hidden = true;
1869     if ("ca_cert" in net && net.ca_cert &&
1870         net.ca_cert.indexOf("keystore://WIFI_SERVERCERT_" === 0)) {
1871       pub.serverCertificate = net.ca_cert.substr(27);
1872     }
1873     if(net.subject_match) {
1874       pub.subjectMatch = net.subject_match;
1875     }
1876     return pub;
1877   };
1879   netFromDOM = function(net, configured) {
1880     // Takes a network from the DOM and makes it suitable for insertion into
1881     // self.configuredNetworks (that is calling addNetwork will do the right
1882     // thing).
1883     // NB: Modifies net in place: safe since we don't share objects between
1884     // the dom and the chrome code.
1886     // Things that are useful for the UI but not to us.
1887     delete net.bssid;
1888     delete net.signalStrength;
1889     delete net.relSignalStrength;
1890     delete net.security;
1891     delete net.capabilities;
1893     if (!configured)
1894       configured = {};
1896     net.ssid = quote(net.ssid);
1898     let wep = false;
1899     if ("keyManagement" in net) {
1900       if (net.keyManagement === "WEP") {
1901         wep = true;
1902         net.keyManagement = "NONE";
1903       } else if (net.keyManagement === "WPA-EAP") {
1904         net.keyManagement += " IEEE8021X";
1905       }
1907       configured.key_mgmt = net.key_mgmt = net.keyManagement; // WPA2-PSK, WPA-PSK, etc.
1908       delete net.keyManagement;
1909     } else {
1910       configured.key_mgmt = net.key_mgmt = "NONE";
1911     }
1913     if (net.hidden) {
1914       configured.scan_ssid = net.scan_ssid = 1;
1915       delete net.hidden;
1916     }
1918     function checkAssign(name, checkStar) {
1919       if (name in net) {
1920         let value = net[name];
1921         if (!value || (checkStar && value === '*')) {
1922           if (name in configured)
1923             net[name] = configured[name];
1924           else
1925             delete net[name];
1926         } else {
1927           configured[name] = net[name] = quote(value);
1928         }
1929       }
1930     }
1932     checkAssign("psk", true);
1933     checkAssign("identity", false);
1934     checkAssign("password", true);
1935     if (wep && net.wep && net.wep != '*') {
1936       configured.wep_key0 = net.wep_key0 = isWepHexKey(net.wep) ? net.wep : quote(net.wep);
1937       configured.auth_alg = net.auth_alg = "OPEN SHARED";
1938     }
1940     function hasValidProperty(name) {
1941       return ((name in net) && net[name] != null);
1942     }
1944     if (hasValidProperty("eap")) {
1945       if (hasValidProperty("pin")) {
1946         net.pin = quote(net.pin);
1947       }
1949       if (hasValidProperty("phase1"))
1950         net.phase1 = quote(net.phase1);
1952       if (hasValidProperty("phase2")) {
1953         if (net.phase2 === "TLS") {
1954           net.phase2 = quote("autheap=" + net.phase2);
1955         } else { // PAP, MSCHAPV2, etc.
1956           net.phase2 = quote("auth=" + net.phase2);
1957         }
1958       }
1960       if (hasValidProperty("serverCertificate"))
1961         net.ca_cert = quote("keystore://WIFI_SERVERCERT_" + net.serverCertificate);
1963       if (hasValidProperty("subjectMatch"))
1964         net.subject_match = quote(net.subjectMatch);
1965     }
1967     return net;
1968   };
1970   WifiManager.onsupplicantconnection = function() {
1971     debug("Connected to supplicant");
1972     // Give it a state other than UNINITIALIZED, INITIALIZING or DISABLING
1973     // defined in getter function of WifiManager.enabled. It implies that
1974     // we are ready to accept dom request from applications.
1975     WifiManager.state = "DISCONNECTED";
1976     self._reloadConfiguredNetworks(function(ok) {
1977       // Prime this.networks.
1978       if (!ok)
1979         return;
1981       // The select network command we used in associate() disables others networks.
1982       // Enable them here to make sure wpa_supplicant helps to connect to known
1983       // network automatically.
1984       self._enableAllNetworks();
1985       WifiManager.saveConfig(function() {})
1986     });
1988     // Notify everybody, even if they didn't ask us to come up.
1989     WifiManager.getMacAddress(function (mac) {
1990       self.macAddress = mac;
1991       debug("Got mac: " + mac);
1992       self._fireEvent("wifiUp", { macAddress: mac });
1993       self.requestDone();
1994     });
1996     if (WifiManager.state === "SCANNING")
1997       startScanStuckTimer();
1998   };
2000   WifiManager.onsupplicantlost = function() {
2001     WifiManager.supplicantStarted = false;
2002     WifiManager.state = "UNINITIALIZED";
2003     debug("Supplicant died!");
2005     // Notify everybody, even if they didn't ask us to come up.
2006     self._fireEvent("wifiDown", {});
2007     self.requestDone();
2008   };
2010   WifiManager.onpasswordmaybeincorrect = function() {
2011     WifiManager.authenticationFailuresCount++;
2012   };
2014   WifiManager.ondisconnected = function() {
2015     // We may fail to establish the connection, re-enable the
2016     // rest of our networks.
2017     if (self._needToEnableNetworks) {
2018       self._enableAllNetworks();
2019       self._needToEnableNetworks = false;
2020     }
2022     let connectionInfo = this.connectionInfo;
2023     WifiManager.getNetworkId(connectionInfo.ssid, function(netId) {
2024       // Trying to get netId from current network.
2025       if (!netId &&
2026           self.currentNetwork && self.currentNetwork.ssid &&
2027           dequote(self.currentNetwork.ssid) == connectionInfo.ssid &&
2028           typeof self.currentNetwork.netId !== "undefined") {
2029         netId = self.currentNetwork.netId;
2030       }
2031       if (netId) {
2032         WifiManager.disableNetwork(netId, function() {});
2033       }
2034     });
2035     self._fireEvent("onconnectingfailed", {network: self.currentNetwork});
2036   }
2038   WifiManager.onstatechange = function() {
2039     debug("State change: " + this.prevState + " -> " + this.state);
2041     if (self._connectionInfoTimer &&
2042         this.state !== "CONNECTED" &&
2043         this.state !== "COMPLETED") {
2044       self._stopConnectionInfoTimer();
2045     }
2047     if (this.state !== "SCANNING" &&
2048         self._scanStuckTimer) {
2049       self._scanStuckTimer.cancel();
2050       self._scanStuckTimer = null;
2051     }
2053     switch (this.state) {
2054       case "DORMANT":
2055         // The dormant state is a bad state to be in since we won't
2056         // automatically connect. Try to knock us out of it. We only
2057         // hit this state when we've failed to run DHCP, so trying
2058         // again isn't the worst thing we can do. Eventually, we'll
2059         // need to detect if we're looping in this state and bail out.
2060         WifiManager.reconnect(function(){});
2061         break;
2062       case "ASSOCIATING":
2063         // id has not yet been filled in, so we can only report the ssid and
2064         // bssid.
2065         self.currentNetwork =
2066           { bssid: WifiManager.connectionInfo.bssid,
2067             ssid: quote(WifiManager.connectionInfo.ssid) };
2068         self._fireEvent("onconnecting", { network: netToDOM(self.currentNetwork) });
2069         break;
2070       case "ASSOCIATED":
2071         if (!self.currentNetwork) {
2072           self.currentNetwork =
2073             { bssid: WifiManager.connectionInfo.bssid,
2074               ssid: quote(WifiManager.connectionInfo.ssid) };
2075         }
2077         self.currentNetwork.netId = this.id;
2078         WifiManager.getNetworkConfiguration(self.currentNetwork, function (){});
2079         break;
2080       case "COMPLETED":
2081         // Now that we've successfully completed the connection, re-enable the
2082         // rest of our networks.
2083         // XXX Need to do this eventually if the user entered an incorrect
2084         // password. For now, we require user interaction to break the loop and
2085         // select a better network!
2086         if (self._needToEnableNetworks) {
2087           self._enableAllNetworks();
2088           self._needToEnableNetworks = false;
2089         }
2091         // We get the ASSOCIATED event when we've associated but not connected, so
2092         // wait until the handshake is complete.
2093         if (this.fromStatus || !self.currentNetwork) {
2094           // In this case, we connected to an already-connected wpa_supplicant,
2095           // because of that we need to gather information about the current
2096           // network here.
2097           self.currentNetwork = { ssid: quote(WifiManager.connectionInfo.ssid),
2098                                   netId: WifiManager.connectionInfo.id };
2099           WifiManager.getNetworkConfiguration(self.currentNetwork, function(){});
2100         }
2102         // Update http proxy when connected to network.
2103         let netConnect = WifiManager.getHttpProxyNetwork(self.currentNetwork);
2104         if (netConnect)
2105           WifiManager.setHttpProxy(netConnect);
2107         // The full authentication process is completed, reset the count.
2108         WifiManager.authenticationFailuresCount = 0;
2109         WifiManager.loopDetectionCount = 0;
2110         self._startConnectionInfoTimer();
2111         self._fireEvent("onassociate", { network: netToDOM(self.currentNetwork) });
2112         break;
2113       case "CONNECTED":
2114         // BSSID is read after connected, update it.
2115         self.currentNetwork.bssid = WifiManager.connectionInfo.bssid;
2116         break;
2117       case "DISCONNECTED":
2118         // wpa_supplicant may give us a "DISCONNECTED" event even if
2119         // we are already in "DISCONNECTED" state.
2120         if ((WifiNetworkInterface.state ===
2121              Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED) &&
2122              (this.prevState === "INITIALIZING" ||
2123               this.prevState === "DISCONNECTED" ||
2124               this.prevState === "INTERFACE_DISABLED" ||
2125               this.prevState === "INACTIVE" ||
2126               this.prevState === "UNINITIALIZED")) {
2127           return;
2128         }
2130         self._fireEvent("ondisconnect", {});
2132         // When disconnected, clear the http proxy setting if it exists.
2133         // Temporarily set http proxy to empty and restore user setting after setHttpProxy.
2134         let netDisconnect = WifiManager.getHttpProxyNetwork(self.currentNetwork);
2135         if (netDisconnect) {
2136           let prehttpProxyHostSetting = netDisconnect.httpProxyHost;
2137           let prehttpProxyPortSetting = netDisconnect.httpProxyPort;
2139           netDisconnect.httpProxyHost = "";
2140           netDisconnect.httpProxyPort = 0;
2142           WifiManager.setHttpProxy(netDisconnect);
2144           netDisconnect.httpProxyHost = prehttpProxyHostSetting;
2145           netDisconnect.httpProxyPort = prehttpProxyPortSetting;
2146         }
2148         self.currentNetwork = null;
2149         self.ipAddress = "";
2151         if (self._turnOnBackgroundScan) {
2152           self._turnOnBackgroundScan = false;
2153           WifiManager.setBackgroundScan("ON", function(did_something, ok) {
2154             WifiManager.reassociate(function() {});
2155           });
2156         }
2158         WifiManager.connectionDropped(function() {
2159           // We've disconnected from a network because of a call to forgetNetwork.
2160           // Reconnect to the next available network (if any).
2161           if (self._reconnectOnDisconnect) {
2162             self._reconnectOnDisconnect = false;
2163             WifiManager.reconnect(function(){});
2164           }
2165         });
2167         WifiNetworkInterface.state =
2168           Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
2169         WifiNetworkInterface.ips = [];
2170         WifiNetworkInterface.prefixLengths = [];
2171         WifiNetworkInterface.gateways = [];
2172         WifiNetworkInterface.dnses = [];
2173         gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
2175         break;
2176       case "WPS_TIMEOUT":
2177         self._fireEvent("onwpstimeout", {});
2178         break;
2179       case "WPS_FAIL":
2180         self._fireEvent("onwpsfail", {});
2181         break;
2182       case "WPS_OVERLAP_DETECTED":
2183         self._fireEvent("onwpsoverlap", {});
2184         break;
2185       case "AUTHENTICATING":
2186         self._fireEvent("onauthenticating", {network: netToDOM(self.currentNetwork)});
2187         break;
2188       case "SCANNING":
2189         // If we're already scanning in the background, we don't need to worry
2190         // about getting stuck while scanning.
2191         if (!WifiManager.backgroundScanEnabled && WifiManager.enabled)
2192           startScanStuckTimer();
2193         break;
2194     }
2195   };
2197   WifiManager.onnetworkconnected = function() {
2198     if (!this.info || !this.info.ipaddr_str) {
2199       debug("Network information is invalid.");
2200       return;
2201     }
2203     let maskLength =
2204       netHelpers.getMaskLength(netHelpers.stringToIP(this.info.mask_str));
2205     if (!maskLength) {
2206       maskLength = 32; // max prefix for IPv4.
2207     }
2208     WifiNetworkInterface.state =
2209       Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
2210     WifiNetworkInterface.ips = [this.info.ipaddr_str];
2211     WifiNetworkInterface.prefixLengths = [maskLength];
2212     WifiNetworkInterface.gateways = [this.info.gateway_str];
2213     if (typeof this.info.dns1_str == "string" &&
2214         this.info.dns1_str.length) {
2215       WifiNetworkInterface.dnses.push(this.info.dns1_str);
2216     }
2217     if (typeof this.info.dns2_str == "string" &&
2218         this.info.dns2_str.length) {
2219       WifiNetworkInterface.dnses.push(this.info.dns2_str);
2220     }
2221     gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
2223     self.ipAddress = this.info.ipaddr_str;
2225     // We start the connection information timer when we associate, but
2226     // don't have our IP address until here. Make sure that we fire a new
2227     // connectionInformation event with the IP address the next time the
2228     // timer fires.
2229     self._lastConnectionInfo = null;
2230     self._fireEvent("onconnect", { network: netToDOM(self.currentNetwork) });
2231   };
2233   WifiManager.onscanresultsavailable = function() {
2234     if (self._scanStuckTimer) {
2235       // We got scan results! We must not be stuck for now, try again.
2236       self._scanStuckTimer.cancel();
2237       self._scanStuckTimer.initWithCallback(scanIsStuck, SCAN_STUCK_WAIT,
2238                                             Ci.nsITimer.TYPE_ONE_SHOT);
2239     }
2241     if (self.wantScanResults.length === 0) {
2242       debug("Scan results available, but we don't need them");
2243       return;
2244     }
2246     debug("Scan results are available! Asking for them.");
2247     WifiManager.getScanResults(function(r) {
2248       // Failure.
2249       if (!r) {
2250         self.wantScanResults.forEach(function(callback) { callback(null) });
2251         self.wantScanResults = [];
2252         return;
2253       }
2255       // Now that we have scan results, there's no more need to continue
2256       // scanning. Ignore any errors from this command.
2257       WifiManager.setScanMode("inactive", function() {});
2258       let lines = r.split("\n");
2259       // NB: Skip the header line.
2260       self.networksArray = [];
2261       for (let i = 1; i < lines.length; ++i) {
2262         // bssid / frequency / signal level / flags / ssid
2263         var match = /([\S]+)\s+([\S]+)\s+([\S]+)\s+(\[[\S]+\])?\s(.*)/.exec(lines[i]);
2265         if (match && match[5]) {
2266           let ssid = match[5],
2267               bssid = match[1],
2268               signalLevel = match[3],
2269               flags = match[4];
2271           // Skip ad-hoc networks which aren't supported (bug 811635).
2272           if (flags && flags.indexOf("[IBSS]") >= 0)
2273             continue;
2275           // If this is the first time that we've seen this SSID in the scan
2276           // results, add it to the list along with any other information.
2277           // Also, we use the highest signal strength that we see.
2278           let network = new ScanResult(ssid, bssid, flags, signalLevel);
2280           let networkKey = getNetworkKey(network);
2281           let eapIndex = -1;
2282           if (networkKey in self.configuredNetworks) {
2283             let known = self.configuredNetworks[networkKey];
2284             network.known = true;
2286             if ("identity" in known && known.identity)
2287               network.identity = dequote(known.identity);
2289             // Note: we don't hand out passwords here! The * marks that there
2290             // is a password that we're hiding.
2291             if (("psk" in known && known.psk) ||
2292                 ("password" in known && known.password) ||
2293                 ("wep_key0" in known && known.wep_key0)) {
2294               network.password = "*";
2295             }
2296           }
2298           self.networksArray.push(network);
2299           if (network.bssid === WifiManager.connectionInfo.bssid)
2300             network.connected = true;
2302           let signal = calculateSignal(Number(match[3]));
2303           if (signal > network.relSignalStrength)
2304             network.relSignalStrength = signal;
2305         } else if (!match) {
2306           debug("Match didn't find anything for: " + lines[i]);
2307         }
2308       }
2310       self.wantScanResults.forEach(function(callback) { callback(self.networksArray) });
2311       self.wantScanResults = [];
2312     });
2313   };
2315   WifiManager.onstationinfoupdate = function() {
2316     self._fireEvent("stationinfoupdate", { station: this.station });
2317   };
2319   WifiManager.onstopconnectioninfotimer = function() {
2320     self._stopConnectionInfoTimer();
2321   };
2323   // Read the 'wifi.enabled' setting in order to start with a known
2324   // value at boot time. The handle() will be called after reading.
2325   //
2326   // nsISettingsServiceCallback implementation.
2327   var initWifiEnabledCb = {
2328     handle: function handle(aName, aResult) {
2329       if (aName !== SETTINGS_WIFI_ENABLED)
2330         return;
2331       if (aResult === null)
2332         aResult = true;
2333       self.handleWifiEnabled(aResult);
2334     },
2335     handleError: function handleError(aErrorMessage) {
2336       debug("Error reading the 'wifi.enabled' setting. Default to wifi on.");
2337       self.handleWifiEnabled(true);
2338     }
2339   };
2341   var initWifiDebuggingEnabledCb = {
2342     handle: function handle(aName, aResult) {
2343       if (aName !== SETTINGS_WIFI_DEBUG_ENABLED)
2344         return;
2345       if (aResult === null)
2346         aResult = false;
2347       DEBUG = aResult;
2348       updateDebug();
2349     },
2350     handleError: function handleError(aErrorMessage) {
2351       debug("Error reading the 'wifi.debugging.enabled' setting. Default to debugging off.");
2352       DEBUG = false;
2353       updateDebug();
2354     }
2355   };
2357   this.initTetheringSettings();
2359   let lock = gSettingsService.createLock();
2360   lock.get(SETTINGS_WIFI_ENABLED, initWifiEnabledCb);
2361   lock.get(SETTINGS_WIFI_DEBUG_ENABLED, initWifiDebuggingEnabledCb);
2363   lock.get(SETTINGS_WIFI_SSID, this);
2364   lock.get(SETTINGS_WIFI_SECURITY_TYPE, this);
2365   lock.get(SETTINGS_WIFI_SECURITY_PASSWORD, this);
2366   lock.get(SETTINGS_WIFI_IP, this);
2367   lock.get(SETTINGS_WIFI_PREFIX, this);
2368   lock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
2369   lock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
2370   lock.get(SETTINGS_WIFI_DNS1, this);
2371   lock.get(SETTINGS_WIFI_DNS2, this);
2372   lock.get(SETTINGS_WIFI_TETHERING_ENABLED, this);
2374   lock.get(SETTINGS_USB_DHCPSERVER_STARTIP, this);
2375   lock.get(SETTINGS_USB_DHCPSERVER_ENDIP, this);
2377   this._wifiTetheringSettingsToRead = [SETTINGS_WIFI_SSID,
2378                                        SETTINGS_WIFI_SECURITY_TYPE,
2379                                        SETTINGS_WIFI_SECURITY_PASSWORD,
2380                                        SETTINGS_WIFI_IP,
2381                                        SETTINGS_WIFI_PREFIX,
2382                                        SETTINGS_WIFI_DHCPSERVER_STARTIP,
2383                                        SETTINGS_WIFI_DHCPSERVER_ENDIP,
2384                                        SETTINGS_WIFI_DNS1,
2385                                        SETTINGS_WIFI_DNS2,
2386                                        SETTINGS_WIFI_TETHERING_ENABLED,
2387                                        SETTINGS_USB_DHCPSERVER_STARTIP,
2388                                        SETTINGS_USB_DHCPSERVER_ENDIP];
2391 function translateState(state) {
2392   switch (state) {
2393     case "INTERFACE_DISABLED":
2394     case "INACTIVE":
2395     case "SCANNING":
2396     case "DISCONNECTED":
2397     default:
2398       return "disconnected";
2400     case "AUTHENTICATING":
2401     case "ASSOCIATING":
2402     case "ASSOCIATED":
2403     case "FOUR_WAY_HANDSHAKE":
2404     case "GROUP_HANDSHAKE":
2405       return "connecting";
2407     case "COMPLETED":
2408       return WifiManager.getDhcpInfo() ? "connected" : "associated";
2409   }
2412 WifiWorker.prototype = {
2413   classID:   WIFIWORKER_CID,
2414   classInfo: XPCOMUtils.generateCI({classID: WIFIWORKER_CID,
2415                                     contractID: WIFIWORKER_CONTRACTID,
2416                                     classDescription: "WifiWorker",
2417                                     interfaces: [Ci.nsIWorkerHolder,
2418                                                  Ci.nsIWifi,
2419                                                  Ci.nsIObserver]}),
2421   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
2422                                          Ci.nsIWifi,
2423                                          Ci.nsIObserver,
2424                                          Ci.nsISettingsServiceCallback]),
2426   disconnectedByWifi: false,
2428   disconnectedByWifiTethering: false,
2430   _wifiTetheringSettingsToRead: [],
2432   _oldWifiTetheringEnabledState: null,
2434   tetheringSettings: {},
2436   initTetheringSettings: function initTetheringSettings() {
2437     this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = null;
2438     this.tetheringSettings[SETTINGS_WIFI_SSID] = DEFAULT_WIFI_SSID;
2439     this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE] = DEFAULT_WIFI_SECURITY_TYPE;
2440     this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD] = DEFAULT_WIFI_SECURITY_PASSWORD;
2441     this.tetheringSettings[SETTINGS_WIFI_IP] = DEFAULT_WIFI_IP;
2442     this.tetheringSettings[SETTINGS_WIFI_PREFIX] = DEFAULT_WIFI_PREFIX;
2443     this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
2444     this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP;
2445     this.tetheringSettings[SETTINGS_WIFI_DNS1] = DEFAULT_DNS1;
2446     this.tetheringSettings[SETTINGS_WIFI_DNS2] = DEFAULT_DNS2;
2448     this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP] = DEFAULT_USB_DHCPSERVER_STARTIP;
2449     this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP] = DEFAULT_USB_DHCPSERVER_ENDIP;
2450   },
2452   // Internal methods.
2453   waitForScan: function(callback) {
2454     this.wantScanResults.push(callback);
2455   },
2457   // In order to select a specific network, we disable the rest of the
2458   // networks known to us. However, in general, we want the supplicant to
2459   // connect to which ever network it thinks is best, so when we select the
2460   // proper network (or fail to), we need to re-enable the rest.
2461   _enableAllNetworks: function() {
2462     for each (let net in this.configuredNetworks) {
2463       WifiManager.enableNetwork(net.netId, false, function(ok) {
2464         net.disabled = ok ? 1 : 0;
2465       });
2466     }
2467   },
2469   _startConnectionInfoTimer: function() {
2470     if (this._connectionInfoTimer)
2471       return;
2473     var self = this;
2474     function getConnectionInformation() {
2475       WifiManager.getConnectionInfo(function(connInfo) {
2476         // See comments in calculateSignal for information about this.
2477         if (!connInfo) {
2478           self._lastConnectionInfo = null;
2479           return;
2480         }
2482         let { rssi, linkspeed } = connInfo;
2483         if (rssi > 0)
2484           rssi -= 256;
2485         if (rssi <= MIN_RSSI)
2486           rssi = MIN_RSSI;
2487         else if (rssi >= MAX_RSSI)
2488           rssi = MAX_RSSI;
2490         let info = { signalStrength: rssi,
2491                      relSignalStrength: calculateSignal(rssi),
2492                      linkSpeed: linkspeed,
2493                      ipAddress: self.ipAddress };
2494         let last = self._lastConnectionInfo;
2496         // Only fire the event if the link speed changed or the signal
2497         // strength changed by more than 10%.
2498         function tensPlace(percent) ((percent / 10) | 0)
2500         if (last && last.linkSpeed === info.linkSpeed &&
2501             last.ipAddress === info.ipAddress &&
2502             tensPlace(last.relSignalStrength) === tensPlace(info.relSignalStrength)) {
2503           return;
2504         }
2506         self._lastConnectionInfo = info;
2507         debug("Firing connectioninfoupdate: " + uneval(info));
2508         self._fireEvent("connectioninfoupdate", info);
2509       });
2510     }
2512     // Prime our _lastConnectionInfo immediately and fire the event at the
2513     // same time.
2514     getConnectionInformation();
2516     // Now, set up the timer for regular updates.
2517     this._connectionInfoTimer =
2518       Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
2519     this._connectionInfoTimer.init(getConnectionInformation, 5000,
2520                                    Ci.nsITimer.TYPE_REPEATING_SLACK);
2521   },
2523   _stopConnectionInfoTimer: function() {
2524     if (!this._connectionInfoTimer)
2525       return;
2527     this._connectionInfoTimer.cancel();
2528     this._connectionInfoTimer = null;
2529     this._lastConnectionInfo = null;
2530   },
2532   _reloadConfiguredNetworks: function(callback) {
2533     WifiManager.getConfiguredNetworks((function(networks) {
2534       if (!networks) {
2535         debug("Unable to get configured networks");
2536         callback(false);
2537         return;
2538       }
2540       this._highestPriority = -1;
2542       // Convert between netId-based and ssid-based indexing.
2543       for (let net in networks) {
2544         let network = networks[net];
2545         delete networks[net];
2547         if (!network.ssid) {
2548           WifiManager.removeNetwork(network.netId, function() {});
2549           continue;
2550         }
2552         if (network.hasOwnProperty("priority") &&
2553             network.priority > this._highestPriority) {
2554           this._highestPriority = network.priority;
2555         }
2557         let networkKey = getNetworkKey(network);
2558         // Accept latest config of same network(same SSID and same security).
2559         if (networks[networkKey]) {
2560           WifiManager.removeNetwork(networks[networkKey].netId, function() {});
2561         }
2562         networks[networkKey] = network;
2563       }
2565       this.configuredNetworks = networks;
2566       callback(true);
2567     }).bind(this));
2568   },
2570   // Important side effect: calls WifiManager.saveConfig.
2571   _reprioritizeNetworks: function(callback) {
2572     // First, sort the networks in orer of their priority.
2573     var ordered = Object.getOwnPropertyNames(this.configuredNetworks);
2574     let self = this;
2575     ordered.sort(function(a, b) {
2576       var neta = self.configuredNetworks[a],
2577           netb = self.configuredNetworks[b];
2579       // Sort unsorted networks to the end of the list.
2580       if (isNaN(neta.priority))
2581         return isNaN(netb.priority) ? 0 : 1;
2582       if (isNaN(netb.priority))
2583         return -1;
2584       return netb.priority - neta.priority;
2585     });
2587     // Skip unsorted networks.
2588     let newPriority = 0, i;
2589     for (i = ordered.length - 1; i >= 0; --i) {
2590       if (!isNaN(this.configuredNetworks[ordered[i]].priority))
2591         break;
2592     }
2594     // No networks we care about?
2595     if (i < 0) {
2596       WifiManager.saveConfig(callback);
2597       return;
2598     }
2600     // Now assign priorities from 0 to length, starting with the smallest
2601     // priority and heading towards the highest (note the dependency between
2602     // total and i here).
2603     let done = 0, errors = 0, total = i + 1;
2604     for (; i >= 0; --i) {
2605       let network = this.configuredNetworks[ordered[i]];
2606       network.priority = newPriority++;
2608       // Note: networkUpdated declared below since it happens logically after
2609       // this loop.
2610       WifiManager.updateNetwork(network, networkUpdated);
2611     }
2613     function networkUpdated(ok) {
2614       if (!ok)
2615         ++errors;
2616       if (++done === total) {
2617         if (errors > 0) {
2618           callback(false);
2619           return;
2620         }
2622         WifiManager.saveConfig(function(ok) {
2623           if (!ok) {
2624             callback(false);
2625             return;
2626           }
2628           self._reloadConfiguredNetworks(function(ok) {
2629             callback(ok);
2630           });
2631         });
2632       }
2633     }
2634   },
2636   // nsIWifi
2638   _domManagers: [],
2639   _fireEvent: function(message, data) {
2640     this._domManagers.forEach(function(manager) {
2641       // Note: We should never have a dead message manager here because we
2642       // observe our child message managers shutting down, below.
2643       manager.sendAsyncMessage("WifiManager:" + message, data);
2644     });
2645   },
2647   _sendMessage: function(message, success, data, msg) {
2648     try {
2649       msg.manager.sendAsyncMessage(message + (success ? ":OK" : ":NO"),
2650                                    { data: data, rid: msg.rid, mid: msg.mid });
2651     } catch (e) {
2652       debug("sendAsyncMessage error : " + e);
2653     }
2654     this._splicePendingRequest(msg);
2655   },
2657   _domRequest: [],
2659   _splicePendingRequest: function(msg) {
2660     for (let i = 0; i < this._domRequest.length; i++) {
2661       if (this._domRequest[i].msg === msg) {
2662         this._domRequest.splice(i, 1);
2663         return;
2664       }
2665     }
2666   },
2668   _clearPendingRequest: function() {
2669     if (this._domRequest.length === 0) return;
2670     this._domRequest.forEach((function(req) {
2671       this._sendMessage(req.name + ":Return", false, "Wifi is disabled", req.msg);
2672     }).bind(this));
2673   },
2675   receiveMessage: function MessageManager_receiveMessage(aMessage) {
2676     let msg = aMessage.data || {};
2677     msg.manager = aMessage.target;
2679     if (WifiManager.p2pSupported()) {
2680       // If p2pObserver returns something truthy, return it!
2681       // Otherwise, continue to do the rest of tasks.
2682       var p2pRet = this._p2pObserver.onDOMMessage(aMessage);
2683       if (p2pRet) {
2684         return p2pRet;
2685       }
2686     }
2688     // Note: By the time we receive child-process-shutdown, the child process
2689     // has already forgotten its permissions so we do this before the
2690     // permissions check.
2691     if (aMessage.name === "child-process-shutdown") {
2692       let i;
2693       if ((i = this._domManagers.indexOf(msg.manager)) != -1) {
2694         this._domManagers.splice(i, 1);
2695       }
2696       for (i = this._domRequest.length - 1; i >= 0; i--) {
2697         if (this._domRequest[i].msg.manager === msg.manager) {
2698           this._domRequest.splice(i, 1);
2699         }
2700       }
2701       return;
2702     }
2704     if (!aMessage.target.assertPermission("wifi-manage")) {
2705       return;
2706     }
2708     // We are interested in DOMRequests only.
2709     if (aMessage.name != "WifiManager:getState") {
2710       this._domRequest.push({name: aMessage.name, msg:msg});
2711     }
2713     switch (aMessage.name) {
2714       case "WifiManager:setWifiEnabled":
2715         this.setWifiEnabled(msg);
2716         break;
2717       case "WifiManager:getNetworks":
2718         this.getNetworks(msg);
2719         break;
2720       case "WifiManager:getKnownNetworks":
2721         this.getKnownNetworks(msg);
2722         break;
2723       case "WifiManager:associate":
2724         this.associate(msg);
2725         break;
2726       case "WifiManager:forget":
2727         this.forget(msg);
2728         break;
2729       case "WifiManager:wps":
2730         this.wps(msg);
2731         break;
2732       case "WifiManager:setPowerSavingMode":
2733         this.setPowerSavingMode(msg);
2734         break;
2735       case "WifiManager:setHttpProxy":
2736         this.setHttpProxy(msg);
2737         break;
2738       case "WifiManager:setStaticIpMode":
2739         this.setStaticIpMode(msg);
2740         break;
2741       case "WifiManager:importCert":
2742         this.importCert(msg);
2743         break;
2744       case "WifiManager:getImportedCerts":
2745         this.getImportedCerts(msg);
2746         break;
2747       case "WifiManager:deleteCert":
2748         this.deleteCert(msg);
2749         break;
2750       case "WifiManager:setWifiTethering":
2751         this.setWifiTethering(msg);
2752         break;
2753       case "WifiManager:getState": {
2754         let i;
2755         if ((i = this._domManagers.indexOf(msg.manager)) === -1) {
2756           this._domManagers.push(msg.manager);
2757         }
2759         let net = this.currentNetwork ? netToDOM(this.currentNetwork) : null;
2760         return { network: net,
2761                  connectionInfo: this._lastConnectionInfo,
2762                  enabled: WifiManager.enabled,
2763                  status: translateState(WifiManager.state),
2764                  macAddress: this.macAddress,
2765                  capabilities: WifiManager.getCapabilities()};
2766       }
2767     }
2768   },
2770   getNetworks: function(msg) {
2771     const message = "WifiManager:getNetworks:Return";
2772     if (!WifiManager.enabled) {
2773       this._sendMessage(message, false, "Wifi is disabled", msg);
2774       return;
2775     }
2777     let sent = false;
2778     let callback = (function (networks) {
2779       if (sent)
2780         return;
2781       sent = true;
2782       this._sendMessage(message, networks !== null, networks, msg);
2783     }).bind(this);
2784     this.waitForScan(callback);
2786     WifiManager.scan(true, (function(ok) {
2787       // If the scan command succeeded, we're done.
2788       if (ok)
2789         return;
2791       // Avoid sending multiple responses.
2792       if (sent)
2793         return;
2795       // Otherwise, let the client know that it failed, it's responsible for
2796       // trying again in a few seconds.
2797       sent = true;
2798       this._sendMessage(message, false, "ScanFailed", msg);
2799     }).bind(this));
2800   },
2802   getWifiScanResults: function(callback) {
2803     var count = 0;
2804     var timer = null;
2805     var self = this;
2807     if (!WifiManager.enabled) {
2808       callback.onfailure();
2809       return;
2810     }
2812     self.waitForScan(waitForScanCallback);
2813     doScan();
2814     function doScan() {
2815       WifiManager.scan(true, (function (ok) {
2816         if (!ok) {
2817           if (!timer) {
2818             count = 0;
2819             timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
2820           }
2822           if (count++ >= 3) {
2823             timer = null;
2824             self.wantScanResults.splice(self.wantScanResults.indexOf(waitForScanCallback), 1);
2825             callback.onfailure();
2826             return;
2827           }
2829           // Else it's still running, continue waiting.
2830           timer.initWithCallback(doScan, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
2831           return;
2832         }
2833       }).bind(this));
2834     }
2836     function waitForScanCallback(networks) {
2837       if (networks === null) {
2838         callback.onfailure();
2839         return;
2840       }
2842       var wifiScanResults = new Array();
2843       var net;
2844       for (let net in networks) {
2845         let value = networks[net];
2846         wifiScanResults.push(transformResult(value));
2847       }
2848       callback.onready(wifiScanResults.length, wifiScanResults);
2849     }
2851     function transformResult(element) {
2852       var result = new WifiScanResult();
2853       result.connected = false;
2854       for (let id in element) {
2855         if (id === "__exposedProps__") {
2856           continue;
2857         }
2858         if (id === "security") {
2859           result[id] = 0;
2860           var security = element[id];
2861           for (let j = 0; j < security.length; j++) {
2862             if (security[j] === "WPA-PSK") {
2863               result[id] |= Ci.nsIWifiScanResult.WPA_PSK;
2864             } else if (security[j] === "WPA-EAP") {
2865               result[id] |= Ci.nsIWifiScanResult.WPA_EAP;
2866             } else if (security[j] === "WEP") {
2867               result[id] |= Ci.nsIWifiScanResult.WEP;
2868             } else {
2869              result[id] = 0;
2870             }
2871           }
2872         } else {
2873           result[id] = element[id];
2874         }
2875       }
2876       return result;
2877     }
2878   },
2880   getKnownNetworks: function(msg) {
2881     const message = "WifiManager:getKnownNetworks:Return";
2882     if (!WifiManager.enabled) {
2883       this._sendMessage(message, false, "Wifi is disabled", msg);
2884       return;
2885     }
2887     this._reloadConfiguredNetworks((function(ok) {
2888       if (!ok) {
2889         this._sendMessage(message, false, "Failed", msg);
2890         return;
2891       }
2893       var networks = [];
2894       for (let networkKey in this.configuredNetworks) {
2895         networks.push(netToDOM(this.configuredNetworks[networkKey]));
2896       }
2898       this._sendMessage(message, true, networks, msg);
2899     }).bind(this));
2900   },
2902   _setWifiEnabledCallback: function(status) {
2903     if (status !== 0) {
2904       this.requestDone();
2905       return;
2906     }
2908     // If we're enabling ourselves, then wait until we've connected to the
2909     // supplicant to notify. If we're disabling, we take care of this in
2910     // supplicantlost.
2911     if (WifiManager.supplicantStarted)
2912       WifiManager.start();
2913   },
2915   /**
2916    * Compatibility flags for detecting if Gaia is controlling wifi by settings
2917    * or API, once API is called, gecko will no longer accept wifi enable
2918    * control from settings.
2919    * This is used to deal with compatibility issue while Gaia adopted to use
2920    * API but gecko doesn't remove the settings code in time.
2921    * TODO: Remove this flag in Bug 1050147
2922    */
2923   ignoreWifiEnabledFromSettings: false,
2924   setWifiEnabled: function(msg) {
2925     const message = "WifiManager:setWifiEnabled:Return";
2926     let self = this;
2927     let enabled = msg.data;
2929     self.ignoreWifiEnabledFromSettings = true;
2930     // No change.
2931     if (enabled === WifiManager.enabled) {
2932       this._sendMessage(message, true, true, msg);
2933       return;
2934     }
2936     // Can't enable wifi while hotspot mode is enabled.
2937     if (enabled && (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
2938         WifiManager.isWifiTetheringEnabled(WifiManager.tetheringState))) {
2939       self._sendMessage(message, false, "Can't enable Wifi while hotspot mode is enabled", msg);
2940       return;
2941     }
2943     WifiManager.setWifiEnabled(enabled, function(ok) {
2944       if (ok === 0 || ok === "no change") {
2945         self._sendMessage(message, true, true, msg);
2947         // Reply error to pending requests.
2948         if (!enabled) {
2949           self._clearPendingRequest();
2950         } else {
2951           WifiManager.start();
2952         }
2953       } else {
2954         self._sendMessage(message, false, "Set wifi enabled failed", msg);
2955       }
2956     });
2957   },
2959   _setWifiEnabled: function(enabled, callback) {
2960     // Reply error to pending requests.
2961     if (!enabled) {
2962       this._clearPendingRequest();
2963     }
2965     WifiManager.setWifiEnabled(enabled, callback);
2966   },
2968   // requestDone() must be called to before callback complete(or error)
2969   // so next queue in the request quene can be executed.
2970   // TODO: Remove command queue in Bug 1050147
2971   queueRequest: function(data, callback) {
2972     if (!callback) {
2973         throw "Try to enqueue a request without callback";
2974     }
2976     let optimizeCommandList = ["setWifiEnabled", "setWifiApEnabled"];
2977     if (optimizeCommandList.indexOf(data.command) != -1) {
2978       this._stateRequests = this._stateRequests.filter(function(element) {
2979         return element.data.command !== data.command;
2980       });
2981     }
2983     this._stateRequests.push({
2984       data: data,
2985       callback: callback
2986     });
2988     this.nextRequest();
2989   },
2991   getWifiTetheringParameters: function getWifiTetheringParameters(enable) {
2992     if (this.useTetheringAPI) {
2993       return this.getWifiTetheringConfiguration(enable);
2994     } else {
2995       return this.getWifiTetheringParametersBySetting(enable);
2996     }
2997   },
2999   getWifiTetheringConfiguration: function getWifiTetheringConfiguration(enable) {
3000     let config = {};
3001     let params = this.tetheringConfig;
3003     let check = function(field, _default) {
3004       config[field] = field in params ? params[field] : _default;
3005     };
3007     check("ssid", DEFAULT_WIFI_SSID);
3008     check("security", DEFAULT_WIFI_SECURITY_TYPE);
3009     check("key", DEFAULT_WIFI_SECURITY_PASSWORD);
3010     check("ip", DEFAULT_WIFI_IP);
3011     check("prefix", DEFAULT_WIFI_PREFIX);
3012     check("wifiStartIp", DEFAULT_WIFI_DHCPSERVER_STARTIP);
3013     check("wifiEndIp", DEFAULT_WIFI_DHCPSERVER_ENDIP);
3014     check("usbStartIp", DEFAULT_USB_DHCPSERVER_STARTIP);
3015     check("usbEndIp", DEFAULT_USB_DHCPSERVER_ENDIP);
3016     check("dns1", DEFAULT_DNS1);
3017     check("dns2", DEFAULT_DNS2);
3019     config.enable = enable;
3020     config.mode = enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION;
3021     config.link = enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN;
3023     // Check the format to prevent netd from crash.
3024     if (enable && (!config.ssid || config.ssid == "")) {
3025       debug("Invalid SSID value.");
3026       return null;
3027     }
3029     if (enable && (config.security != WIFI_SECURITY_TYPE_NONE && !config.key)) {
3030       debug("Invalid security password.");
3031       return null;
3032     }
3034     // Using the default values here until application supports these settings.
3035     if (config.ip == "" || config.prefix == "" ||
3036         config.wifiStartIp == "" || config.wifiEndIp == "" ||
3037         config.usbStartIp == "" || config.usbEndIp == "") {
3038       debug("Invalid subnet information.");
3039       return null;
3040     }
3042     return config;
3043   },
3045   getWifiTetheringParametersBySetting: function getWifiTetheringParametersBySetting(enable) {
3046     let ssid;
3047     let securityType;
3048     let securityId;
3049     let interfaceIp;
3050     let prefix;
3051     let wifiDhcpStartIp;
3052     let wifiDhcpEndIp;
3053     let usbDhcpStartIp;
3054     let usbDhcpEndIp;
3055     let dns1;
3056     let dns2;
3058     ssid = this.tetheringSettings[SETTINGS_WIFI_SSID];
3059     securityType = this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE];
3060     securityId = this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD];
3061     interfaceIp = this.tetheringSettings[SETTINGS_WIFI_IP];
3062     prefix = this.tetheringSettings[SETTINGS_WIFI_PREFIX];
3063     wifiDhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
3064     wifiDhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
3065     usbDhcpStartIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP];
3066     usbDhcpEndIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP];
3067     dns1 = this.tetheringSettings[SETTINGS_WIFI_DNS1];
3068     dns2 = this.tetheringSettings[SETTINGS_WIFI_DNS2];
3070     // Check the format to prevent netd from crash.
3071     if (!ssid || ssid == "") {
3072       debug("Invalid SSID value.");
3073       return null;
3074     }
3075     // Truncate ssid if its length of encoded to utf8 is longer than 32.
3076     while (unescape(encodeURIComponent(ssid)).length > 32)
3077     {
3078       ssid = ssid.substring(0, ssid.length-1);
3079     }
3081     if (securityType != WIFI_SECURITY_TYPE_NONE &&
3082         securityType != WIFI_SECURITY_TYPE_WPA_PSK &&
3083         securityType != WIFI_SECURITY_TYPE_WPA2_PSK) {
3085       debug("Invalid security type.");
3086       return null;
3087     }
3088     if (securityType != WIFI_SECURITY_TYPE_NONE && !securityId) {
3089       debug("Invalid security password.");
3090       return null;
3091     }
3092     // Using the default values here until application supports these settings.
3093     if (interfaceIp == "" || prefix == "" ||
3094         wifiDhcpStartIp == "" || wifiDhcpEndIp == "" ||
3095         usbDhcpStartIp == "" || usbDhcpEndIp == "") {
3096       debug("Invalid subnet information.");
3097       return null;
3098     }
3100     return {
3101       ssid: ssid,
3102       security: securityType,
3103       key: securityId,
3104       ip: interfaceIp,
3105       prefix: prefix,
3106       wifiStartIp: wifiDhcpStartIp,
3107       wifiEndIp: wifiDhcpEndIp,
3108       usbStartIp: usbDhcpStartIp,
3109       usbEndIp: usbDhcpEndIp,
3110       dns1: dns1,
3111       dns2: dns2,
3112       enable: enable,
3113       mode: enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION,
3114       link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
3115     };
3116   },
3118   setWifiApEnabled: function(enabled, callback) {
3119     let configuration = this.getWifiTetheringParameters(enabled);
3121     if (!configuration) {
3122       this.requestDone();
3123       debug("Invalid Wifi Tethering configuration.");
3124       return;
3125     }
3127     WifiManager.setWifiApEnabled(enabled, configuration, callback);
3128   },
3130   associate: function(msg) {
3131     const MAX_PRIORITY = 9999;
3132     const message = "WifiManager:associate:Return";
3133     let network = msg.data;
3135     let privnet = network;
3136     let dontConnect = privnet.dontConnect;
3137     delete privnet.dontConnect;
3139     if (!WifiManager.enabled) {
3140       this._sendMessage(message, false, "Wifi is disabled", msg);
3141       return;
3142     }
3144     let self = this;
3145     function networkReady() {
3146       // saveConfig now before we disable most of the other networks.
3147       function selectAndConnect() {
3148         WifiManager.enableNetwork(privnet.netId, true, function (ok) {
3149           if (ok)
3150             self._needToEnableNetworks = true;
3151           if (WifiManager.state === "DISCONNECTED" ||
3152               WifiManager.state === "SCANNING") {
3153             WifiManager.reconnect(function (ok) {
3154               self._sendMessage(message, ok, ok, msg);
3155             });
3156           } else {
3157             self._sendMessage(message, ok, ok, msg);
3158           }
3159         });
3160       }
3162       var selectAndConnectOrReturn = dontConnect ?
3163         function() {
3164           self._sendMessage(message, true, "Wifi has been recorded", msg);
3165         } : selectAndConnect;
3166       if (self._highestPriority >= MAX_PRIORITY) {
3167         self._reprioritizeNetworks(selectAndConnectOrReturn);
3168       } else {
3169         WifiManager.saveConfig(selectAndConnectOrReturn);
3170       }
3171     }
3173     let ssid = privnet.ssid;
3174     let networkKey = getNetworkKey(privnet);
3175     let configured;
3177     if (networkKey in this._addingNetworks) {
3178       this._sendMessage(message, false, "Racing associates");
3179       return;
3180     }
3182     if (networkKey in this.configuredNetworks)
3183       configured = this.configuredNetworks[networkKey];
3185     netFromDOM(privnet, configured);
3187     privnet.priority = ++this._highestPriority;
3188     if (configured) {
3189       privnet.netId = configured.netId;
3190       // Sync priority back to configured so if priority reaches MAX_PRIORITY,
3191       // it can be sorted correctly in _reprioritizeNetworks() because the
3192       // function sort network based on priority in configure list.
3193       configured.priority = privnet.priority;
3194       WifiManager.updateNetwork(privnet, (function(ok) {
3195         if (!ok) {
3196           this._sendMessage(message, false, "Network is misconfigured", msg);
3197           return;
3198         }
3200         networkReady();
3201       }).bind(this));
3202     } else {
3203       // networkReady, above, calls saveConfig. We want to remember the new
3204       // network as being enabled, which isn't the default, so we explicitly
3205       // set it to being "enabled" before we add it and save the
3206       // configuration.
3207       privnet.disabled = 0;
3208       this._addingNetworks[networkKey] = privnet;
3209       WifiManager.addNetwork(privnet, (function(ok) {
3210         delete this._addingNetworks[networkKey];
3212         if (!ok) {
3213           this._sendMessage(message, false, "Network is misconfigured", msg);
3214           return;
3215         }
3217         this.configuredNetworks[networkKey] = privnet;
3218         networkReady();
3219       }).bind(this));
3220     }
3221   },
3223   forget: function(msg) {
3224     const message = "WifiManager:forget:Return";
3225     let network = msg.data;
3226     if (!WifiManager.enabled) {
3227       this._sendMessage(message, false, "Wifi is disabled", msg);
3228       return;
3229     }
3231     this._reloadConfiguredNetworks((function(ok) {
3232       // Give it a chance to remove the network even if reload is failed.
3233       if (!ok) {
3234         debug("Warning !!! Failed to reload the configured networks");
3235       }
3237       let ssid = network.ssid;
3238       let networkKey = getNetworkKey(network);
3239       if (!(networkKey in this.configuredNetworks)) {
3240         this._sendMessage(message, false, "Trying to forget an unknown network", msg);
3241         return;
3242       }
3244       let self = this;
3245       let configured = this.configuredNetworks[networkKey];
3246       this._reconnectOnDisconnect = (this.currentNetwork &&
3247                                     (this.currentNetwork.ssid === ssid));
3248       WifiManager.removeNetwork(configured.netId, function(ok) {
3249         if (self._needToEnableNetworks) {
3250           self._enableAllNetworks();
3251           self._needToEnableNetworks = false;
3252         }
3254         if (!ok) {
3255           self._sendMessage(message, false, "Unable to remove the network", msg);
3256           self._reconnectOnDisconnect = false;
3257           return;
3258         }
3260         WifiManager.saveConfig(function() {
3261           self._reloadConfiguredNetworks(function() {
3262             self._sendMessage(message, true, true, msg);
3263           });
3264         });
3265       });
3266     }).bind(this));
3267   },
3269   wps: function(msg) {
3270     const message = "WifiManager:wps:Return";
3271     let self = this;
3272     let detail = msg.data;
3274     if (!WifiManager.enabled) {
3275       this._sendMessage(message, false, "Wifi is disabled", msg);
3276       return;
3277     }
3279     if (detail.method === "pbc") {
3280       WifiManager.wpsPbc(function(ok) {
3281         if (ok)
3282           self._sendMessage(message, true, true, msg);
3283         else
3284           self._sendMessage(message, false, "WPS PBC failed", msg);
3285       });
3286     } else if (detail.method === "pin") {
3287       WifiManager.wpsPin(detail, function(pin) {
3288         if (pin)
3289           self._sendMessage(message, true, pin, msg);
3290         else
3291           self._sendMessage(message, false, "WPS PIN failed", msg);
3292       });
3293     } else if (detail.method === "cancel") {
3294       WifiManager.wpsCancel(function(ok) {
3295         if (ok)
3296           self._sendMessage(message, true, true, msg);
3297         else
3298           self._sendMessage(message, false, "WPS Cancel failed", msg);
3299       });
3300     } else {
3301       self._sendMessage(message, false, "Invalid WPS method=" + detail.method,
3302                         msg);
3303     }
3304   },
3306   setPowerSavingMode: function(msg) {
3307     const message = "WifiManager:setPowerSavingMode:Return";
3308     let self = this;
3309     let enabled = msg.data;
3310     let mode = enabled ? "AUTO" : "ACTIVE";
3312     if (!WifiManager.enabled) {
3313       this._sendMessage(message, false, "Wifi is disabled", msg);
3314       return;
3315     }
3317     // Some wifi drivers may not implement this command. Set power mode
3318     // even if suspend optimization command failed.
3319     WifiManager.setSuspendOptimizations(enabled, function(ok) {
3320       WifiManager.setPowerMode(mode, function(ok) {
3321         if (ok) {
3322           self._sendMessage(message, true, true, msg);
3323         } else {
3324           self._sendMessage(message, false, "Set power saving mode failed", msg);
3325         }
3326       });
3327     });
3328   },
3330   setHttpProxy: function(msg) {
3331     const message = "WifiManager:setHttpProxy:Return";
3332     let self = this;
3333     let network = msg.data.network;
3334     let info = msg.data.info;
3336     if (!WifiManager.enabled) {
3337       this._sendMessage(message, false, "Wifi is disabled", msg);
3338       return;
3339     }
3341     WifiManager.configureHttpProxy(network, info, function(ok) {
3342       if (ok) {
3343         // If configured network is current connected network
3344         // need update http proxy immediately.
3345         let setNetworkKey = getNetworkKey(network);
3346         let curNetworkKey = self.currentNetwork ? getNetworkKey(self.currentNetwork) : null;
3347         if (setNetworkKey === curNetworkKey)
3348           WifiManager.setHttpProxy(network);
3350         self._sendMessage(message, true, true, msg);
3351       } else {
3352         self._sendMessage(message, false, "Set http proxy failed", msg);
3353       }
3354     });
3355   },
3357   setStaticIpMode: function(msg) {
3358     const message = "WifiManager:setStaticMode:Return";
3359     let self = this;
3360     let network = msg.data.network;
3361     let info = msg.data.info;
3363     if (!WifiManager.enabled) {
3364       this._sendMessage(message, false, "Wifi is disabled", msg);
3365       return;
3366     }
3368     // To compatiable with DHCP returned info structure, do translation here
3369     info.ipaddr_str = info.ipaddr;
3370     info.proxy_str = info.proxy;
3371     info.gateway_str = info.gateway;
3372     info.dns1_str = info.dns1;
3373     info.dns2_str = info.dns2;
3375     WifiManager.setStaticIpMode(network, info, function(ok) {
3376       if (ok) {
3377         self._sendMessage(message, true, true, msg);
3378       } else {
3379         self._sendMessage(message, false, "Set static ip mode failed", msg);
3380       }
3381     });
3382   },
3384   importCert: function importCert(msg) {
3385     const message = "WifiManager:importCert:Return";
3386     let self = this;
3388     if (!WifiManager.enabled) {
3389       this._sendMessage(message, false, "Wifi is disabled", msg);
3390       return;
3391     }
3393     WifiManager.importCert(msg.data, function(data) {
3394       if (data.status === 0) {
3395         let usageString = ["ServerCert"];
3396         let usageArray = [];
3397         for (let i = 0; i < usageString.length; i++) {
3398           if (data.usageFlag & (0x01 << i)) {
3399             usageArray.push(usageString[i]);
3400           }
3401         }
3403         self._sendMessage(message, true, {
3404           nickname: data.nickname,
3405           usage: usageArray
3406         }, msg);
3407       } else {
3408         self._sendMessage(message, false, "Import Cert failed", msg);
3409       }
3410     });
3411   },
3413   getImportedCerts: function getImportedCerts(msg) {
3414     const message = "WifiManager:getImportedCerts:Return";
3415     let self = this;
3417     if (!WifiManager.enabled) {
3418       this._sendMessage(message, false, "Wifi is disabled", msg);
3419       return;
3420     }
3422     let certDB = Cc["@mozilla.org/security/x509certdb;1"]
3423                  .getService(Ci.nsIX509CertDB);
3424     if (!certDB) {
3425       self._sendMessage(message, false, "Failed to query NSS DB service", msg);
3426     }
3428     let certList = certDB.getCerts();
3429     if (!certList) {
3430       self._sendMessage(message, false, "Failed to get certificate List", msg);
3431     }
3433     let certListEnum = certList.getEnumerator();
3434     if (!certListEnum) {
3435       self._sendMessage(message, false, "Failed to get certificate List enumerator", msg);
3436     }
3437     let importedCerts = {
3438       ServerCert: [],
3439     };
3440     let UsageMapping = {
3441       SERVERCERT: "ServerCert",
3442     };
3444     while (certListEnum.hasMoreElements()) {
3445       let certInfo = certListEnum.getNext().QueryInterface(Ci.nsIX509Cert);
3446       let certNicknameInfo = /WIFI\_([A-Z]*)\_(.*)/.exec(certInfo.nickname);
3447       if (!certNicknameInfo) {
3448         continue;
3449       }
3450       importedCerts[UsageMapping[certNicknameInfo[1]]].push(certNicknameInfo[2]);
3451     }
3453     self._sendMessage(message, true, importedCerts, msg);
3454   },
3456   deleteCert: function deleteCert(msg) {
3457     const message = "WifiManager:deleteCert:Return";
3458     let self = this;
3460     if (!WifiManager.enabled) {
3461       this._sendMessage(message, false, "Wifi is disabled", msg);
3462       return;
3463     }
3465     WifiManager.deleteCert(msg.data, function(data) {
3466       self._sendMessage(message, data.status === 0, "Delete Cert failed", msg);
3467     });
3468   },
3470   // TODO : These two variables should be removed once GAIA uses tethering API.
3471   useTetheringAPI : false,
3472   tetheringConfig : {},
3474   setWifiTethering: function setWifiTethering(msg) {
3475     const message = "WifiManager:setWifiTethering:Return";
3476     let self = this;
3477     let enabled = msg.data.enabled;
3479     this.useTetheringAPI = true;
3480     this.tetheringConfig = msg.data.config;
3482     if (WifiManager.enabled) {
3483       this._sendMessage(message, false, "Wifi is enabled", msg);
3484       return;
3485     }
3487     this.setWifiApEnabled(enabled, function() {
3488       if ((enabled && WifiManager.tetheringState == "COMPLETED") ||
3489           (!enabled && WifiManager.tetheringState == "UNINITIALIZED")) {
3490         self._sendMessage(message, true, msg.data, msg);
3491       } else {
3492         msg.data.reason = enabled ?
3493           "Enable WIFI tethering faild" : "Disable WIFI tethering faild";
3494         self._sendMessage(message, false, msg.data, msg);
3495       }
3496     });
3497   },
3499   // This is a bit ugly, but works. In particular, this depends on the fact
3500   // that RadioManager never actually tries to get the worker from us.
3501   get worker() { throw "Not implemented"; },
3503   shutdown: function() {
3504     debug("shutting down ...");
3505     this.queueRequest({command: "setWifiEnabled", value: false}, function(data) {
3506       this._setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
3507     }.bind(this));
3508   },
3510   // TODO: Remove command queue in Bug 1050147.
3511   requestProcessing: false,   // Hold while dequeue and execution a request.
3512                               // Released upon the request is fully executed,
3513                               // i.e, mostly after callback is done.
3514   requestDone: function requestDone() {
3515     this.requestProcessing = false;
3516     this.nextRequest();
3517   },
3519   nextRequest: function nextRequest() {
3520     // No request to process
3521     if (this._stateRequests.length === 0) {
3522       return;
3523     }
3525     // Handling request, wait for it.
3526     if (this.requestProcessing) {
3527       return;
3528     }
3530     // Hold processing lock
3531     this.requestProcessing = true;
3533     // Find next valid request
3534     let request = this._stateRequests.shift();
3536     request.callback(request.data);
3537   },
3539   notifyTetheringOn: function notifyTetheringOn() {
3540     // It's really sad that we don't have an API to notify the wifi
3541     // hotspot status. Toggle settings to let gaia know that wifi hotspot
3542     // is enabled.
3543     let self = this;
3544     this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = true;
3545     this._oldWifiTetheringEnabledState = true;
3546     gSettingsService.createLock().set(
3547       SETTINGS_WIFI_TETHERING_ENABLED,
3548       true,
3549       {
3550         handle: function(aName, aResult) {
3551           self.requestDone();
3552         },
3553         handleError: function(aErrorMessage) {
3554           self.requestDone();
3555         }
3556       });
3557   },
3559   notifyTetheringOff: function notifyTetheringOff() {
3560     // It's really sad that we don't have an API to notify the wifi
3561     // hotspot status. Toggle settings to let gaia know that wifi hotspot
3562     // is disabled.
3563     let self = this;
3564     this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
3565     this._oldWifiTetheringEnabledState = false;
3566     gSettingsService.createLock().set(
3567       SETTINGS_WIFI_TETHERING_ENABLED,
3568       false,
3569       {
3570         handle: function(aName, aResult) {
3571           self.requestDone();
3572         },
3573         handleError: function(aErrorMessage) {
3574           self.requestDone();
3575         }
3576       });
3577   },
3579   handleWifiEnabled: function(enabled) {
3580     if (this.ignoreWifiEnabledFromSettings) {
3581       return;
3582     }
3584     // Make sure Wifi hotspot is idle before switching to Wifi mode.
3585     if (enabled) {
3586       this.queueRequest({command: "setWifiApEnabled", value: false}, function(data) {
3587         if (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
3588             WifiManager.isWifiTetheringEnabled(WifiManager.tetheringState)) {
3589           this.disconnectedByWifi = true;
3590           this.setWifiApEnabled(false, this.notifyTetheringOff.bind(this));
3591         } else {
3592           this.requestDone();
3593         }
3594       }.bind(this));
3595     }
3597     this.queueRequest({command: "setWifiEnabled", value: enabled}, function(data) {
3598       this._setWifiEnabled(enabled, this._setWifiEnabledCallback.bind(this));
3599     }.bind(this));
3601     if (!enabled) {
3602       this.queueRequest({command: "setWifiApEnabled", value: true}, function(data) {
3603         if (this.disconnectedByWifi) {
3604           this.setWifiApEnabled(true, this.notifyTetheringOn.bind(this));
3605         } else {
3606           this.requestDone();
3607         }
3608         this.disconnectedByWifi = false;
3609       }.bind(this));
3610     }
3611   },
3613   handleWifiTetheringEnabled: function(enabled) {
3614     // Make sure Wifi is idle before switching to Wifi hotspot mode.
3615     if (enabled) {
3616       this.queueRequest({command: "setWifiEnabled", value: false}, function(data) {
3617         if (WifiManager.isWifiEnabled(WifiManager.state)) {
3618           this.disconnectedByWifiTethering = true;
3619           this._setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
3620         } else {
3621           this.requestDone();
3622         }
3623       }.bind(this));
3624     }
3626     this.queueRequest({command: "setWifiApEnabled", value: enabled}, function(data) {
3627       this.setWifiApEnabled(enabled, this.requestDone.bind(this));
3628     }.bind(this));
3630     if (!enabled) {
3631       this.queueRequest({command: "setWifiEnabled", value: true}, function(data) {
3632         if (this.disconnectedByWifiTethering) {
3633           this._setWifiEnabled(true, this._setWifiEnabledCallback.bind(this));
3634         } else {
3635           this.requestDone();
3636         }
3637         this.disconnectedByWifiTethering = false;
3638       }.bind(this));
3639     }
3640   },
3642   // nsIObserver implementation
3643   observe: function observe(subject, topic, data) {
3644     switch (topic) {
3645     case kMozSettingsChangedObserverTopic:
3646       // The string we're interested in will be a JSON string that looks like:
3647       // {"key":"wifi.enabled","value":"true"}.
3649       let setting = JSON.parse(data);
3650       // To avoid WifiWorker setting the wifi again, don't need to deal with
3651       // the "mozsettings-changed" event fired from internal setting.
3652       if (setting.isInternalChange) {
3653         return;
3654       }
3656       this.handle(setting.key, setting.value);
3657       break;
3659     case "xpcom-shutdown":
3660       let wifiService = Cc["@mozilla.org/wifi/service;1"].getService(Ci.nsIWifiProxyService);
3661       wifiService.shutdown();
3662       let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"].getService(Ci.nsIWifiCertService);
3663       wifiCertService.shutdown();
3664       break;
3665     }
3666   },
3668   handle: function handle(aName, aResult) {
3669     switch(aName) {
3670       // TODO: Remove function call in Bug 1050147.
3671       case SETTINGS_WIFI_ENABLED:
3672         this.handleWifiEnabled(aResult)
3673         break;
3674       case SETTINGS_WIFI_DEBUG_ENABLED:
3675         if (aResult === null)
3676           aResult = false;
3677         DEBUG = aResult;
3678         updateDebug();
3679         break;
3680       case SETTINGS_WIFI_TETHERING_ENABLED:
3681         this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
3682         // Fall through!
3683       case SETTINGS_WIFI_SSID:
3684       case SETTINGS_WIFI_SECURITY_TYPE:
3685       case SETTINGS_WIFI_SECURITY_PASSWORD:
3686       case SETTINGS_WIFI_IP:
3687       case SETTINGS_WIFI_PREFIX:
3688       case SETTINGS_WIFI_DHCPSERVER_STARTIP:
3689       case SETTINGS_WIFI_DHCPSERVER_ENDIP:
3690       case SETTINGS_WIFI_DNS1:
3691       case SETTINGS_WIFI_DNS2:
3692       case SETTINGS_USB_DHCPSERVER_STARTIP:
3693       case SETTINGS_USB_DHCPSERVER_ENDIP:
3694         // TODO: code related to wifi-tethering setting should be removed after GAIA
3695         //       use tethering API
3696         if (this.useTetheringAPI) {
3697           break;
3698         }
3700         if (aResult !== null) {
3701           this.tetheringSettings[aName] = aResult;
3702         }
3703         debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
3704         let index = this._wifiTetheringSettingsToRead.indexOf(aName);
3706         if (index != -1) {
3707           this._wifiTetheringSettingsToRead.splice(index, 1);
3708         }
3710         if (this._wifiTetheringSettingsToRead.length) {
3711           debug("We haven't read completely the wifi Tethering data from settings db.");
3712           break;
3713         }
3715         if (this._oldWifiTetheringEnabledState === this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
3716           debug("No changes for SETTINGS_WIFI_TETHERING_ENABLED flag. Nothing to do.");
3717           break;
3718         }
3720         if (this._oldWifiTetheringEnabledState === null &&
3721             !this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
3722           debug("Do nothing when initial settings for SETTINGS_WIFI_TETHERING_ENABLED flag is false.");
3723           break;
3724         }
3726         this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
3727         this.handleWifiTetheringEnabled(aResult)
3728         break;
3729     };
3730   },
3732   handleError: function handleError(aErrorMessage) {
3733     debug("There was an error while reading Tethering settings.");
3734     this.tetheringSettings = {};
3735     this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
3736   },
3739 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WifiWorker]);
3741 let debug;
3742 function updateDebug() {
3743   if (DEBUG) {
3744     debug = function (s) {
3745       dump("-*- WifiWorker component: " + s + "\n");
3746     };
3747   } else {
3748     debug = function (s) {};
3749   }
3750   WifiManager.syncDebug();
3752 updateDebug();