summaryrefslogtreecommitdiffstats
path: root/dom/wifi
diff options
context:
space:
mode:
Diffstat (limited to 'dom/wifi')
-rw-r--r--dom/wifi/DOMWifiManager.js543
-rw-r--r--dom/wifi/DOMWifiManager.manifest18
-rw-r--r--dom/wifi/DOMWifiP2pManager.js328
-rw-r--r--dom/wifi/DOMWifiP2pManager.manifest6
-rw-r--r--dom/wifi/StateMachine.jsm205
-rw-r--r--dom/wifi/WifiCertService.cpp536
-rw-r--r--dom/wifi/WifiCertService.h40
-rw-r--r--dom/wifi/WifiCommand.jsm594
-rw-r--r--dom/wifi/WifiHotspotUtils.cpp188
-rw-r--r--dom/wifi/WifiHotspotUtils.h46
-rw-r--r--dom/wifi/WifiNetUtil.jsm154
-rw-r--r--dom/wifi/WifiP2pManager.jsm1649
-rw-r--r--dom/wifi/WifiP2pWorkerObserver.jsm319
-rw-r--r--dom/wifi/WifiProxyService.cpp357
-rw-r--r--dom/wifi/WifiProxyService.h49
-rw-r--r--dom/wifi/WifiUtils.cpp521
-rw-r--r--dom/wifi/WifiUtils.h107
-rw-r--r--dom/wifi/WifiWorker.h9
-rw-r--r--dom/wifi/WifiWorker.js3928
-rw-r--r--dom/wifi/WifiWorker.manifest1
-rw-r--r--dom/wifi/moz.build41
-rw-r--r--dom/wifi/nsIWifi.idl60
-rw-r--r--dom/wifi/nsIWifiCertService.idl54
-rw-r--r--dom/wifi/nsIWifiService.idl22
-rw-r--r--dom/wifi/test/marionette/head.js1486
-rw-r--r--dom/wifi/test/marionette/manifest.ini22
-rw-r--r--dom/wifi/test/marionette/test_wifi_associate.js35
-rw-r--r--dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_PEAP.js623
-rw-r--r--dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_TLS.js622
-rw-r--r--dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_TTLS.js623
-rw-r--r--dom/wifi/test/marionette/test_wifi_associate_wo_connect.js55
-rw-r--r--dom/wifi/test/marionette/test_wifi_auto_connect.js44
-rw-r--r--dom/wifi/test/marionette/test_wifi_enable.js11
-rw-r--r--dom/wifi/test/marionette/test_wifi_enable_api.js13
-rw-r--r--dom/wifi/test/marionette/test_wifi_manage_pkcs12_certificate.js338
-rw-r--r--dom/wifi/test/marionette/test_wifi_manage_server_certificate.js106
-rw-r--r--dom/wifi/test/marionette/test_wifi_manage_user_certificate.js34
-rw-r--r--dom/wifi/test/marionette/test_wifi_scan.js43
-rw-r--r--dom/wifi/test/marionette/test_wifi_static_ip.js65
-rw-r--r--dom/wifi/test/marionette/test_wifi_tethering_wifi_active.js74
-rw-r--r--dom/wifi/test/marionette/test_wifi_tethering_wifi_disabled.js11
-rw-r--r--dom/wifi/test/marionette/test_wifi_tethering_wifi_inactive.js21
42 files changed, 14001 insertions, 0 deletions
diff --git a/dom/wifi/DOMWifiManager.js b/dom/wifi/DOMWifiManager.js
new file mode 100644
index 000000000..8bedfc398
--- /dev/null
+++ b/dom/wifi/DOMWifiManager.js
@@ -0,0 +1,543 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+
+const DEBUG = false; // set to false to suppress debug messages
+
+const DOMWIFIMANAGER_CONTRACTID = "@mozilla.org/wifimanager;1";
+const DOMWIFIMANAGER_CID = Components.ID("{c9b5f09e-25d2-40ca-aef4-c4d13d93c706}");
+
+function MozWifiNetwork() {
+}
+
+MozWifiNetwork.prototype = {
+
+ init: function(aWindow) {
+ this._window = aWindow;
+ },
+
+ __init: function(obj) {
+ for (let key in obj) {
+ this[key] = obj[key];
+ }
+ },
+
+ classID: Components.ID("{c01fd751-43c0-460a-8b64-abf652ec7220}"),
+ contractID: "@mozilla.org/mozwifinetwork;1",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+ Ci.nsIDOMGlobalPropertyInitializer])
+};
+
+function MozWifiConnection(obj) {
+ this.status = obj.status;
+ this.network = obj.network;
+}
+
+MozWifiConnection.prototype = {
+ classID: Components.ID("{23579da4-201b-4319-bd42-9b7f337343ac}"),
+ contractID: "@mozilla.org/mozwificonnection;1",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
+};
+
+function MozWifiConnectionInfo(obj) {
+ this.signalStrength = obj.signalStrength;
+ this.relSignalStrength = obj.relSignalStrength;
+ this.linkSpeed = obj.linkSpeed;
+ this.ipAddress = obj.ipAddress;
+}
+
+MozWifiConnectionInfo.prototype = {
+ classID: Components.ID("{83670352-6ed4-4c35-8de9-402296a1959c}"),
+ contractID: "@mozilla.org/mozwificonnectioninfo;1",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
+}
+
+function MozWifiCapabilities(obj) {
+ this.security = obj.security;
+ this.eapMethod = obj.eapMethod;
+ this.eapPhase2 = obj.eapPhase2;
+ this.certificate = obj.certificate;
+}
+
+MozWifiCapabilities.prototype = {
+ classID: Components.ID("08c88ece-8092-481b-863b-5515a52e411a"),
+ contractID: "@mozilla.org/mozwificapabilities;1",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
+}
+
+function DOMWifiManager() {
+ this.defineEventHandlerGetterSetter("onstatuschange");
+ this.defineEventHandlerGetterSetter("onconnectioninfoupdate");
+ this.defineEventHandlerGetterSetter("onenabled");
+ this.defineEventHandlerGetterSetter("ondisabled");
+ this.defineEventHandlerGetterSetter("onstationinfoupdate");
+}
+
+DOMWifiManager.prototype = {
+ __proto__: DOMRequestIpcHelper.prototype,
+ classDescription: "DOMWifiManager",
+ classID: DOMWIFIMANAGER_CID,
+ contractID: DOMWIFIMANAGER_CONTRACTID,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver]),
+
+ // nsIDOMGlobalPropertyInitializer implementation
+ init: function(aWindow) {
+ // Maintain this state for synchronous APIs.
+ this._currentNetwork = null;
+ this._connectionStatus = "disconnected";
+ this._enabled = false;
+ this._lastConnectionInfo = null;
+ this._capabilities = null;
+ this._stationNumber = 0;
+
+ const messages = ["WifiManager:getNetworks:Return:OK", "WifiManager:getNetworks:Return:NO",
+ "WifiManager:getKnownNetworks:Return:OK", "WifiManager:getKnownNetworks:Return:NO",
+ "WifiManager:associate:Return:OK", "WifiManager:associate:Return:NO",
+ "WifiManager:forget:Return:OK", "WifiManager:forget:Return:NO",
+ "WifiManager:wps:Return:OK", "WifiManager:wps:Return:NO",
+ "WifiManager:setPowerSavingMode:Return:OK", "WifiManager:setPowerSavingMode:Return:NO",
+ "WifiManager:setHttpProxy:Return:OK", "WifiManager:setHttpProxy:Return:NO",
+ "WifiManager:setStaticIpMode:Return:OK", "WifiManager:setStaticIpMode:Return:NO",
+ "WifiManager:importCert:Return:OK", "WifiManager:importCert:Return:NO",
+ "WifiManager:getImportedCerts:Return:OK", "WifiManager:getImportedCerts:Return:NO",
+ "WifiManager:deleteCert:Return:OK", "WifiManager:deleteCert:Return:NO",
+ "WifiManager:setWifiEnabled:Return:OK", "WifiManager:setWifiEnabled:Return:NO",
+ "WifiManager:wifiDown", "WifiManager:wifiUp",
+ "WifiManager:onconnecting", "WifiManager:onassociate",
+ "WifiManager:onconnect", "WifiManager:ondisconnect",
+ "WifiManager:onwpstimeout", "WifiManager:onwpsfail",
+ "WifiManager:onwpsoverlap", "WifiManager:connectioninfoupdate",
+ "WifiManager:onauthenticating", "WifiManager:onconnectingfailed",
+ "WifiManager:stationinfoupdate"];
+ this.initDOMRequestHelper(aWindow, messages);
+ this._mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
+
+ var state = this._mm.sendSyncMessage("WifiManager:getState")[0];
+ if (state) {
+ this._currentNetwork = this._convertWifiNetwork(state.network);
+ this._lastConnectionInfo = this._convertConnectionInfo(state.connectionInfo);
+ this._enabled = state.enabled;
+ this._connectionStatus = state.status;
+ this._macAddress = state.macAddress;
+ this._capabilities = this._convertWifiCapabilities(state.capabilities);
+ } else {
+ this._currentNetwork = null;
+ this._lastConnectionInfo = null;
+ this._enabled = false;
+ this._connectionStatus = "disconnected";
+ this._macAddress = "";
+ }
+ },
+
+ _convertWifiNetworkToJSON: function(aNetwork) {
+ let json = {};
+
+ for (let key in aNetwork) {
+ // In WifiWorker.js there are lots of check using "key in network".
+ // So if the value of any property of WifiNetwork is undefined, do not clone it.
+ if (aNetwork[key] != undefined) {
+ json[key] = aNetwork[key];
+ }
+ }
+ return json;
+ },
+
+ _convertWifiNetwork: function(aNetwork) {
+ let network = aNetwork ? new this._window.MozWifiNetwork(aNetwork) : null;
+ return network;
+ },
+
+ _convertWifiNetworks: function(aNetworks) {
+ let networks = new this._window.Array();
+ for (let i in aNetworks) {
+ networks.push(this._convertWifiNetwork(aNetworks[i]));
+ }
+ return networks;
+ },
+
+ _convertConnection: function(aConn) {
+ let conn = aConn ? new MozWifiConnection(aConn) : null;
+ return conn;
+ },
+
+ _convertConnectionInfo: function(aInfo) {
+ let info = aInfo ? new MozWifiConnectionInfo(aInfo) : null;
+ return info;
+ },
+
+ _convertWifiCapabilities: function(aCapabilities) {
+ let capabilities = aCapabilities ?
+ new MozWifiCapabilities(aCapabilities) : null;
+ return capabilities;
+ },
+
+ _sendMessageForRequest: function(name, data, request) {
+ let id = this.getRequestId(request);
+ this._mm.sendAsyncMessage(name, { data: data, rid: id, mid: this._id });
+ },
+
+ receiveMessage: function(aMessage) {
+ let msg = aMessage.json;
+ if (msg.mid && msg.mid != this._id)
+ return;
+
+ let request;
+ if (msg.rid) {
+ request = this.takeRequest(msg.rid);
+ if (!request) {
+ return;
+ }
+ }
+
+ switch (aMessage.name) {
+ case "WifiManager:setWifiEnabled:Return:OK":
+ Services.DOMRequest.fireSuccess(request, msg.data);
+ break;
+
+ case "WifiManager:setWifiEnabled:Return:NO":
+ Services.DOMRequest.fireError(request, "Unable to enable/disable Wifi");
+ break;
+
+ case "WifiManager:getNetworks:Return:OK":
+ Services.DOMRequest.fireSuccess(request, this._convertWifiNetworks(msg.data));
+ break;
+
+ case "WifiManager:getNetworks:Return:NO":
+ Services.DOMRequest.fireError(request, "Unable to scan for networks");
+ break;
+
+ case "WifiManager:getKnownNetworks:Return:OK":
+ Services.DOMRequest.fireSuccess(request, this._convertWifiNetworks(msg.data));
+ break;
+
+ case "WifiManager:getKnownNetworks:Return:NO":
+ Services.DOMRequest.fireError(request, "Unable to get known networks");
+ break;
+
+ case "WifiManager:associate:Return:OK":
+ Services.DOMRequest.fireSuccess(request, true);
+ break;
+
+ case "WifiManager:associate:Return:NO":
+ Services.DOMRequest.fireError(request, "Unable to add the network");
+ break;
+
+ case "WifiManager:forget:Return:OK":
+ Services.DOMRequest.fireSuccess(request, true);
+ break;
+
+ case "WifiManager:forget:Return:NO":
+ Services.DOMRequest.fireError(request, msg.data);
+ break;
+
+ case "WifiManager:wps:Return:OK":
+ Services.DOMRequest.fireSuccess(request, msg.data);
+ break;
+
+ case "WifiManager:wps:Return:NO":
+ Services.DOMRequest.fireError(request, msg.data);
+ break;
+
+ case "WifiManager:setPowerSavingMode:Return:OK":
+ Services.DOMRequest.fireSuccess(request, msg.data);
+ break;
+
+ case "WifiManager:setPowerSavingMode:Return:NO":
+ Services.DOMRequest.fireError(request, msg.data);
+ break;
+
+ case "WifiManager:setHttpProxy:Return:OK":
+ Services.DOMRequest.fireSuccess(request, msg.data);
+ break;
+
+ case "WifiManager:setHttpProxy:Return:NO":
+ Services.DOMRequest.fireError(request, msg.data);
+ break;
+
+ case "WifiManager:setStaticIpMode:Return:OK":
+ Services.DOMRequest.fireSuccess(request, msg.data);
+ break;
+
+ case "WifiManager:setStaticIpMode:Return:NO":
+ Services.DOMRequest.fireError(request, msg.data);
+ break;
+
+ case "WifiManager:importCert:Return:OK":
+ Services.DOMRequest.fireSuccess(request, Cu.cloneInto(msg.data, this._window));
+ break;
+
+ case "WifiManager:importCert:Return:NO":
+ Services.DOMRequest.fireError(request, msg.data);
+ break;
+
+ case "WifiManager:getImportedCerts:Return:OK":
+ Services.DOMRequest.fireSuccess(request, Cu.cloneInto(msg.data, this._window));
+ break;
+
+ case "WifiManager:getImportedCerts:Return:NO":
+ Services.DOMRequest.fireError(request, msg.data);
+ break;
+
+ case "WifiManager:deleteCert:Return:OK":
+ Services.DOMRequest.fireSuccess(request, msg.data);
+ break;
+
+ case "WifiManager:deleteCert:Return:NO":
+ Services.DOMRequest.fireError(request, msg.data);
+ break;
+
+ case "WifiManager:wifiDown":
+ this._enabled = false;
+ this._currentNetwork = null;
+ this._fireEnabledOrDisabled(false);
+ break;
+
+ case "WifiManager:wifiUp":
+ this._enabled = true;
+ this._macAddress = msg.macAddress;
+ this._fireEnabledOrDisabled(true);
+ break;
+
+ case "WifiManager:onconnecting":
+ this._currentNetwork = this._convertWifiNetwork(msg.network);
+ this._connectionStatus = "connecting";
+ this._fireStatusChangeEvent(msg.network);
+ break;
+
+ case "WifiManager:onassociate":
+ this._currentNetwork = this._convertWifiNetwork(msg.network);
+ this._connectionStatus = "associated";
+ this._fireStatusChangeEvent(msg.network);
+ break;
+
+ case "WifiManager:onconnect":
+ this._currentNetwork = this._convertWifiNetwork(msg.network);
+ this._connectionStatus = "connected";
+ this._fireStatusChangeEvent(msg.network);
+ break;
+
+ case "WifiManager:ondisconnect":
+ this._currentNetwork = null;
+ this._connectionStatus = "disconnected";
+ this._lastConnectionInfo = null;
+ this._fireStatusChangeEvent(msg.network);
+ break;
+
+ case "WifiManager:onwpstimeout":
+ this._currentNetwork = null;
+ this._connectionStatus = "wps-timedout";
+ this._lastConnectionInfo = null;
+ this._fireStatusChangeEvent(msg.network);
+ break;
+
+ case "WifiManager:onwpsfail":
+ this._currentNetwork = null;
+ this._connectionStatus = "wps-failed";
+ this._lastConnectionInfo = null;
+ this._fireStatusChangeEvent(msg.network);
+ break;
+
+ case "WifiManager:onwpsoverlap":
+ this._currentNetwork = null;
+ this._connectionStatus = "wps-overlapped";
+ this._lastConnectionInfo = null;
+ this._fireStatusChangeEvent(msg.network);
+ break;
+
+ case "WifiManager:connectioninfoupdate":
+ this._lastConnectionInfo = this._convertConnectionInfo(msg);
+ this._fireConnectionInfoUpdate(msg);
+ break;
+ case "WifiManager:onconnectingfailed":
+ this._currentNetwork = null;
+ this._connectionStatus = "connectingfailed";
+ this._lastConnectionInfo = null;
+ this._fireStatusChangeEvent(msg.network);
+ break;
+ case "WifiManager:onauthenticating":
+ this._currentNetwork = this._convertWifiNetwork(msg.network);
+ this._connectionStatus = "authenticating";
+ this._fireStatusChangeEvent(msg.network);
+ break;
+ case "WifiManager:stationinfoupdate":
+ this._stationNumber = msg.station;
+ this._fireStationInfoUpdate(msg);
+ break;
+ }
+ },
+
+ _fireStatusChangeEvent: function StatusChangeEvent(aNetwork) {
+ var event = new this._window.MozWifiStatusChangeEvent("statuschange",
+ { network: this._convertWifiNetwork(aNetwork),
+ status: this._connectionStatus
+ });
+ this.__DOM_IMPL__.dispatchEvent(event);
+ },
+
+ _fireConnectionInfoUpdate: function onConnectionInfoUpdate(info) {
+ var evt = new this._window.MozWifiConnectionInfoEvent("connectioninfoupdate",
+ { network: this._currentNetwork,
+ signalStrength: info.signalStrength,
+ relSignalStrength: info.relSignalStrength,
+ linkSpeed: info.linkSpeed,
+ ipAddress: info.ipAddress,
+ });
+ this.__DOM_IMPL__.dispatchEvent(evt);
+ },
+
+ _fireEnabledOrDisabled: function enabledDisabled(enabled) {
+ var evt = new this._window.Event(enabled ? "enabled" : "disabled");
+ this.__DOM_IMPL__.dispatchEvent(evt);
+ },
+
+ _fireStationInfoUpdate: function onStationInfoUpdate(info) {
+ var evt = new this._window.MozWifiStationInfoEvent("stationinfoupdate",
+ { station: this._stationNumber}
+ );
+ this.__DOM_IMPL__.dispatchEvent(evt);
+ },
+
+ setWifiEnabled: function setWifiEnabled(enabled) {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:setWifiEnabled", enabled, request);
+ return request;
+ },
+
+ getNetworks: function getNetworks() {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:getNetworks", null, request);
+ return request;
+ },
+
+ getKnownNetworks: function getKnownNetworks() {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:getKnownNetworks", null, request);
+ return request;
+ },
+
+ associate: function associate(network) {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:associate",
+ this._convertWifiNetworkToJSON(network), request);
+ return request;
+ },
+
+ forget: function forget(network) {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:forget",
+ this._convertWifiNetworkToJSON(network), request);
+ return request;
+ },
+
+ wps: function wps(detail) {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:wps", detail, request);
+ return request;
+ },
+
+ setPowerSavingMode: function setPowerSavingMode(enabled) {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:setPowerSavingMode", enabled, request);
+ return request;
+ },
+
+ setHttpProxy: function setHttpProxy(network, info) {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:setHttpProxy",
+ { network: this._convertWifiNetworkToJSON(network), info:info}, request);
+ return request;
+ },
+
+ setStaticIpMode: function setStaticIpMode(network, info) {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:setStaticIpMode",
+ { network: this._convertWifiNetworkToJSON(network), info: info}, request);
+ return request;
+ },
+
+ importCert: function nsIDOMWifiManager_importCert(certBlob, certPassword, certNickname) {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:importCert",
+ {
+ certBlob: certBlob,
+ certPassword: certPassword,
+ certNickname: certNickname
+ }, request);
+ return request;
+ },
+
+ getImportedCerts: function nsIDOMWifiManager_getImportedCerts() {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:getImportedCerts", null, request);
+ return request;
+ },
+
+ deleteCert: function nsIDOMWifiManager_deleteCert(certNickname) {
+ var request = this.createRequest();
+ this._sendMessageForRequest("WifiManager:deleteCert",
+ {
+ certNickname: certNickname
+ }, request);
+ return request;
+ },
+
+ get enabled() {
+ return this._enabled;
+ },
+
+ get macAddress() {
+ return this._macAddress;
+ },
+
+ get connection() {
+ let _connection = this._convertConnection({ status: this._connectionStatus,
+ network: this._currentNetwork,
+ });
+ return _connection;
+ },
+
+ get connectionInformation() {
+ return this._lastConnectionInfo;
+ },
+
+ get capabilities() {
+ return this._capabilities;
+ },
+
+ defineEventHandlerGetterSetter: function(name) {
+ Object.defineProperty(this, name, {
+ get: function() {
+ return this.__DOM_IMPL__.getEventHandler(name);
+ },
+ set: function(handler) {
+ this.__DOM_IMPL__.setEventHandler(name, handler);
+ }
+ });
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
+ DOMWifiManager, MozWifiNetwork, MozWifiConnection, MozWifiCapabilities,
+ MozWifiConnectionInfo
+]);
+
+var debug;
+if (DEBUG) {
+ debug = function (s) {
+ dump("-*- DOMWifiManager component: " + s + "\n");
+ };
+} else {
+ debug = function (s) {};
+}
diff --git a/dom/wifi/DOMWifiManager.manifest b/dom/wifi/DOMWifiManager.manifest
new file mode 100644
index 000000000..3ba83c1b2
--- /dev/null
+++ b/dom/wifi/DOMWifiManager.manifest
@@ -0,0 +1,18 @@
+# DOMWifiManager.js
+component {c9b5f09e-25d2-40ca-aef4-c4d13d93c706} DOMWifiManager.js
+contract @mozilla.org/wifimanager;1 {c9b5f09e-25d2-40ca-aef4-c4d13d93c706}
+
+component {c01fd751-43c0-460a-8b64-abf652ec7220} DOMWifiManager.js
+contract @mozilla.org/mozwifinetwork;1 {c01fd751-43c0-460a-8b64-abf652ec7220}
+
+component {23579da4-201b-4319-bd42-9b7f337343ac} DOMWifiManager.js
+contract @mozilla.org/mozwificonnection;1 {23579da4-201b-4319-bd42-9b7f337343ac}
+
+component {83670352-6ed4-4c35-8de9-402296a1959c} DOMWifiManager.js
+contract @mozilla.org/mozwificonnectioninfo;1 {83670352-6ed4-4c35-8de9-402296a1959c}
+
+component {ad5c5295-85fb-4460-8e0c-e130d3f029ab} DOMWifiManager.js
+contract @mozilla.org/mozwificertificateinfo;1 {ad5c5295-85fb-4460-8e0c-e130d3f029ab}
+
+component {08c88ece-8092-481b-863b-5515a52e411a} DOMWifiManager.js
+contract @mozilla.org/mozwificapabilities;1 {08c88ece-8092-481b-863b-5515a52e411a}
diff --git a/dom/wifi/DOMWifiP2pManager.js b/dom/wifi/DOMWifiP2pManager.js
new file mode 100644
index 000000000..3a5637521
--- /dev/null
+++ b/dom/wifi/DOMWifiP2pManager.js
@@ -0,0 +1,328 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+
+const DEBUG = false;
+
+// interface MozWifiP2pGroupOwner implementation.
+
+function MozWifiP2pGroupOwner(aGo) {
+ this.groupName = aGo.groupName;
+ this.macAddress = aGo.macAddress;
+ this.ipAddress = aGo.ipAddress;
+ this.passphrase = aGo.passphrase;
+ this.ssid = aGo.ssid;
+ this.wpsCapabilities = aGo.wpsCapabilities;
+ this.freq = aGo.freq;
+ this.isLocal = aGo.isLocal;
+}
+
+MozWifiP2pGroupOwner.prototype = {
+ classID: Components.ID("{a9b81450-349d-11e3-aa6e-0800200c9a66}"),
+ contractID: "@mozilla.org/wifip2pgroupowner;1",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
+};
+
+// interface MozWifiP2pManager implementation.
+
+const MOZ_WIFIP2PMANAGER_CONTRACTID = "@mozilla.org/wifip2pmanager;1";
+const MOZ_WIFIP2PMANAGER_CID = Components.ID("{8d9125a0-3498-11e3-aa6e-0800200c9a66}");
+
+function MozWifiP2pManager() {
+ this.defineEventHandlerGetterSetter("onstatuschange");
+ this.defineEventHandlerGetterSetter("onpeerinfoupdate");
+ this.defineEventHandlerGetterSetter("onenabled");
+ this.defineEventHandlerGetterSetter("ondisabled");
+
+ this.currentPeer = null;
+ this.enabled = false;
+ this.groupOwner = null;
+}
+
+// For smaller, read-only APIs, we expose any property that doesn't begin with
+// an underscore.
+function exposeReadOnly(obj) {
+ let exposedProps = {};
+ for (let i in obj) {
+ if (i[0] === "_") {
+ continue;
+ }
+ exposedProps[i] = "r";
+ }
+
+ obj.__exposedProps__ = exposedProps;
+ return obj;
+}
+
+function debug(msg) {
+ if (DEBUG) {
+ dump('-------------- MozWifiP2pManager: ' + msg);
+ }
+}
+
+MozWifiP2pManager.prototype = {
+ __proto__: DOMRequestIpcHelper.prototype,
+
+ classID: MOZ_WIFIP2PMANAGER_CID,
+ contractID: MOZ_WIFIP2PMANAGER_CONTRACTID,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver,
+ Ci.nsISupports]),
+
+ //
+ // nsIDOMGlobalPropertyInitializer implementation.
+ //
+
+ init: function(aWindow) {
+ const messages = ["WifiP2pManager:setScanEnabled:Return:OK",
+ "WifiP2pManager:setScanEnabled:Return:NO",
+ "WifiP2pManager:getPeerList:Return:OK",
+ "WifiP2pManager:getPeerList:Return:NO",
+ "WifiP2pManager:connect:Return:OK",
+ "WifiP2pManager:connect:Return:NO",
+ "WifiP2pManager:disconnect:Return:OK",
+ "WifiP2pManager:disconnect:Return:NO",
+ "WifiP2pManager:setPairingConfirmation:Return",
+ "WifiP2pManager:setDeviceName:Return:OK",
+ "WifiP2pManager:setDeviceName:Return:NO",
+
+ "WifiP2pManager:p2pDown",
+ "WifiP2pManager:p2pUp",
+ "WifiP2pManager:onconnecting",
+ "WifiP2pManager:onconnected",
+ "WifiP2pManager:ondisconnected",
+ "WifiP2pManager:ongroupnstop",
+ "WifiP2pManager:onconnectingfailed",
+ "WifiP2pManager:onwpstimeout",
+ "WifiP2pManager:onwpsfail",
+ "WifiP2pManager:onpeerinfoupdate",
+ ];
+
+ this.initDOMRequestHelper(aWindow, messages);
+ this._mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
+
+ // Notify the internal a new DOM mananger is created.
+ let state = this._mm.sendSyncMessage("WifiP2pManager:getState")[0];
+ if (state) {
+ debug('State: ' + JSON.stringify(state));
+ this.enabled = state.enabled;
+ this.currentPeer = state.currentPeer;
+ if (state.groupOwner) {
+ this.groupOwner = new MozWifiP2pGroupOwner(state.groupOwner);
+ }
+ } else {
+ debug('Failed to get state');
+ }
+ },
+
+ uninit: function() {
+ },
+
+ _sendMessageForRequest: function(name, data, request) {
+ let id = this.getRequestId(request);
+ this._mm.sendAsyncMessage(name, { data: data, rid: id, mid: this._id });
+ },
+
+ receiveMessage: function(aMessage) {
+ let msg = aMessage.json;
+ if (msg.mid && msg.mid !== this._id) {
+ return;
+ }
+
+ let request;
+ switch (aMessage.name) {
+ case "WifiP2pManager:setScanEnabled:Return:OK":
+ request = this.takeRequest(msg.rid);
+ Services.DOMRequest.fireSuccess(request, exposeReadOnly(msg.data));
+ break;
+
+ case "WifiP2pManager:setScanEnabled:Return:NO":
+ request = this.takeRequest(msg.rid);
+ Services.DOMRequest.fireError(request, "Unable to enable/disable Wifi P2P peer discovery.");
+ break;
+
+ case "WifiP2pManager:getPeerList:Return:OK":
+ request = this.takeRequest(msg.rid);
+ Services.DOMRequest.fireSuccess(request, Cu.cloneInto(msg.data, this._window));
+ break;
+
+ case "WifiP2pManager:getPeerList:Return:NO":
+ request = this.takeRequest(msg.rid);
+ Services.DOMRequest.fireError(request, "Unable to disable Wifi P2P peer discovery.");
+ break;
+
+ case "WifiP2pManager:connect:Return:OK":
+ request = this.takeRequest(msg.rid);
+ Services.DOMRequest.fireSuccess(request, exposeReadOnly(msg.data));
+ break;
+
+ case "WifiP2pManager:connect:Return:NO":
+ request = this.takeRequest(msg.rid);
+ Services.DOMRequest.fireError(request, "Unable to connect to Wifi P2P peer.");
+ break;
+
+ case "WifiP2pManager:disconnect:Return:OK":
+ request = this.takeRequest(msg.rid);
+ Services.DOMRequest.fireSuccess(request, exposeReadOnly(msg.data));
+ break;
+
+ case "WifiP2pManager:disconnect:Return:NO":
+ request = this.takeRequest(msg.rid);
+ Services.DOMRequest.fireError(request, "Unable to disconnect to Wifi P2P peer.");
+ break;
+
+ case "WifiP2pManager:setDeviceName:Return:OK":
+ request = this.takeRequest(msg.rid);
+ Services.DOMRequest.fireSuccess(request, exposeReadOnly(msg.data));
+ break;
+
+ case "WifiP2pManager:setDeviceName:Return:NO":
+ request = this.takeRequest(msg.rid);
+ Services.DOMRequest.fireError(request, "Unable to set device name.");
+ break;
+
+ case "WifiP2pManager:p2pDown":
+ this.enabled = false;
+ this.currentPeer = null;
+ this._fireEnabledOrDisabled(false);
+ break;
+
+ case "WifiP2pManager:p2pUp":
+ this.enabled = true;
+ this._fireEnabledOrDisabled(true);
+ break;
+
+ case "WifiP2pManager:onconnecting":
+ debug('onconnecting with peer: ' + JSON.stringify(msg.peer));
+ this.currentPeer = msg.peer;
+ this._fireStatusChangeEvent(msg.peer.address);
+ break;
+
+ case "WifiP2pManager:onconnected":
+ debug('onconnected with peer: ' + JSON.stringify(msg.peer));
+ this.currentPeer = msg.peer;
+ this.groupOwner = new MozWifiP2pGroupOwner(msg.groupOwner);
+ this._fireStatusChangeEvent(msg.peer.address);
+ break;
+
+ case "WifiP2pManager:ondisconnected":
+ debug('ondisconnected with peer: ' + JSON.stringify(msg.peer));
+ this.currentPeer = null;
+ this.groupOwner = null;
+ this._fireStatusChangeEvent(msg.peer.address);
+ break;
+
+ case "WifiP2pManager:onconnectingfailed":
+ this._fireStatusChangeEvent(null);
+ break;
+
+ case "WifiP2pManager:onwpstimeout":
+ this._fireStatusChangeEvent(null);
+ break;
+
+ case "WifiP2pManager:onwpsfail":
+ this._fireStatusChangeEvent(null);
+ break;
+
+ case "WifiP2pManager:onpeerinfoupdate":
+ this._firePeerInfoUpdateEvent();
+ break;
+ }
+ },
+
+ _firePeerInfoUpdateEvent: function PeerInfoUpdate() {
+ let evt = new this._window.Event("peerinfoupdate");
+ this.__DOM_IMPL__.dispatchEvent(evt);
+ },
+
+ _fireStatusChangeEvent: function WifiP2pStatusChange(peerAddress) {
+ let evt = new this._window.MozWifiP2pStatusChangeEvent("statuschange",
+ { peerAddress: peerAddress });
+ this.__DOM_IMPL__.dispatchEvent(evt);
+ },
+
+ _fireEnabledOrDisabled: function enabledDisabled(enabled) {
+ let evt = new this._window.Event(enabled ? "enabled" : "disabled");
+ this.__DOM_IMPL__.dispatchEvent(evt);
+ },
+
+ //
+ // WifiP2pManager.webidl implementation.
+ //
+
+ enableScan: function () {
+ let request = this.createRequest();
+ this._sendMessageForRequest("WifiP2pManager:enableScan", null, request);
+ return request;
+ },
+
+ disableScan: function () {
+ let request = this.createRequest();
+ this._sendMessageForRequest("WifiP2pManager:disableScan", null, request);
+ return request;
+ },
+
+ setScanEnabled: function(enabled) {
+ let request = this.createRequest();
+ this._sendMessageForRequest("WifiP2pManager:setScanEnabled", enabled, request);
+ return request;
+ },
+
+ connect: function (address, wpsMethod, goIntent) {
+ let request = this.createRequest();
+ let connectionInfo = { address: address, wpsMethod: wpsMethod, goIntent: goIntent };
+ this._sendMessageForRequest("WifiP2pManager:connect", connectionInfo, request);
+ return request;
+ },
+
+ disconnect: function (address) {
+ let request = this.createRequest();
+ this._sendMessageForRequest("WifiP2pManager:disconnect", address, request);
+ return request;
+ },
+
+ getPeerList: function () {
+ let request = this.createRequest();
+ this._sendMessageForRequest("WifiP2pManager:getPeerList", null, request);
+ return request;
+ },
+
+ setPairingConfirmation: function (accepted, pin) {
+ let request = this.createRequest();
+ let result = { accepted: accepted, pin: pin };
+ this._sendMessageForRequest("WifiP2pManager:setPairingConfirmation", result, request);
+ return request;
+ },
+
+ setDeviceName: function(newDeviceName) {
+ let request = this.createRequest();
+ this._sendMessageForRequest("WifiP2pManager:setDeviceName", newDeviceName, request);
+ return request;
+ },
+
+ // Helpers.
+ defineEventHandlerGetterSetter: function(event) {
+ Object.defineProperty(this, event, {
+ get: function() {
+ return this.__DOM_IMPL__.getEventHandler(event);
+ },
+
+ set: function(handler) {
+ this.__DOM_IMPL__.setEventHandler(event, handler);
+ }
+ });
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MozWifiP2pManager, MozWifiP2pGroupOwner]);
diff --git a/dom/wifi/DOMWifiP2pManager.manifest b/dom/wifi/DOMWifiP2pManager.manifest
new file mode 100644
index 000000000..bc80efa48
--- /dev/null
+++ b/dom/wifi/DOMWifiP2pManager.manifest
@@ -0,0 +1,6 @@
+# DOMWifiP2pManager.js
+component {8d9125a0-3498-11e3-aa6e-0800200c9a66} DOMWifiP2pManager.js
+contract @mozilla.org/wifip2pmanager;1 {8d9125a0-3498-11e3-aa6e-0800200c9a66}
+
+component {a9b81450-349d-11e3-aa6e-0800200c9a66} DOMWifiP2pManager.js
+contract @mozilla.org/wifip2pgroupowner;1 {a9b81450-349d-11e3-aa6e-0800200c9a66} \ No newline at end of file
diff --git a/dom/wifi/StateMachine.jsm b/dom/wifi/StateMachine.jsm
new file mode 100644
index 000000000..94b876f82
--- /dev/null
+++ b/dom/wifi/StateMachine.jsm
@@ -0,0 +1,205 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+this.EXPORTED_SYMBOLS = ["StateMachine"];
+
+const DEBUG = false;
+
+this.StateMachine = function(aDebugTag) {
+ function debug(aMsg) {
+ dump('-------------- StateMachine:' + aDebugTag + ': ' + aMsg);
+ }
+
+ var sm = {};
+
+ var _initialState;
+ var _curState;
+ var _prevState;
+ var _paused;
+ var _eventQueue = [];
+ var _deferredEventQueue = [];
+ var _defaultEventHandler;
+
+ // Public interfaces.
+
+ sm.setDefaultEventHandler = function(aDefaultEventHandler) {
+ _defaultEventHandler = aDefaultEventHandler;
+ };
+
+ sm.start = function(aInitialState) {
+ _initialState = aInitialState;
+ sm.gotoState(_initialState);
+ };
+
+ sm.sendEvent = function (aEvent) {
+ if (!_initialState) {
+ if (DEBUG) {
+ debug('StateMachine is not running. Call StateMachine.start() first.');
+ }
+ return;
+ }
+ _eventQueue.push(aEvent);
+ asyncCall(handleFirstEvent);
+ };
+
+ sm.getPreviousState = function() {
+ return _prevState;
+ };
+
+ sm.getCurrentState = function() {
+ return _curState;
+ };
+
+ // State object maker.
+ // @param aName string for this state's name.
+ // @param aDelegate object:
+ // .handleEvent: required.
+ // .enter: called before entering this state (optional).
+ // .exit: called before exiting this state (optional).
+ sm.makeState = function (aName, aDelegate) {
+ if (!aDelegate.handleEvent) {
+ throw "handleEvent is a required delegate function.";
+ }
+ var nop = function() {};
+ return {
+ name: aName,
+ enter: (aDelegate.enter || nop),
+ exit: (aDelegate.exit || nop),
+ handleEvent: aDelegate.handleEvent
+ };
+ };
+
+ sm.deferEvent = function (aEvent) {
+ // The definition of a 'deferred event' is:
+ // We are not able to handle this event now but after receiving
+ // certain event or entering a new state, we might be able to handle
+ // it. For example, we couldn't handle CONNECT_EVENT in the
+ // diconnecting state. But once we finish doing "disconnecting", we
+ // could then handle CONNECT_EVENT!
+ //
+ // So, the deferred event may be handled in the following cases:
+ // 1. Once we entered a new state.
+ // 2. Once we handled a regular event.
+ if (DEBUG) {
+ debug('Deferring event: ' + JSON.stringify(aEvent));
+ }
+ _deferredEventQueue.push(aEvent);
+ };
+
+ // Goto the new state. If the current state is null, the exit
+ // function won't be called.
+ sm.gotoState = function (aNewState) {
+ if (_curState) {
+ if (DEBUG) {
+ debug("exiting state: " + _curState.name);
+ }
+ _curState.exit();
+ }
+
+ _prevState = _curState;
+ _curState = aNewState;
+
+ if (DEBUG) {
+ debug("entering state: " + _curState.name);
+ }
+ _curState.enter();
+
+ // We are in the new state now. We got a chance to handle the
+ // deferred events.
+ handleDeferredEvents();
+
+ sm.resume();
+ };
+
+ // No incoming event will be handled after you call pause().
+ // (But they will be queued.)
+ sm.pause = function() {
+ _paused = true;
+ };
+
+ // Continue to handle incoming events.
+ sm.resume = function() {
+ _paused = false;
+ asyncCall(handleFirstEvent);
+ };
+
+ //----------------------------------------------------------
+ // Private stuff
+ //----------------------------------------------------------
+
+ function asyncCall(f) {
+ Services.tm.currentThread.dispatch(f, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+
+ function handleFirstEvent() {
+ var hadDeferredEvents;
+
+ if (0 === _eventQueue.length) {
+ return;
+ }
+
+ if (_paused) {
+ return; // The state machine is paused now.
+ }
+
+ hadDeferredEvents = _deferredEventQueue.length > 0;
+
+ handleOneEvent(_eventQueue.shift()); // The handler may defer this event.
+
+ // We've handled one event. If we had deferred events before, now is
+ // a good chance to handle them.
+ if (hadDeferredEvents) {
+ handleDeferredEvents();
+ }
+
+ // Continue to handle the next regular event.
+ handleFirstEvent();
+ }
+
+ function handleDeferredEvents() {
+ if (_deferredEventQueue.length && DEBUG) {
+ debug('Handle deferred events: ' + _deferredEventQueue.length);
+ }
+ for (let i = 0; i < _deferredEventQueue.length; i++) {
+ handleOneEvent(_deferredEventQueue.shift());
+ }
+ }
+
+ function handleOneEvent(aEvent)
+ {
+ if (DEBUG) {
+ debug('Handling event: ' + JSON.stringify(aEvent));
+ }
+
+ var handled = _curState.handleEvent(aEvent);
+
+ if (undefined === handled) {
+ throw "handleEvent returns undefined: " + _curState.name;
+ }
+ if (!handled) {
+ // Event is not handled in the current state. Try handleEventCommon().
+ handled = (_defaultEventHandler ? _defaultEventHandler(aEvent) : handled);
+ }
+ if (undefined === handled) {
+ throw "handleEventCommon returns undefined: " + _curState.name;
+ }
+ if (!handled) {
+ if (DEBUG) {
+ debug('!!!!!!!!! FIXME !!!!!!!!! Event not handled: ' + JSON.stringify(aEvent));
+ }
+ }
+
+ return handled;
+ }
+
+ return sm;
+};
diff --git a/dom/wifi/WifiCertService.cpp b/dom/wifi/WifiCertService.cpp
new file mode 100644
index 000000000..85e0d2f5b
--- /dev/null
+++ b/dom/wifi/WifiCertService.cpp
@@ -0,0 +1,536 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WifiCertService.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "cert.h"
+#include "certdb.h"
+#include "CryptoTask.h"
+#include "nsIDOMBlob.h"
+#include "nsIWifiService.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXULAppAPI.h"
+#include "ScopedNSSTypes.h"
+
+#define NS_WIFICERTSERVICE_CID \
+ { 0x83585afd, 0x0e11, 0x43aa, {0x83, 0x46, 0xf3, 0x4d, 0x97, 0x5e, 0x46, 0x77} }
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+// The singleton Wifi Cert service, to be used on the main thread.
+StaticRefPtr<WifiCertService> gWifiCertService;
+
+class ImportCertTask final: public CryptoTask
+{
+public:
+ ImportCertTask(int32_t aId, Blob* aCertBlob,
+ const nsAString& aCertPassword,
+ const nsAString& aCertNickname)
+ : mBlob(aCertBlob)
+ , mPassword(aCertPassword)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mResult.mId = aId;
+ mResult.mStatus = 0;
+ mResult.mUsageFlag = 0;
+ mResult.mNickname = aCertNickname;
+ }
+
+private:
+ virtual void ReleaseNSSResources() {}
+
+ virtual nsresult CalculateResult() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // read data from blob.
+ nsCString blobBuf;
+ nsresult rv = ReadBlob(blobBuf);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ char* buf;
+ uint32_t size = blobBuf.GetMutableData(&buf);
+ if (size == 0) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Try import as DER format first.
+ rv = ImportDERBlob(buf, size);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ // Try import as PKCS#12 format.
+ return ImportPKCS12Blob(buf, size, mPassword);
+ }
+
+ virtual void CallCallback(nsresult rv)
+ {
+ if (NS_FAILED(rv)) {
+ mResult.mStatus = -1;
+ }
+ gWifiCertService->DispatchResult(mResult);
+ }
+
+ nsresult ImportDERBlob(char* buf, uint32_t size)
+ {
+ // Create certificate object.
+ ScopedCERTCertificate cert(CERT_DecodeCertFromPackage(buf, size));
+ if (!cert) {
+ return MapSECStatus(SECFailure);
+ }
+
+ // Import certificate.
+ return ImportCert(cert);
+ }
+
+ static SECItem*
+ HandleNicknameCollision(SECItem* aOldNickname, PRBool* aCancel, void* aWincx)
+ {
+ const char* dummyName = "Imported User Cert";
+ const size_t dummyNameLen = strlen(dummyName);
+ SECItem* newNick = ::SECITEM_AllocItem(nullptr, nullptr, dummyNameLen + 1);
+ if (!newNick) {
+ return nullptr;
+ }
+
+ newNick->type = siAsciiString;
+ // Dummy name, will be renamed later.
+ memcpy(newNick->data, dummyName, dummyNameLen + 1);
+ newNick->len = dummyNameLen;
+
+ return newNick;
+ }
+
+ static SECStatus
+ HandleNicknameUpdate(const CERTCertificate *aCert,
+ const SECItem *default_nickname,
+ SECItem **new_nickname,
+ void *arg)
+ {
+ WifiCertServiceResultOptions *result = (WifiCertServiceResultOptions *)arg;
+
+ nsCString userNickname;
+ CopyUTF16toUTF8(result->mNickname, userNickname);
+
+ nsCString fullNickname;
+ if (aCert->isRoot && (aCert->nsCertType & NS_CERT_TYPE_SSL_CA)) {
+ // Accept self-signed SSL CA as server certificate.
+ fullNickname.AssignLiteral("WIFI_SERVERCERT_");
+ fullNickname += userNickname;
+ result->mUsageFlag |= nsIWifiCertService::WIFI_CERT_USAGE_FLAG_SERVER;
+ } else if (aCert->nsCertType & NS_CERT_TYPE_SSL_CLIENT) {
+ // User Certificate
+ fullNickname.AssignLiteral("WIFI_USERCERT_");
+ fullNickname += userNickname;
+ result->mUsageFlag |= nsIWifiCertService::WIFI_CERT_USAGE_FLAG_USER;
+ }
+ char* nickname;
+ uint32_t length = fullNickname.GetMutableData(&nickname);
+
+ SECItem* newNick = ::SECITEM_AllocItem(nullptr, nullptr, length + 1);
+ if (!newNick) {
+ return SECFailure;
+ }
+
+ newNick->type = siAsciiString;
+ memcpy(newNick->data, nickname, length + 1);
+ newNick->len = length;
+
+ *new_nickname = newNick;
+ return SECSuccess;
+ }
+
+ nsresult ImportPKCS12Blob(char* buf, uint32_t size, const nsAString& aPassword)
+ {
+ nsString password(aPassword);
+
+ // password is null-terminated wide-char string.
+ // passwordItem is required to be big-endian form of password, stored in char
+ // array, including the null-termination.
+ uint32_t length = password.Length() + 1;
+ ScopedSECItem passwordItem(
+ ::SECITEM_AllocItem(nullptr, nullptr, length * sizeof(nsString::char_type)));
+
+ if (!passwordItem) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mozilla::NativeEndian::copyAndSwapToBigEndian(passwordItem->data,
+ password.BeginReading(),
+ length);
+ // Create a decoder.
+ ScopedSEC_PKCS12DecoderContext p12dcx(SEC_PKCS12DecoderStart(
+ passwordItem, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr));
+
+ if (!p12dcx) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Assign data to decorder.
+ SECStatus srv = SEC_PKCS12DecoderUpdate(p12dcx,
+ reinterpret_cast<unsigned char*>(buf),
+ size);
+ if (srv != SECSuccess) {
+ return MapSECStatus(srv);
+ }
+
+ // Verify certificates.
+ srv = SEC_PKCS12DecoderVerify(p12dcx);
+ if (srv != SECSuccess) {
+ return MapSECStatus(srv);
+ }
+
+ // Set certificate nickname and usage flag.
+ srv = SEC_PKCS12DecoderRenameCertNicknames(p12dcx, HandleNicknameUpdate,
+ &mResult);
+
+ // Validate certificates.
+ srv = SEC_PKCS12DecoderValidateBags(p12dcx, HandleNicknameCollision);
+ if (srv != SECSuccess) {
+ return MapSECStatus(srv);
+ }
+
+ // Initialize slot.
+ ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
+ if (!slot) {
+ return NS_ERROR_FAILURE;
+ }
+ if (PK11_NeedLogin(slot) && PK11_NeedUserInit(slot)) {
+ srv = PK11_InitPin(slot, "", "");
+ if (srv != SECSuccess) {
+ return MapSECStatus(srv);
+ }
+ }
+
+ // Import cert and key.
+ srv = SEC_PKCS12DecoderImportBags(p12dcx);
+ if (srv != SECSuccess) {
+ return MapSECStatus(srv);
+ }
+
+ // User certificate must be imported from PKCS#12.
+ return (mResult.mUsageFlag & nsIWifiCertService::WIFI_CERT_USAGE_FLAG_USER)
+ ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ nsresult ReadBlob(/*out*/ nsCString& aBuf)
+ {
+ NS_ENSURE_ARG_POINTER(mBlob);
+
+ static const uint64_t MAX_FILE_SIZE = 16384;
+
+ ErrorResult rv;
+ uint64_t size = mBlob->GetSize(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ if (size > MAX_FILE_SIZE) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ mBlob->GetInternalStream(getter_AddRefs(inputStream), rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ rv = NS_ReadInputStreamToString(inputStream, aBuf, (uint32_t)size);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ return NS_OK;
+ }
+
+ nsresult ImportCert(CERTCertificate* aCert)
+ {
+ nsCString userNickname, fullNickname;
+
+ CopyUTF16toUTF8(mResult.mNickname, userNickname);
+ // Determine certificate nickname by adding prefix according to its type.
+ if (aCert->isRoot && (aCert->nsCertType & NS_CERT_TYPE_SSL_CA)) {
+ // Accept self-signed SSL CA as server certificate.
+ fullNickname.AssignLiteral("WIFI_SERVERCERT_");
+ fullNickname += userNickname;
+ mResult.mUsageFlag |= nsIWifiCertService::WIFI_CERT_USAGE_FLAG_SERVER;
+ } else if (aCert->nsCertType & NS_CERT_TYPE_SSL_CLIENT) {
+ // User Certificate
+ fullNickname.AssignLiteral("WIFI_USERCERT_");
+ fullNickname += userNickname;
+ mResult.mUsageFlag |= nsIWifiCertService::WIFI_CERT_USAGE_FLAG_USER;
+ } else {
+ return NS_ERROR_ABORT;
+ }
+
+ char* nickname;
+ uint32_t length;
+ length = fullNickname.GetMutableData(&nickname);
+ if (length == 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Import certificate, duplicated nickname will cause error.
+ SECStatus srv = CERT_AddTempCertToPerm(aCert, nickname, nullptr);
+ if (srv != SECSuccess) {
+ return MapSECStatus(srv);
+ }
+
+ return NS_OK;
+ }
+
+ RefPtr<Blob> mBlob;
+ nsString mPassword;
+ WifiCertServiceResultOptions mResult;
+};
+
+class DeleteCertTask final: public CryptoTask
+{
+public:
+ DeleteCertTask(int32_t aId, const nsAString& aCertNickname)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mResult.mId = aId;
+ mResult.mStatus = 0;
+ mResult.mUsageFlag = 0;
+ mResult.mNickname = aCertNickname;
+ }
+
+private:
+ virtual void ReleaseNSSResources() {}
+
+ virtual nsresult CalculateResult() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCString userNickname;
+ CopyUTF16toUTF8(mResult.mNickname, userNickname);
+
+ // Delete server certificate.
+ nsCString serverCertName("WIFI_SERVERCERT_", 16);
+ serverCertName += userNickname;
+ nsresult rv = deleteCert(serverCertName);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Delete user certificate and private key.
+ nsCString userCertName("WIFI_USERCERT_", 14);
+ userCertName += userNickname;
+ rv = deleteCert(userCertName);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ nsresult deleteCert(const nsCString &aCertNickname)
+ {
+ ScopedCERTCertificate cert(
+ CERT_FindCertByNickname(CERT_GetDefaultCertDB(), aCertNickname.get())
+ );
+ // Because we delete certificates in blind, so it's acceptable to delete
+ // a non-exist certificate.
+ if (!cert) {
+ return NS_OK;
+ }
+
+ ScopedPK11SlotInfo slot(
+ PK11_KeyForCertExists(cert, nullptr, nullptr)
+ );
+
+ SECStatus srv;
+ if (slot) {
+ // Delete private key along with certificate.
+ srv = PK11_DeleteTokenCertAndKey(cert, nullptr);
+ } else {
+ srv = SEC_DeletePermCertificate(cert);
+ }
+
+ if (srv != SECSuccess) {
+ return MapSECStatus(srv);
+ }
+
+ return NS_OK;
+ }
+
+ virtual void CallCallback(nsresult rv)
+ {
+ if (NS_FAILED(rv)) {
+ mResult.mStatus = -1;
+ }
+ gWifiCertService->DispatchResult(mResult);
+ }
+
+ WifiCertServiceResultOptions mResult;
+};
+
+NS_IMPL_ISUPPORTS(WifiCertService, nsIWifiCertService)
+
+NS_IMETHODIMP
+WifiCertService::Start(nsIWifiEventListener* aListener)
+{
+ MOZ_ASSERT(aListener);
+
+ mListener = aListener;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WifiCertService::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mListener = nullptr;
+
+ return NS_OK;
+}
+
+void
+WifiCertService::DispatchResult(const WifiCertServiceResultOptions& aOptions)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mozilla::AutoSafeJSContext cx;
+ JS::RootedValue val(cx);
+ nsCString dummyInterface;
+
+ if (!ToJSValue(cx, aOptions, &val)) {
+ return;
+ }
+
+ // Certll the listener with a JS value.
+ mListener->OnCommand(val, dummyInterface);
+}
+
+WifiCertService::WifiCertService()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!gWifiCertService);
+}
+
+WifiCertService::~WifiCertService()
+{
+ MOZ_ASSERT(!gWifiCertService);
+
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+already_AddRefed<WifiCertService>
+WifiCertService::FactoryCreate()
+{
+ if (!XRE_IsParentProcess()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gWifiCertService) {
+ gWifiCertService = new WifiCertService();
+ ClearOnShutdown(&gWifiCertService);
+ }
+
+ RefPtr<WifiCertService> service = gWifiCertService.get();
+ return service.forget();
+}
+
+NS_IMETHODIMP
+WifiCertService::ImportCert(int32_t aId, nsIDOMBlob* aCertBlob,
+ const nsAString& aCertPassword,
+ const nsAString& aCertNickname)
+{
+ RefPtr<Blob> blob = static_cast<Blob*>(aCertBlob);
+ RefPtr<CryptoTask> task = new ImportCertTask(aId, blob, aCertPassword,
+ aCertNickname);
+ return task->Dispatch("WifiImportCert");
+}
+
+NS_IMETHODIMP
+WifiCertService::DeleteCert(int32_t aId, const nsAString& aCertNickname)
+{
+ RefPtr<CryptoTask> task = new DeleteCertTask(aId, aCertNickname);
+ return task->Dispatch("WifiDeleteCert");
+}
+
+NS_IMETHODIMP
+WifiCertService::HasPrivateKey(const nsAString& aCertNickname, bool *aHasKey)
+{
+ *aHasKey = false;
+
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCString certNickname;
+ CopyUTF16toUTF8(aCertNickname, certNickname);
+
+ ScopedCERTCertificate cert(
+ CERT_FindCertByNickname(CERT_GetDefaultCertDB(), certNickname.get())
+ );
+ if (!cert) {
+ return NS_OK;
+ }
+
+ ScopedPK11SlotInfo slot(
+ PK11_KeyForCertExists(cert, nullptr, nullptr)
+ );
+ if (slot) {
+ *aHasKey = true;
+ }
+
+ return NS_OK;
+}
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WifiCertService,
+ WifiCertService::FactoryCreate)
+
+NS_DEFINE_NAMED_CID(NS_WIFICERTSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kWifiCertServiceCIDs[] = {
+ { &kNS_WIFICERTSERVICE_CID, false, nullptr, WifiCertServiceConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWifiCertServiceContracts[] = {
+ { "@mozilla.org/wifi/certservice;1", &kNS_WIFICERTSERVICE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kWifiCertServiceModule = {
+ mozilla::Module::kVersion,
+ kWifiCertServiceCIDs,
+ kWifiCertServiceContracts,
+ nullptr
+};
+
+} // namespace mozilla
+
+NSMODULE_DEFN(WifiCertServiceModule) = &kWifiCertServiceModule;
diff --git a/dom/wifi/WifiCertService.h b/dom/wifi/WifiCertService.h
new file mode 100644
index 000000000..f542bb0c8
--- /dev/null
+++ b/dom/wifi/WifiCertService.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WifiCertService_h
+#define WifiCertService_h
+
+#include "nsIWifiCertService.h"
+#include "nsCOMPtr.h"
+#include "nsNSSShutDown.h"
+#include "nsThread.h"
+#include "mozilla/dom/WifiOptionsBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class WifiCertService final : public nsIWifiCertService,
+ public nsNSSShutDownObject
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWIFICERTSERVICE
+
+ static already_AddRefed<WifiCertService>
+ FactoryCreate();
+ void DispatchResult(const mozilla::dom::WifiCertServiceResultOptions& aOptions);
+
+private:
+ WifiCertService();
+ ~WifiCertService();
+ virtual void virtualDestroyNSSReference() {};
+ nsCOMPtr<nsIWifiEventListener> mListener;
+};
+
+} // namespce dom
+} // namespace mozilla
+
+#endif // WifiCertService_h
diff --git a/dom/wifi/WifiCommand.jsm b/dom/wifi/WifiCommand.jsm
new file mode 100644
index 000000000..93b0f1a1e
--- /dev/null
+++ b/dom/wifi/WifiCommand.jsm
@@ -0,0 +1,594 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["WifiCommand"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/systemlibs.js");
+
+const SUPP_PROP = "init.svc.wpa_supplicant";
+const WPA_SUPPLICANT = "wpa_supplicant";
+const DEBUG = false;
+
+this.WifiCommand = function(aControlMessage, aInterface, aSdkVersion) {
+ function debug(msg) {
+ if (DEBUG) {
+ dump('-------------- WifiCommand: ' + msg);
+ }
+ }
+
+ var command = {};
+
+ //-------------------------------------------------
+ // Utilities.
+ //-------------------------------------------------
+ command.getSdkVersion = function() {
+ return aSdkVersion;
+ };
+
+ //-------------------------------------------------
+ // General commands.
+ //-------------------------------------------------
+
+ command.loadDriver = function (callback) {
+ voidControlMessage("load_driver", function(status) {
+ callback(status);
+ });
+ };
+
+ command.unloadDriver = function (callback) {
+ voidControlMessage("unload_driver", function(status) {
+ callback(status);
+ });
+ };
+
+ command.startSupplicant = function (callback) {
+ voidControlMessage("start_supplicant", callback);
+ };
+
+ command.killSupplicant = function (callback) {
+ // It is interesting to note that this function does exactly what
+ // wifi_stop_supplicant does. Unforunately, on the Galaxy S2, Samsung
+ // changed that function in a way that means that it doesn't recognize
+ // wpa_supplicant as already running. Therefore, we have to roll our own
+ // version here.
+ stopProcess(SUPP_PROP, WPA_SUPPLICANT, callback);
+ };
+
+ command.terminateSupplicant = function (callback) {
+ doBooleanCommand("TERMINATE", "OK", callback);
+ };
+
+ command.stopSupplicant = function (callback) {
+ voidControlMessage("stop_supplicant", callback);
+ };
+
+ command.listNetworks = function (callback) {
+ doStringCommand("LIST_NETWORKS", callback);
+ };
+
+ command.addNetwork = function (callback) {
+ doIntCommand("ADD_NETWORK", callback);
+ };
+
+ command.setNetworkVariable = function (netId, name, value, callback) {
+ doBooleanCommand("SET_NETWORK " + netId + " " + name + " " +
+ value, "OK", callback);
+ };
+
+ command.getNetworkVariable = function (netId, name, callback) {
+ doStringCommand("GET_NETWORK " + netId + " " + name, callback);
+ };
+
+ command.removeNetwork = function (netId, callback) {
+ doBooleanCommand("REMOVE_NETWORK " + netId, "OK", callback);
+ };
+
+ command.enableNetwork = function (netId, disableOthers, callback) {
+ doBooleanCommand((disableOthers ? "SELECT_NETWORK " : "ENABLE_NETWORK ") +
+ netId, "OK", callback);
+ };
+
+ command.disableNetwork = function (netId, callback) {
+ doBooleanCommand("DISABLE_NETWORK " + netId, "OK", callback);
+ };
+
+ command.status = function (callback) {
+ doStringCommand("STATUS", callback);
+ };
+
+ command.ping = function (callback) {
+ doBooleanCommand("PING", "PONG", callback);
+ };
+
+ command.scanResults = function (callback) {
+ doStringCommand("SCAN_RESULTS", callback);
+ };
+
+ command.disconnect = function (callback) {
+ doBooleanCommand("DISCONNECT", "OK", callback);
+ };
+
+ command.reconnect = function (callback) {
+ doBooleanCommand("RECONNECT", "OK", callback);
+ };
+
+ command.reassociate = function (callback) {
+ doBooleanCommand("REASSOCIATE", "OK", callback);
+ };
+
+ command.setBackgroundScan = function (enable, callback) {
+ doBooleanCommand("SET pno " + (enable ? "1" : "0"),
+ "OK",
+ function(ok) {
+ callback(true, ok);
+ });
+ };
+
+ command.doSetScanMode = function (setActive, callback) {
+ doBooleanCommand(setActive ?
+ "DRIVER SCAN-ACTIVE" :
+ "DRIVER SCAN-PASSIVE", "OK", callback);
+ };
+
+ command.scan = function (callback) {
+ doBooleanCommand("SCAN", "OK", callback);
+ };
+
+ command.setLogLevel = function (level, callback) {
+ doBooleanCommand("LOG_LEVEL " + level, "OK", callback);
+ };
+
+ command.getLogLevel = function (callback) {
+ doStringCommand("LOG_LEVEL", callback);
+ };
+
+ command.wpsPbc = function (callback, iface) {
+ let cmd = 'WPS_PBC';
+
+ // If the network interface is specified and we are based on JB,
+ // append the argument 'interface=[iface]' to the supplicant command.
+ //
+ // Note: The argument "interface" is only required for wifi p2p on JB.
+ // For other cases, the argument is useless and even leads error.
+ // Check the evil work here:
+ // http://androidxref.com/4.2.2_r1/xref/external/wpa_supplicant_8/wpa_supplicant/ctrl_iface_unix.c#172
+ //
+ if (iface && isJellybean()) {
+ cmd += (' inferface=' + iface);
+ }
+
+ doBooleanCommand(cmd, "OK", callback);
+ };
+
+ command.wpsPin = function (detail, callback) {
+ let cmd = 'WPS_PIN ';
+
+ // See the comment above in wpsPbc().
+ if (detail.iface && isJellybean()) {
+ cmd += ('inferface=' + iface + ' ');
+ }
+
+ cmd += (detail.bssid === undefined ? "any" : detail.bssid);
+ cmd += (detail.pin === undefined ? "" : (" " + detail.pin));
+
+ doStringCommand(cmd, callback);
+ };
+
+ command.wpsCancel = function (callback) {
+ doBooleanCommand("WPS_CANCEL", "OK", callback);
+ };
+
+ command.startDriver = function (callback) {
+ doBooleanCommand("DRIVER START", "OK");
+ };
+
+ command.stopDriver = function (callback) {
+ doBooleanCommand("DRIVER STOP", "OK");
+ };
+
+ command.startPacketFiltering = function (callback) {
+ var commandChain = ["DRIVER RXFILTER-ADD 0",
+ "DRIVER RXFILTER-ADD 1",
+ "DRIVER RXFILTER-ADD 3",
+ "DRIVER RXFILTER-START"];
+
+ doBooleanCommandChain(commandChain, callback);
+ };
+
+ command.stopPacketFiltering = function (callback) {
+ var commandChain = ["DRIVER RXFILTER-STOP",
+ "DRIVER RXFILTER-REMOVE 3",
+ "DRIVER RXFILTER-REMOVE 1",
+ "DRIVER RXFILTER-REMOVE 0"];
+
+ doBooleanCommandChain(commandChain, callback);
+ };
+
+ command.doGetRssi = function (cmd, callback) {
+ doCommand(cmd, function(data) {
+ var rssi = -200;
+
+ if (!data.status) {
+ // If we are associating, the reply is "OK".
+ var reply = data.reply;
+ if (reply !== "OK") {
+ // Format is: <SSID> rssi XX". SSID can contain spaces.
+ var offset = reply.lastIndexOf("rssi ");
+ if (offset !== -1) {
+ rssi = reply.substr(offset + 5) | 0;
+ }
+ }
+ }
+ callback(rssi);
+ });
+ };
+
+ command.getRssi = function (callback) {
+ command.doGetRssi("DRIVER RSSI", callback);
+ };
+
+ command.getRssiApprox = function (callback) {
+ command.doGetRssi("DRIVER RSSI-APPROX", callback);
+ };
+
+ command.getLinkSpeed = function (callback) {
+ doStringCommand("DRIVER LINKSPEED", function(reply) {
+ if (reply) {
+ reply = reply.split(" ")[1] | 0; // Format: LinkSpeed XX
+ }
+ callback(reply);
+ });
+ };
+
+ let infoKeys = [{regexp: /RSSI=/i, prop: 'rssi'},
+ {regexp: /LINKSPEED=/i, prop: 'linkspeed'}];
+
+ command.getConnectionInfoICS = function (callback) {
+ doStringCommand("SIGNAL_POLL", function(reply) {
+ if (!reply) {
+ callback(null);
+ return;
+ }
+
+ // Find any values matching |infoKeys|. This gets executed frequently
+ // enough that we want to avoid creating intermediate strings as much as
+ // possible.
+ let rval = {};
+ for (let i = 0; i < infoKeys.length; i++) {
+ let re = infoKeys[i].regexp;
+ let iKeyStart = reply.search(re);
+ if (iKeyStart !== -1) {
+ let prop = infoKeys[i].prop;
+ let iValueStart = reply.indexOf('=', iKeyStart) + 1;
+ let iNewlineAfterValue = reply.indexOf('\n', iValueStart);
+ let iValueEnd = iNewlineAfterValue !== -1
+ ? iNewlineAfterValue
+ : reply.length;
+ rval[prop] = reply.substring(iValueStart, iValueEnd) | 0;
+ }
+ }
+
+ callback(rval);
+ });
+ };
+
+ command.getMacAddress = function (callback) {
+ doStringCommand("DRIVER MACADDR", function(reply) {
+ if (reply) {
+ reply = reply.split(" ")[2]; // Format: Macaddr = XX.XX.XX.XX.XX.XX
+ }
+ callback(reply);
+ });
+ };
+
+ command.connectToHostapd = function(callback) {
+ voidControlMessage("connect_to_hostapd", callback);
+ };
+
+ command.closeHostapdConnection = function(callback) {
+ voidControlMessage("close_hostapd_connection", callback);
+ };
+
+ command.hostapdCommand = function (callback, request) {
+ var msg = { cmd: "hostapd_command",
+ request: request,
+ iface: aInterface };
+
+ aControlMessage(msg, function(data) {
+ callback(data.status ? null : data.reply);
+ });
+ };
+
+ command.hostapdGetStations = function (callback) {
+ var msg = { cmd: "hostapd_get_stations",
+ iface: aInterface };
+
+ aControlMessage(msg, function(data) {
+ callback(data.status);
+ });
+ };
+
+ command.setPowerModeICS = function (mode, callback) {
+ doBooleanCommand("DRIVER POWERMODE " + (mode === "AUTO" ? 0 : 1), "OK", callback);
+ };
+
+ command.setPowerModeJB = function (mode, callback) {
+ doBooleanCommand("SET ps " + (mode === "AUTO" ? 1 : 0), "OK", callback);
+ };
+
+ command.getPowerMode = function (callback) {
+ doStringCommand("DRIVER GETPOWER", function(reply) {
+ if (reply) {
+ reply = (reply.split()[2]|0); // Format: powermode = XX
+ }
+ callback(reply);
+ });
+ };
+
+ command.setNumAllowedChannels = function (numChannels, callback) {
+ doBooleanCommand("DRIVER SCAN-CHANNELS " + numChannels, "OK", callback);
+ };
+
+ command.getNumAllowedChannels = function (callback) {
+ doStringCommand("DRIVER SCAN-CHANNELS", function(reply) {
+ if (reply) {
+ reply = (reply.split()[2]|0); // Format: Scan-Channels = X
+ }
+ callback(reply);
+ });
+ };
+
+ command.setBluetoothCoexistenceMode = function (mode, callback) {
+ doBooleanCommand("DRIVER BTCOEXMODE " + mode, "OK", callback);
+ };
+
+ command.setBluetoothCoexistenceScanMode = function (mode, callback) {
+ doBooleanCommand("DRIVER BTCOEXSCAN-" + (mode ? "START" : "STOP"),
+ "OK", callback);
+ };
+
+ command.saveConfig = function (callback) {
+ // Make sure we never write out a value for AP_SCAN other than 1.
+ doBooleanCommand("AP_SCAN 1", "OK", function(ok) {
+ doBooleanCommand("SAVE_CONFIG", "OK", callback);
+ });
+ };
+
+ command.reloadConfig = function (callback) {
+ doBooleanCommand("RECONFIGURE", "OK", callback);
+ };
+
+ command.setScanResultHandling = function (mode, callback) {
+ doBooleanCommand("AP_SCAN " + mode, "OK", callback);
+ };
+
+ command.addToBlacklist = function (bssid, callback) {
+ doBooleanCommand("BLACKLIST " + bssid, "OK", callback);
+ };
+
+ command.clearBlacklist = function (callback) {
+ doBooleanCommand("BLACKLIST clear", "OK", callback);
+ };
+
+ command.setSuspendOptimizationsICS = function (enabled, callback) {
+ doBooleanCommand("DRIVER SETSUSPENDOPT " + (enabled ? 0 : 1),
+ "OK", callback);
+ };
+
+ command.setSuspendOptimizationsJB = function (enabled, callback) {
+ doBooleanCommand("DRIVER SETSUSPENDMODE " + (enabled ? 1 : 0),
+ "OK", callback);
+ };
+
+ command.connectToSupplicant = function(callback) {
+ voidControlMessage("connect_to_supplicant", callback);
+ };
+
+ command.closeSupplicantConnection = function(callback) {
+ voidControlMessage("close_supplicant_connection", callback);
+ };
+
+ command.getMacAddress = function(callback) {
+ doStringCommand("DRIVER MACADDR", function(reply) {
+ if (reply) {
+ reply = reply.split(" ")[2]; // Format: Macaddr = XX.XX.XX.XX.XX.XX
+ }
+ callback(reply);
+ });
+ };
+
+ command.setDeviceName = function(deviceName, callback) {
+ doBooleanCommand("SET device_name " + deviceName, "OK", callback);
+ };
+
+ //-------------------------------------------------
+ // P2P commands.
+ //-------------------------------------------------
+
+ command.p2pProvDiscovery = function(address, wpsMethod, callback) {
+ var command = "P2P_PROV_DISC " + address + " " + wpsMethod;
+ doBooleanCommand(command, "OK", callback);
+ };
+
+ command.p2pConnect = function(config, callback) {
+ var command = "P2P_CONNECT " + config.address + " " + config.wpsMethodWithPin + " ";
+ if (config.joinExistingGroup) {
+ command += "join";
+ } else {
+ command += "go_intent=" + config.goIntent;
+ }
+
+ debug('P2P connect command: ' + command);
+ doBooleanCommand(command, "OK", callback);
+ };
+
+ command.p2pGroupRemove = function(iface, callback) {
+ debug("groupRemove()");
+ doBooleanCommand("P2P_GROUP_REMOVE " + iface, "OK", callback);
+ };
+
+ command.p2pEnable = function(detail, callback) {
+ var commandChain = ["SET device_name " + detail.deviceName,
+ "SET device_type " + detail.deviceType,
+ "SET config_methods " + detail.wpsMethods,
+ "P2P_SET conc_pref sta",
+ "P2P_FLUSH"];
+
+ doBooleanCommandChain(commandChain, callback);
+ };
+
+ command.p2pDisable = function(callback) {
+ doBooleanCommand("P2P_SET disabled 1", "OK", callback);
+ };
+
+ command.p2pEnableScan = function(timeout, callback) {
+ doBooleanCommand("P2P_FIND " + timeout, "OK", callback);
+ };
+
+ command.p2pDisableScan = function(callback) {
+ doBooleanCommand("P2P_STOP_FIND", "OK", callback);
+ };
+
+ command.p2pGetGroupCapab = function(address, callback) {
+ command.p2pPeer(address, function(reply) {
+ debug('p2p_peer reply: ' + reply);
+ if (!reply) {
+ callback(0);
+ return;
+ }
+ var capab = /group_capab=0x([0-9a-fA-F]+)/.exec(reply)[1];
+ if (!capab) {
+ callback(0);
+ } else {
+ callback(parseInt(capab, 16));
+ }
+ });
+ };
+
+ command.p2pPeer = function(address, callback) {
+ doStringCommand("P2P_PEER " + address, callback);
+ };
+
+ command.p2pGroupAdd = function(netId, callback) {
+ doBooleanCommand("P2P_GROUP_ADD persistent=" + netId, callback);
+ };
+
+ command.p2pReinvoke = function(netId, address, callback) {
+ doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + address, "OK", callback);
+ };
+
+ //----------------------------------------------------------
+ // Private stuff.
+ //----------------------------------------------------------
+
+ function voidControlMessage(cmd, callback) {
+ aControlMessage({ cmd: cmd, iface: aInterface }, function (data) {
+ callback(data.status);
+ });
+ }
+
+ function doCommand(request, callback) {
+ var msg = { cmd: "command",
+ request: request,
+ iface: aInterface };
+
+ aControlMessage(msg, callback);
+ }
+
+ function doIntCommand(request, callback) {
+ doCommand(request, function(data) {
+ callback(data.status ? -1 : (data.reply|0));
+ });
+ }
+
+ function doBooleanCommand(request, expected, callback) {
+ doCommand(request, function(data) {
+ callback(data.status ? false : (data.reply === expected));
+ });
+ }
+
+ function doStringCommand(request, callback) {
+ doCommand(request, function(data) {
+ callback(data.status ? null : data.reply);
+ });
+ }
+
+ function doBooleanCommandChain(commandChain, callback, i) {
+ if (undefined === i) {
+ i = 0;
+ }
+
+ doBooleanCommand(commandChain[i], "OK", function(ok) {
+ if (!ok) {
+ return callback(false);
+ }
+ i++;
+ if (i === commandChain.length || !commandChain[i]) {
+ // Reach the end or empty command.
+ return callback(true);
+ }
+ doBooleanCommandChain(commandChain, callback, i);
+ });
+ }
+
+ //--------------------------------------------------
+ // Helper functions.
+ //--------------------------------------------------
+
+ function stopProcess(service, process, callback) {
+ var count = 0;
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ function tick() {
+ let result = libcutils.property_get(service);
+ if (result === null) {
+ callback();
+ return;
+ }
+ if (result === "stopped" || ++count >= 5) {
+ // Either we succeeded or ran out of time.
+ timer = null;
+ callback();
+ return;
+ }
+
+ // Else it's still running, continue waiting.
+ timer.initWithCallback(tick, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+
+ setProperty("ctl.stop", process, tick);
+ }
+
+ // Wrapper around libcutils.property_set that returns true if setting the
+ // value was successful.
+ // Note that the callback is not called asynchronously.
+ function setProperty(key, value, callback) {
+ let ok = true;
+ try {
+ libcutils.property_set(key, value);
+ } catch(e) {
+ ok = false;
+ }
+ callback(ok);
+ }
+
+ function isJellybean() {
+ // According to http://developer.android.com/guide/topics/manifest/uses-sdk-element.html
+ // ----------------------------------------------------
+ // | Platform Version | API Level | VERSION_CODE |
+ // ----------------------------------------------------
+ // | Android 4.1, 4.1.1 | 16 | JELLY_BEAN_MR2 |
+ // | Android 4.2, 4.2.2 | 17 | JELLY_BEAN_MR1 |
+ // | Android 4.3 | 18 | JELLY_BEAN |
+ // ----------------------------------------------------
+ return aSdkVersion === 16 || aSdkVersion === 17 || aSdkVersion === 18;
+ }
+
+ return command;
+};
diff --git a/dom/wifi/WifiHotspotUtils.cpp b/dom/wifi/WifiHotspotUtils.cpp
new file mode 100644
index 000000000..5fdb9b76e
--- /dev/null
+++ b/dom/wifi/WifiHotspotUtils.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WifiHotspotUtils.h"
+#include <dlfcn.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <cutils/properties.h>
+
+#include "prinit.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Sprintf.h"
+#include "nsDebug.h"
+#include "nsPrintfCString.h"
+
+static void* sWifiHotspotUtilsLib;
+static PRCallOnceType sInitWifiHotspotUtilsLib;
+// Socket pair used to exit from a blocking read.
+static struct wpa_ctrl* ctrl_conn;
+static const char *ctrl_iface_dir = "/data/misc/wifi/hostapd";
+static char *ctrl_ifname = nullptr;
+
+DEFINE_DLFUNC(wpa_ctrl_open, struct wpa_ctrl*, const char*)
+DEFINE_DLFUNC(wpa_ctrl_close, void, struct wpa_ctrl*)
+DEFINE_DLFUNC(wpa_ctrl_attach, int32_t, struct wpa_ctrl*)
+DEFINE_DLFUNC(wpa_ctrl_detach, int32_t, struct wpa_ctrl*)
+DEFINE_DLFUNC(wpa_ctrl_request, int32_t, struct wpa_ctrl*,
+ const char*, size_t cmd_len, char *reply,
+ size_t *reply_len, void (*msg_cb)(char *msg, size_t len))
+
+
+static PRStatus
+InitWifiHotspotUtilsLib()
+{
+ sWifiHotspotUtilsLib = dlopen("/system/lib/libwpa_client.so", RTLD_LAZY);
+ // We might fail to open the hardware lib. That's OK.
+ return PR_SUCCESS;
+}
+
+static void*
+GetWifiHotspotLibHandle()
+{
+ PR_CallOnce(&sInitWifiHotspotUtilsLib, InitWifiHotspotUtilsLib);
+ return sWifiHotspotUtilsLib;
+}
+
+struct wpa_ctrl *
+WifiHotspotUtils::openConnection(const char *ifname)
+{
+ if (!ifname) {
+ return nullptr;
+ }
+
+ USE_DLFUNC(wpa_ctrl_open)
+ ctrl_conn = wpa_ctrl_open(nsPrintfCString("%s/%s", ctrl_iface_dir, ifname).get());
+ return ctrl_conn;
+}
+
+int32_t
+WifiHotspotUtils::sendCommand(struct wpa_ctrl *ctrl, const char *cmd,
+ char *reply, size_t *reply_len)
+{
+ int32_t ret;
+
+ if (!ctrl_conn) {
+ NS_WARNING(nsPrintfCString("Not connected to hostapd - \"%s\" command dropped.\n", cmd).get());
+ return -1;
+ }
+
+ USE_DLFUNC(wpa_ctrl_request)
+ ret = wpa_ctrl_request(ctrl, cmd, strlen(cmd), reply, reply_len, nullptr);
+ if (ret == -2) {
+ NS_WARNING(nsPrintfCString("'%s' command timed out.\n", cmd).get());
+ return -2;
+ } else if (ret < 0 || strncmp(reply, "FAIL", 4) == 0) {
+ return -1;
+ }
+
+ // Make the reply printable.
+ reply[*reply_len] = '\0';
+ if (strncmp(cmd, "STA-FIRST", 9) == 0 ||
+ strncmp(cmd, "STA-NEXT", 8) == 0) {
+ char *pos = reply;
+
+ while (*pos && *pos != '\n')
+ pos++;
+ *pos = '\0';
+ }
+
+ return 0;
+}
+
+
+// static
+void*
+WifiHotspotUtils::GetSharedLibrary()
+{
+ void* wpaClientLib = GetWifiHotspotLibHandle();
+ if (!wpaClientLib) {
+ NS_WARNING("No /system/lib/libwpa_client.so");
+ }
+ return wpaClientLib;
+}
+
+int32_t WifiHotspotUtils::do_wifi_connect_to_hostapd()
+{
+ struct dirent *dent;
+
+ DIR *dir = opendir(ctrl_iface_dir);
+ if (dir) {
+ while ((dent = readdir(dir))) {
+ if (strcmp(dent->d_name, ".") == 0 ||
+ strcmp(dent->d_name, "..") == 0) {
+ continue;
+ }
+ ctrl_ifname = strdup(dent->d_name);
+ break;
+ }
+ closedir(dir);
+ }
+
+ ctrl_conn = openConnection(ctrl_ifname);
+ if (!ctrl_conn) {
+ NS_WARNING(nsPrintfCString("Unable to open connection to hostapd on \"%s\": %s",
+ ctrl_ifname, strerror(errno)).get());
+ return -1;
+ }
+
+ USE_DLFUNC(wpa_ctrl_attach)
+ if (wpa_ctrl_attach(ctrl_conn) != 0) {
+ USE_DLFUNC(wpa_ctrl_close)
+ wpa_ctrl_close(ctrl_conn);
+ ctrl_conn = nullptr;
+ return -1;
+ }
+
+ return 0;
+}
+
+int32_t WifiHotspotUtils::do_wifi_close_hostapd_connection()
+{
+ if (!ctrl_conn) {
+ NS_WARNING("Invalid ctrl_conn.");
+ return -1;
+ }
+
+ USE_DLFUNC(wpa_ctrl_detach)
+ if (wpa_ctrl_detach(ctrl_conn) < 0) {
+ NS_WARNING("Failed to detach wpa_ctrl.");
+ }
+
+ USE_DLFUNC(wpa_ctrl_close)
+ wpa_ctrl_close(ctrl_conn);
+ ctrl_conn = nullptr;
+ return 0;
+}
+
+int32_t WifiHotspotUtils::do_wifi_hostapd_command(const char *command,
+ char *reply,
+ size_t *reply_len)
+{
+ return sendCommand(ctrl_conn, command, reply, reply_len);
+}
+
+int32_t WifiHotspotUtils::do_wifi_hostapd_get_stations()
+{
+ char addr[32], cmd[64];
+ int stations = 0;
+ size_t addrLen = sizeof(addr);
+
+ if (sendCommand(ctrl_conn, "STA-FIRST", addr, &addrLen)) {
+ return 0;
+ }
+ stations++;
+
+ SprintfLiteral(cmd, "STA-NEXT %s", addr);
+ while (sendCommand(ctrl_conn, cmd, addr, &addrLen) == 0) {
+ stations++;
+ SprintfLiteral(cmd, "STA-NEXT %s", addr);
+ }
+
+ return stations;
+}
diff --git a/dom/wifi/WifiHotspotUtils.h b/dom/wifi/WifiHotspotUtils.h
new file mode 100644
index 000000000..7afdec7da
--- /dev/null
+++ b/dom/wifi/WifiHotspotUtils.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Abstraction on top of the network support from libnetutils that we
+ * use to set up network connections.
+ */
+
+#ifndef WifiHotspotUtils_h
+#define WifiHotspotUtils_h
+
+// Forward declaration.
+struct wpa_ctrl;
+
+class WifiHotspotUtils
+{
+public:
+ static void* GetSharedLibrary();
+
+ int32_t do_wifi_connect_to_hostapd();
+ int32_t do_wifi_close_hostapd_connection();
+ int32_t do_wifi_hostapd_command(const char *command,
+ char *reply,
+ size_t *reply_len);
+ int32_t do_wifi_hostapd_get_stations();
+
+private:
+ struct wpa_ctrl * openConnection(const char *ifname);
+ int32_t sendCommand(struct wpa_ctrl *ctrl, const char *cmd,
+ char *reply, size_t *reply_len);
+};
+
+// Defines a function type with the right arguments and return type.
+#define DEFINE_DLFUNC(name, ret, args...) typedef ret (*FUNC##name)(args);
+
+// Set up a dlsymed function ready to use.
+#define USE_DLFUNC(name) \
+ FUNC##name name = (FUNC##name) dlsym(GetSharedLibrary(), #name); \
+ if (!name) { \
+ MOZ_CRASH("Symbol not found in shared library : " #name); \
+ }
+
+#endif // WifiHotspotUtils_h
diff --git a/dom/wifi/WifiNetUtil.jsm b/dom/wifi/WifiNetUtil.jsm
new file mode 100644
index 000000000..2a1866515
--- /dev/null
+++ b/dom/wifi/WifiNetUtil.jsm
@@ -0,0 +1,154 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+ "@mozilla.org/network/service;1",
+ "nsINetworkService");
+
+this.EXPORTED_SYMBOLS = ["WifiNetUtil"];
+
+const DHCP_PROP = "init.svc.dhcpcd";
+const DHCP = "dhcpcd";
+const DEBUG = false;
+
+this.WifiNetUtil = function(controlMessage) {
+ function debug(msg) {
+ if (DEBUG) {
+ dump('-------------- NetUtil: ' + msg);
+ }
+ }
+
+ var util = {};
+
+ util.runDhcp = function (ifname, gen, callback) {
+ util.stopDhcp(ifname, function() {
+ gNetworkService.dhcpRequest(ifname, function(success, dhcpInfo) {
+ util.runIpConfig(ifname, dhcpInfo, function(data) {
+ callback(data, gen);
+ });
+ });
+ });
+ };
+
+ util.stopDhcp = function (ifname, callback) {
+ // This function does exactly what dhcp_stop does. Unforunately, if we call
+ // this function twice before the previous callback is returned. We may block
+ // our self waiting for the callback. It slows down the wifi startup procedure.
+ // Therefore, we have to roll our own version here.
+ let dhcpService = DHCP_PROP + "_" + ifname;
+ let suffix = (ifname.substr(0, 3) === "p2p") ? "p2p" : ifname;
+ let processName = DHCP + "_" + suffix;
+
+ // The implementation of |dhcp_do_request| would wait until the
+ // |result_prop_name| (e.g. dhcp.wlan0.result) to be non-null
+ // or 30 second timeout. So we manually change the result property
+ // to 'ko' to avoid timeout.
+ //
+ // http://androidxref.com/4.4.4_r1/xref/system/core/libnetutils/dhcp_utils.c#234
+ setProperty('dhcp.' + suffix + '.result', 'ko', function() {
+ stopProcess(dhcpService, processName, callback);
+ });
+ };
+
+ util.startDhcpServer = function (config, callback) {
+ gNetworkService.setDhcpServer(true, config, function (error) {
+ callback(!error);
+ });
+ };
+
+ util.stopDhcpServer = function (callback) {
+ gNetworkService.setDhcpServer(false, null, function (error) {
+ callback(!error);
+ });
+ };
+
+ util.runIpConfig = function (name, data, callback) {
+ if (!data) {
+ debug("IP config failed to run");
+ callback({ info: data });
+ return;
+ }
+
+ setProperty("net." + name + ".dns1", ipToString(data.dns1),
+ function(ok) {
+ if (!ok) {
+ debug("Unable to set net.<ifname>.dns1");
+ return;
+ }
+ setProperty("net." + name + ".dns2", ipToString(data.dns2),
+ function(ok) {
+ if (!ok) {
+ debug("Unable to set net.<ifname>.dns2");
+ return;
+ }
+ setProperty("net." + name + ".gw", ipToString(data.gateway),
+ function(ok) {
+ if (!ok) {
+ debug("Unable to set net.<ifname>.gw");
+ return;
+ }
+ callback({ info: data });
+ });
+ });
+ });
+ };
+
+ //--------------------------------------------------
+ // Helper functions.
+ //--------------------------------------------------
+
+ function stopProcess(service, process, callback) {
+ var count = 0;
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ function tick() {
+ let result = libcutils.property_get(service);
+ if (result === null) {
+ callback();
+ return;
+ }
+ if (result === "stopped" || ++count >= 5) {
+ // Either we succeeded or ran out of time.
+ timer = null;
+ callback();
+ return;
+ }
+
+ // Else it's still running, continue waiting.
+ timer.initWithCallback(tick, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+
+ setProperty("ctl.stop", process, tick);
+ }
+
+ // Wrapper around libcutils.property_set that returns true if setting the
+ // value was successful.
+ // Note that the callback is not called asynchronously.
+ function setProperty(key, value, callback) {
+ let ok = true;
+ try {
+ libcutils.property_set(key, value);
+ } catch(e) {
+ ok = false;
+ }
+ callback(ok);
+ }
+
+ function ipToString(n) {
+ return String((n >> 0) & 0xFF) + "." +
+ ((n >> 8) & 0xFF) + "." +
+ ((n >> 16) & 0xFF) + "." +
+ ((n >> 24) & 0xFF);
+ }
+
+ return util;
+};
diff --git a/dom/wifi/WifiP2pManager.jsm b/dom/wifi/WifiP2pManager.jsm
new file mode 100644
index 000000000..c1b687438
--- /dev/null
+++ b/dom/wifi/WifiP2pManager.jsm
@@ -0,0 +1,1649 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/StateMachine.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSysMsgr",
+ "@mozilla.org/system-message-internal;1",
+ "nsISystemMessagesInternal");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
+ "@mozilla.org/network/manager;1",
+ "nsINetworkManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+ "@mozilla.org/network/service;1",
+ "nsINetworkService");
+
+this.EXPORTED_SYMBOLS = ["WifiP2pManager"];
+
+const EVENT_IGNORED = -1;
+const EVENT_UNKNOWN = -2;
+
+// Events from supplicant for p2p.
+const EVENT_P2P_DEVICE_FOUND = 0;
+const EVENT_P2P_DEVICE_LOST = 1;
+const EVENT_P2P_GROUP_STARTED = 2;
+const EVENT_P2P_GROUP_REMOVED = 3;
+const EVENT_P2P_PROV_DISC_PBC_REQ = 4;
+const EVENT_P2P_PROV_DISC_PBC_RESP = 5;
+const EVENT_P2P_PROV_DISC_SHOW_PIN = 6;
+const EVENT_P2P_PROV_DISC_ENTER_PIN = 7;
+const EVENT_P2P_GO_NEG_REQUEST = 8;
+const EVENT_P2P_GO_NEG_SUCCESS = 9;
+const EVENT_P2P_GO_NEG_FAILURE = 10;
+const EVENT_P2P_GROUP_FORMATION_SUCCESS = 11;
+const EVENT_P2P_GROUP_FORMATION_FAILURE = 12;
+const EVENT_P2P_FIND_STOPPED = 13;
+const EVENT_P2P_INVITATION_RESULT = 14;
+const EVENT_P2P_INVITATION_RECEIVED = 15;
+const EVENT_P2P_PROV_DISC_FAILURE = 16;
+
+// Events from supplicant but not p2p specific.
+const EVENT_AP_STA_DISCONNECTED = 100;
+const EVENT_AP_STA_CONNECTED = 101;
+
+// Events from DOM.
+const EVENT_P2P_SET_PAIRING_CONFIRMATION = 1000;
+const EVENT_P2P_CMD_CONNECT = 1001;
+const EVENT_P2P_CMD_DISCONNECT = 1002;
+const EVENT_P2P_CMD_ENABLE = 1003;
+const EVENT_P2P_CMD_DISABLE = 1004;
+const EVENT_P2P_CMD_ENABLE_SCAN = 1005;
+const EVENT_P2P_CMD_DISABLE_SCAN = 1006;
+const EVENT_P2P_CMD_BLOCK_SCAN = 1007;
+const EVENT_P2P_CMD_UNBLOCK_SCAN = 1008;
+
+// Internal events.
+const EVENT_TIMEOUT_PAIRING_CONFIRMATION = 10000;
+const EVENT_TIMEOUT_NEG_REQ = 10001;
+const EVENT_TIMEOUT_CONNECTING = 10002;
+const EVENT_P2P_ENABLE_SUCCESS = 10003;
+const EVENT_P2P_ENABLE_FAILED = 10004;
+const EVENT_P2P_DISABLE_SUCCESS = 10005;
+
+// WPS method string.
+const WPS_METHOD_PBC = "pbc";
+const WPS_METHOD_DISPLAY = "display";
+const WPS_METHOD_KEYPAD = "keypad";
+
+// Role string.
+const P2P_ROLE_GO = "GO";
+const P2P_ROLE_CLIENT = "client";
+
+// System message for pairing request.
+const PAIRING_REQUEST_SYS_MSG = "wifip2p-pairing-request";
+
+// Configuration.
+const P2P_INTERFACE_NAME = "p2p0";
+const DEFAULT_GO_INTENT = 15;
+const DEFAULT_P2P_DEVICE_NAME = "FirefoxPhone";
+const P2P_SCAN_TIMEOUT_SEC = 120;
+const DEFAULT_P2P_WPS_METHODS = "virtual_push_button physical_display keypad"; // For wpa_supplicant.
+const DEFAULT_P2P_DEVICE_TYPE = "10-0050F204-5"; // For wpa_supplicant.
+
+const GO_NETWORK_INTERFACE = {
+ ip: "192.168.2.1",
+ maskLength: 24,
+ gateway: "192.168.2.1",
+ dns1: "0.0.0.0",
+ dns2: "0.0.0.0",
+ dhcpServer: "192.168.2.1"
+};
+
+const GO_DHCP_SERVER_IP_RANGE = {
+ startIp: "192.168.2.10",
+ endIp: "192.168.2.30"
+};
+
+var gDebug = false;
+
+// Device Capability bitmap
+const DEVICE_CAPAB_SERVICE_DISCOVERY = 1;
+const DEVICE_CAPAB_CLIENT_DISCOVERABILITY = 1<<1;
+const DEVICE_CAPAB_CONCURRENT_OPER = 1<<2;
+const DEVICE_CAPAB_INFRA_MANAGED = 1<<3;
+const DEVICE_CAPAB_DEVICE_LIMIT = 1<<4;
+const DEVICE_CAPAB_INVITATION_PROCEDURE = 1<<5;
+
+// Group Capability bitmap
+const GROUP_CAPAB_GROUP_OWNER = 1;
+const GROUP_CAPAB_PERSISTENT_GROUP = 1<<1;
+const GROUP_CAPAB_GROUP_LIMIT = 1<<2;
+const GROUP_CAPAB_INTRA_BSS_DIST = 1<<3;
+const GROUP_CAPAB_CROSS_CONN = 1<<4;
+const GROUP_CAPAB_PERSISTENT_RECONN = 1<<5;
+const GROUP_CAPAB_GROUP_FORMATION = 1<<6;
+
+// Constants defined in wpa_supplicants.
+const DEV_PW_REGISTRAR_SPECIFIED = 5;
+const DEV_PW_USER_SPECIFIED = 1;
+const DEV_PW_PUSHBUTTON = 4;
+
+this.WifiP2pManager = function (aP2pCommand, aNetUtil) {
+ function debug(aMsg) {
+ if (gDebug) {
+ dump('-------------- WifiP2pManager: ' + aMsg);
+ }
+ }
+
+ let manager = {};
+
+ let _stateMachine = P2pStateMachine(aP2pCommand, aNetUtil);
+
+ // Set debug flag to true or false.
+ //
+ // @param aDebug Boolean to indicate enabling or disabling the debug flag.
+ manager.setDebug = function(aDebug) {
+ gDebug = aDebug;
+ };
+
+ // Set observer of observing internal state machine events.
+ //
+ // @param aObserver Used to notify WifiWorker what's happening
+ // in the internal p2p state machine.
+ manager.setObserver = function(aObserver) {
+ _stateMachine.setObserver(aObserver);
+ };
+
+ // Handle wpa_supplicant events.
+ //
+ // @param aEventString string from wpa_supplicant.
+ manager.handleEvent = function(aEventString) {
+ let event = parseEventString(aEventString);
+ if (EVENT_UNKNOWN === event.id || EVENT_IGNORED === event.id) {
+ debug('Unknow or ignored event: ' + aEventString);
+ return false;
+ }
+ return _stateMachine.sendEvent(event);
+ };
+
+ // Set the confirmation of pairing request.
+ //
+ // @param aResult Object of confirmation result which contains:
+ // .accepted: user granted.
+ // .pin: pin code which is displaying or input by user.
+ // .wpsMethod: string of "pbc" or "display" or "keypad".
+ manager.setPairingConfirmation = function(aResult) {
+ let event = {
+ id: EVENT_P2P_SET_PAIRING_CONFIRMATION,
+ info: {
+ accepted: aResult.accepted,
+ pin: aResult.pin
+ }
+ };
+ _stateMachine.sendEvent(event);
+ };
+
+ // Connect to a known peer.
+ //
+ // @param aAddress MAC address of the peer to connect.
+ // @param aWpsMethod String of "pbc" or "display" or "keypad".
+ // @param aGoIntent Number from 0 to 15.
+ // @param aCallback Callback |true| on attempting to connect.
+ // |false| on failed to connect.
+ manager.connect = function(aAddress, aWpsMethod, aGoIntent, aCallback) {
+ let event = {
+ id: EVENT_P2P_CMD_CONNECT,
+ info: {
+ wpsMethod: aWpsMethod,
+ address: aAddress,
+ goIntent: aGoIntent,
+ onDoConnect: aCallback
+ }
+ };
+ _stateMachine.sendEvent(event);
+ };
+
+ // Disconnect with a known peer.
+ //
+ // @param aAddress The address the user desires to disconect.
+ // @param aCallback Callback |true| on "attempting" to disconnect.
+ // |false| on failed to disconnect.
+ manager.disconnect = function(aAddress, aCallback) {
+ let event = {
+ id: EVENT_P2P_CMD_DISCONNECT,
+ info: {
+ address: aAddress,
+ onDoDisconnect: aCallback
+ }
+ };
+ _stateMachine.sendEvent(event);
+ };
+
+ // Enable/disable wifi p2p.
+ //
+ // @param aEnabled |true| to enable, |false| to disable.
+ // @param aCallbacks object for callbacks:
+ // .onEnabled
+ // .onDisabled
+ // .onSupplicantConnected
+ manager.setEnabled = function(aEnabled, aCallbacks) {
+ let event = {
+ id: (aEnabled ? EVENT_P2P_CMD_ENABLE : EVENT_P2P_CMD_DISABLE),
+ info: {
+ onEnabled: aCallbacks.onEnabled,
+ onDisabled: aCallbacks.onDisabled,
+ onSupplicantConnected: aCallbacks.onSupplicantConnected
+ }
+ };
+ _stateMachine.sendEvent(event);
+ };
+
+ // Enable/disable the wifi p2p scan.
+ //
+ // @param aEnabled |true| to enable scan, |false| to disable scan.
+ // @param aCallback Callback |true| on success to enable/disable scan.
+ // |false| on failed to enable/disable scan.
+ manager.setScanEnabled = function(aEnabled, aCallback) {
+ let event = {
+ id: (aEnabled ? EVENT_P2P_CMD_ENABLE_SCAN : EVENT_P2P_CMD_DISABLE_SCAN),
+ info: { callback: aCallback }
+ };
+ _stateMachine.sendEvent(event);
+ };
+
+ // Block wifi p2p scan.
+ manager.blockScan = function() {
+ _stateMachine.sendEvent({ id: EVENT_P2P_CMD_BLOCK_SCAN });
+ };
+
+ // Un-block and do the pending scan if any.
+ manager.unblockScan = function() {
+ _stateMachine.sendEvent({ id: EVENT_P2P_CMD_UNBLOCK_SCAN });
+ };
+
+ // Set the p2p device name.
+ manager.setDeviceName = function(newDeivceName, callback) {
+ aP2pCommand.setDeviceName(newDeivceName, callback);
+ };
+
+ // Parse wps_supplicant event string.
+ //
+ // @param aEventString The raw event string from wpa_supplicant.
+ //
+ // @return Object:
+ // .id: a number to represent an event.
+ // .info: the additional information carried by this event string.
+ function parseEventString(aEventString) {
+ if (isIgnoredEvent(aEventString)) {
+ return { id: EVENT_IGNORED };
+ }
+
+ let match = RegExp("p2p_dev_addr=([0-9a-fA-F:]+) " +
+ "pri_dev_type=([0-9a-zA-Z-]+) " +
+ "name='(.*)' " +
+ "config_methods=0x([0-9a-fA-F]+) " +
+ "dev_capab=0x([0-9a-fA-F]+) " +
+ "group_capab=0x([0-9a-fA-F]+) ").exec(aEventString + ' ');
+
+ let tokens = aEventString.split(" ");
+
+ let id = EVENT_UNKNOWN;
+
+ // general info.
+ let info = {};
+
+ if (match) {
+ info = {
+ address: match[1] ? match[1] : null,
+ type: match[2] ? match[2] : null,
+ name: match[3] ? match[3] : null,
+ wpsFlag: match[4] ? parseInt(match[4], 16) : null,
+ devFlag: match[5] ? parseInt(match[5], 16) : null,
+ groupFlag: match[6] ? parseInt(match[6], 16) : null
+ };
+ }
+
+ if (0 === aEventString.indexOf("P2P-DEVICE-FOUND")) {
+ id = EVENT_P2P_DEVICE_FOUND;
+ info.wpsCapabilities = wpsFlagToCapabilities(info.wpsFlag);
+ info.isGroupOwner = isPeerGroupOwner(info.groupFlag);
+ } else if (0 === aEventString.indexOf("P2P-DEVICE-LOST")) {
+ // e.g. "P2P-DEVICE-LOST p2p_dev_addr=5e:0a:5b:15:1f:80".
+ id = EVENT_P2P_DEVICE_LOST;
+ info.address = /p2p_dev_addr=([0-9a-f:]+)/.exec(aEventString)[1];
+ } else if (0 === aEventString.indexOf("P2P-GROUP-STARTED")) {
+ // e.g. "P2P-GROUP-STARTED wlan0-p2p-0 GO ssid="DIRECT-3F Testing
+ // passphrase="12345678" go_dev_addr=02:40:61:c2:f3:b7 [PERSISTENT]".
+
+ id = EVENT_P2P_GROUP_STARTED;
+ let groupMatch = RegExp('ssid="(.*)" ' +
+ 'freq=([0-9]*) ' +
+ '(passphrase|psk)=([^ ]+) ' +
+ 'go_dev_addr=([0-9a-f:]+)').exec(aEventString);
+ info.ssid = groupMatch[1];
+ info.freq = groupMatch[2];
+ if ('passphrase' === groupMatch[3]) {
+ let s = groupMatch[4]; // e.g. "G7jHkkz9".
+ info.passphrase = s.substring(1, s.length-1); // Trim the double quote.
+ } else { // psk
+ info.psk = groupMatch[4];
+ }
+ info.goAddress = groupMatch[5];
+ info.ifname = tokens[1];
+ info.role = tokens[2];
+ } else if (0 === aEventString.indexOf("P2P-GROUP-REMOVED")) {
+ id = EVENT_P2P_GROUP_REMOVED;
+ // e.g. "P2P-GROUP-REMOVED wlan0-p2p-0 GO".
+ info.ifname = tokens[1];
+ info.role = tokens[2];
+ } else if (0 === aEventString.indexOf("P2P-PROV-DISC-PBC-REQ")) {
+ id = EVENT_P2P_PROV_DISC_PBC_REQ;
+ info.wpsMethod = WPS_METHOD_PBC;
+ } else if (0 === aEventString.indexOf("P2P-PROV-DISC-PBC-RESP")) {
+ id = EVENT_P2P_PROV_DISC_PBC_RESP;
+ // The address is different from the general pattern.
+ info.address = aEventString.split(" ")[1];
+ info.wpsMethod = WPS_METHOD_PBC;
+ } else if (0 === aEventString.indexOf("P2P-PROV-DISC-SHOW-PIN")) {
+ id = EVENT_P2P_PROV_DISC_SHOW_PIN;
+ // Obtain peer address and pin from tokens.
+ info.address = tokens[1];
+ info.pin = tokens[2];
+ info.wpsMethod = WPS_METHOD_DISPLAY;
+ } else if (0 === aEventString.indexOf("P2P-PROV-DISC-ENTER-PIN")) {
+ id = EVENT_P2P_PROV_DISC_ENTER_PIN;
+ // Obtain peer address from tokens.
+ info.address = tokens[1];
+ info.wpsMethod = WPS_METHOD_KEYPAD;
+ } else if (0 === aEventString.indexOf("P2P-GO-NEG-REQUEST")) {
+ id = EVENT_P2P_GO_NEG_REQUEST;
+ info.address = tokens[1];
+ switch (parseInt(tokens[2].split("=")[1], 10)) {
+ case DEV_PW_REGISTRAR_SPECIFIED: // (5) Peer is display.
+ info.wpsMethod = WPS_METHOD_KEYPAD;
+ break;
+ case DEV_PW_USER_SPECIFIED: // (1) Peer is keypad.
+ info.wpsMethod = WPS_METHOD_DISPLAY;
+ break;
+ case DEV_PW_PUSHBUTTON: // (4) Peer is pbc.
+ info.wpsMethod = WPS_METHOD_PBC;
+ break;
+ default:
+ debug('Unknown wps method from event P2P-GO-NEG-REQUEST');
+ break;
+ }
+ } else if (0 === aEventString.indexOf("P2P-GO-NEG-SUCCESS")) {
+ id = EVENT_P2P_GO_NEG_SUCCESS;
+ } else if (0 === aEventString.indexOf("P2P-GO-NEG-FAILURE")) {
+ id = EVENT_P2P_GO_NEG_FAILURE;
+ } else if (0 === aEventString.indexOf("P2P-GROUP-FORMATION-FAILURE")) {
+ id = EVENT_P2P_GROUP_FORMATION_FAILURE;
+ } else if (0 === aEventString.indexOf("P2P-GROUP-FORMATION-SUCCESS")) {
+ id = EVENT_P2P_GROUP_FORMATION_SUCCESS;
+ } else if (0 === aEventString.indexOf("P2P-FIND-STOPPED")) {
+ id = EVENT_P2P_FIND_STOPPED;
+ } else if (0 === aEventString.indexOf("P2P-INVITATION-RESULT")) {
+ id = EVENT_P2P_INVITATION_RESULT;
+ info.status = /status=([0-9]+)/.exec(aEventString)[1];
+ } else if (0 === aEventString.indexOf("P2P-INVITATION-RECEIVED")) {
+ // e.g. "P2P-INVITATION-RECEIVED sa=32:85:a9:da:e6:1f persistent=7".
+ id = EVENT_P2P_INVITATION_RECEIVED;
+ info.address = /sa=([0-9a-f:]+)/.exec(aEventString)[1];
+ info.netId = /persistent=([0-9]+)/.exec(aEventString)[1];
+ } else if (0 === aEventString.indexOf("P2P-PROV-DISC-FAILURE")) {
+ id = EVENT_P2P_PROV_DISC_FAILURE;
+ } else {
+ // Not P2P event but we do receive it. Try to recognize it.
+ if (0 === aEventString.indexOf("AP-STA-DISCONNECTED")) {
+ id = EVENT_AP_STA_DISCONNECTED;
+ info.address = tokens[1];
+ } else if (0 === aEventString.indexOf("AP-STA-CONNECTED")) {
+ id = EVENT_AP_STA_CONNECTED;
+ info.address = tokens[1];
+ } else {
+ // Neither P2P event nor recognized supplicant event.
+ debug('Unknwon event string: ' + aEventString);
+ }
+ }
+
+ let event = {id: id, info: info};
+ debug('Event parsing result: ' + aEventString + ": " + JSON.stringify(event));
+
+ return event;
+ }
+
+ function isIgnoredEvent(aEventString) {
+ const IGNORED_EVENTS = [
+ "CTRL-EVENT-BSS-ADDED",
+ "CTRL-EVENT-BSS-REMOVED",
+ "CTRL-EVENT-SCAN-RESULTS",
+ "CTRL-EVENT-STATE-CHANGE",
+ "WPS-AP-AVAILABLE",
+ "WPS-ENROLLEE-SEEN"
+ ];
+ for(let i = 0; i < IGNORED_EVENTS.length; i++) {
+ if (0 === aEventString.indexOf(IGNORED_EVENTS[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function isPeerGroupOwner(aGroupFlag) {
+ return (aGroupFlag & GROUP_CAPAB_GROUP_OWNER) !== 0;
+ }
+
+ // Convert flag to a wps capability array.
+ //
+ // @param aWpsFlag Number that represents the wps capabilities.
+ // @return Array of WPS flag.
+ function wpsFlagToCapabilities(aWpsFlag) {
+ let wpsCapabilities = [];
+ if (aWpsFlag & 0x8) {
+ wpsCapabilities.push(WPS_METHOD_DISPLAY);
+ }
+ if (aWpsFlag & 0x80) {
+ wpsCapabilities.push(WPS_METHOD_PBC);
+ }
+ if (aWpsFlag & 0x100) {
+ wpsCapabilities.push(WPS_METHOD_KEYPAD);
+ }
+ return wpsCapabilities;
+ }
+
+ _stateMachine.start();
+ return manager;
+};
+
+function P2pStateMachine(aP2pCommand, aNetUtil) {
+ function debug(aMsg) {
+ if (gDebug) {
+ dump('-------------- WifiP2pStateMachine: ' + aMsg);
+ }
+ }
+
+ let p2pSm = {}; // The state machine to return.
+
+ let _sm = StateMachine('WIFIP2P'); // The general purpose state machine.
+
+ // Information we need to keep track across states.
+ let _observer;
+
+ let _onEnabled;
+ let _onDisabled;
+ let _onSupplicantConnected;
+ let _savedConfig = {}; // Configuration used to do P2P_CONNECT.
+ let _groupInfo = {}; // The information of the group we have formed.
+ let _removedGroupInfo = {}; // Used to store the group info we are going to remove.
+
+ let _scanBlocked = false;
+ let _scanPostponded = false;
+
+ let _localDevice = {
+ address: "",
+ deviceName: DEFAULT_P2P_DEVICE_NAME + "_" + libcutils.property_get("ro.build.product"),
+ wpsCapabilities: [WPS_METHOD_PBC, WPS_METHOD_KEYPAD, WPS_METHOD_DISPLAY]
+ };
+
+ let _p2pNetworkInterface = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
+
+ info: {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo]),
+
+ state: Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED,
+ type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI_P2P,
+ name: P2P_INTERFACE_NAME,
+ ips: [],
+ prefixLengths: [],
+ dnses: [],
+ gateways: [],
+
+ getAddresses: function (ips, prefixLengths) {
+ ips.value = this.ips.slice();
+ prefixLengths.value = this.prefixLengths.slice();
+
+ return this.ips.length;
+ },
+
+ getGateways: function (count) {
+ if (count) {
+ count.value = this.gateways.length;
+ }
+ return this.gateways.slice();
+ },
+
+ getDnses: function (count) {
+ if (count) {
+ count.value = this.dnses.length;
+ }
+ return this.dnses.slice();
+ }
+ },
+
+ httpProxyHost: null,
+ httpProxyPort: null,
+
+ // help
+ registered: false
+ };
+
+ //---------------------------------------------------------
+ // State machine APIs.
+ //---------------------------------------------------------
+
+ // Register the observer which is implemented in WifiP2pWorkerObserver.jsm.
+ //
+ // @param aObserver:
+ // .onEnabled
+ // .onDisbaled
+ // .onPeerFound
+ // .onPeerLost
+ // .onConnecting
+ // .onConnected
+ // .onDisconnected
+ // .onLocalDeviceChanged
+ p2pSm.setObserver = function(aObserver) {
+ _observer = aObserver;
+ };
+
+ p2pSm.start = function() {
+ _sm.start(stateDisabled);
+ };
+
+ p2pSm.sendEvent = function(aEvent) {
+ let willBeHandled = isInP2pManagedState(_sm.getCurrentState());
+ _sm.sendEvent(aEvent);
+ return willBeHandled;
+ };
+
+ // Initialize internal state machine _sm.
+ _sm.setDefaultEventHandler(handleEventCommon);
+
+ //----------------------------------------------------------
+ // State definition.
+ //----------------------------------------------------------
+
+ // The initial state.
+ var stateDisabled = _sm.makeState("DISABLED", {
+ enter: function() {
+ _onEnabled = null;
+ _onSupplicantConnected = null;
+ _savedConfig = null;
+ _groupInfo = null;
+ _removedGroupInfo = null;
+ _scanBlocked = false;
+ _scanPostponded = false;
+
+ unregisterP2pNetworkInteface();
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.id) {
+ case EVENT_P2P_CMD_ENABLE:
+ _onEnabled = aEvent.info.onEnabled;
+ _onSupplicantConnected = aEvent.info.onSupplicantConnected;
+ _sm.gotoState(stateEnabling);
+ break;
+
+ default:
+ return false;
+ } // End of switch.
+ return true;
+ }
+ });
+
+ // The state where we are trying to enable wifi p2p.
+ var stateEnabling = _sm.makeState("ENABLING", {
+ enter: function() {
+
+ function onFailure()
+ {
+ _onEnabled(false);
+ _observer.onDisabled();
+ _sm.gotoState(stateDisabled);
+ }
+
+ function onSuccess()
+ {
+ _onEnabled(true);
+ _observer.onEnabled();
+ _sm.gotoState(stateInactive);
+ }
+
+ _sm.pause();
+
+ // This function will only call back on success.
+ function connectToSupplicantIfNeeded(callback) {
+ if (aP2pCommand.getSdkVersion() >= 19) {
+ // No need to connect to supplicant on KK. Call back directly.
+ callback();
+ return;
+ }
+ aP2pCommand.connectToSupplicant(function (status) {
+ if (0 !== status) {
+ debug('Failed to connect to p2p0');
+ onFailure();
+ return;
+ }
+ debug('wpa_supplicant p2p0 connected!');
+ _onSupplicantConnected();
+ callback();
+ });
+ }
+
+ // Step 1: Connect to p2p0 if needed.
+ connectToSupplicantIfNeeded(function callback () {
+ let detail;
+
+ // Step 2: Get MAC address.
+ if (!_localDevice.address) {
+ aP2pCommand.getMacAddress(function (address) {
+ if (!address) {
+ debug('Failed to get MAC address....');
+ onFailure();
+ return;
+ }
+ debug('Got mac address: ' + address);
+ _localDevice.address = address;
+ _observer.onLocalDeviceChanged(_localDevice);
+ });
+ }
+
+ // Step 3: Enable p2p with the device name and wps methods.
+ detail = { deviceName: _localDevice.deviceName,
+ deviceType: libcutils.property_get("ro.moz.wifi.p2p_device_type") || DEFAULT_P2P_DEVICE_TYPE,
+ wpsMethods: libcutils.property_get("ro.moz.wifi.p2p_wps_methods") || DEFAULT_P2P_WPS_METHODS };
+
+ aP2pCommand.p2pEnable(detail, function (success) {
+ if (!success) {
+ debug('Failed to enable p2p');
+ onFailure();
+ return;
+ }
+
+ debug('P2P is enabled! Enabling net interface...');
+
+ // Step 4: Enable p2p0 net interface. wpa_supplicant may have
+ // already done it for us.
+ gNetworkService.enableInterface(P2P_INTERFACE_NAME, function (success) {
+ onSuccess();
+ });
+ });
+ });
+ },
+
+ handleEvent: function(aEvent) {
+ // We won't receive any event since all of them will be blocked.
+ return true;
+ }
+ });
+
+ // The state just after enabling wifi direct.
+ var stateInactive = _sm.makeState("INACTIVE", {
+ enter: function() {
+ registerP2pNetworkInteface();
+
+ if (_sm.getPreviousState() !== stateEnabling) {
+ _observer.onDisconnected(_savedConfig);
+ }
+
+ _savedConfig = null; // Used to connect p2p peer.
+ _groupInfo = null; // The information of the formed group.
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.id) {
+ // Receiving the following 3 states implies someone is trying to
+ // connect to me.
+ case EVENT_P2P_PROV_DISC_PBC_REQ:
+ case EVENT_P2P_PROV_DISC_SHOW_PIN:
+ case EVENT_P2P_PROV_DISC_ENTER_PIN:
+ debug('Someone is trying to connect to me: ' + JSON.stringify(aEvent.info));
+
+ _savedConfig = {
+ name: aEvent.info.name,
+ address: aEvent.info.address,
+ wpsMethod: aEvent.info.wpsMethod,
+ goIntent: DEFAULT_GO_INTENT,
+ pin: aEvent.info.pin // EVENT_P2P_PROV_DISC_SHOW_PIN only.
+ };
+
+ _sm.gotoState(stateWaitingForConfirmation);
+ break;
+
+ // Connect to a peer.
+ case EVENT_P2P_CMD_CONNECT:
+ debug('Trying to connect to peer: ' + JSON.stringify(aEvent.info));
+
+ _savedConfig = {
+ address: aEvent.info.address,
+ wpsMethod: aEvent.info.wpsMethod,
+ goIntent: aEvent.info.goIntent
+ };
+
+ _sm.gotoState(stateProvisionDiscovery);
+ aEvent.info.onDoConnect(true);
+ break;
+
+ case EVENT_P2P_INVITATION_RECEIVED:
+ _savedConfig = {
+ address: aEvent.info.address,
+ wpsMethod: WPS_METHOD_PBC,
+ goIntent: DEFAULT_GO_INTENT,
+ netId: aEvent.info.netId
+ };
+ _sm.gotoState(stateWaitingForInvitationConfirmation);
+ break;
+
+ case EVENT_P2P_GROUP_STARTED:
+ // Most likely the peer just reinvoked a peristen group and succeeeded.
+
+ _savedConfig = { address: aEvent.info.goAddress };
+
+ _sm.pause();
+ handleGroupStarted(aEvent.info, function (success) {
+ _sm.resume();
+ });
+ break;
+
+ case EVENT_AP_STA_DISCONNECTED:
+ // We will hit this case when we used to be a group owner and
+ // requested to remove the group we owned.
+ break;
+
+ default:
+ return false;
+ } // End of switch.
+ return true;
+ },
+ });
+
+ // Waiting for user's confirmation.
+ var stateWaitingForConfirmation = _sm.makeState("WAITING_FOR_CONFIRMATION", {
+ timeoutTimer: null,
+
+ enter: function() {
+ gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
+ this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.id) {
+ case EVENT_P2P_SET_PAIRING_CONFIRMATION:
+ if (!aEvent.info.accepted) {
+ debug('User rejected this request');
+ _sm.gotoState(stateInactive); // Reset to inactive state.
+ break;
+ }
+
+ debug('User accepted this request');
+
+ // The only information we may have to grab from user.
+ _savedConfig.pin = aEvent.info.pin;
+
+ // The case that user requested to form a group ealier on.
+ // Just go to connecting state and do p2p_connect.
+ if (_sm.getPreviousState() === stateProvisionDiscovery) {
+ _sm.gotoState(stateConnecting);
+ break;
+ }
+
+ // Otherwise, wait for EVENT_P2P_GO_NEG_REQUEST.
+ _sm.gotoState(stateWaitingForNegReq);
+ break;
+
+ case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
+ debug('Confirmation timeout!');
+ _sm.gotoState(stateInactive);
+ break;
+
+ case EVENT_P2P_GO_NEG_REQUEST:
+ _sm.deferEvent(aEvent);
+ break;
+
+ default:
+ return false;
+ } // End of switch.
+
+ return true;
+ },
+
+ exit: function() {
+ this.timeoutTimer.cancel();
+ this.timeoutTimer = null;
+ }
+ });
+
+ var stateWaitingForNegReq = _sm.makeState("WAITING_FOR_NEG_REQ", {
+ timeoutTimer: null,
+
+ enter: function() {
+ debug('Wait for EVENT_P2P_GO_NEG_REQUEST');
+ this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_NEG_REQ);
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.id) {
+ case EVENT_P2P_GO_NEG_REQUEST:
+ if (aEvent.info.wpsMethod !== _savedConfig.wpsMethod) {
+ debug('Unmatched wps method: ' + aEvent.info.wpsMethod + ", " + _savedConfig.wpsMetho);
+ }
+ _sm.gotoState(stateConnecting);
+ break;
+
+ case EVENT_TIMEOUT_NEG_REQ:
+ debug("Waiting for NEG-REQ timeout");
+ _sm.gotoState(stateInactive);
+ break;
+
+ default:
+ return false;
+ } // End of switch.
+ return true;
+ },
+
+ exit: function() {
+ this.timeoutTimer.cancel();
+ this.timeoutTimer = null;
+ }
+ });
+
+ // Waiting for user's confirmation for invitation.
+ var stateWaitingForInvitationConfirmation = _sm.makeState("WAITING_FOR_INV_CONFIRMATION", {
+ timeoutTimer: null,
+
+ enter: function() {
+ gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
+ this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.id) {
+ case EVENT_P2P_SET_PAIRING_CONFIRMATION:
+ if (!aEvent.info.accepted) {
+ debug('User rejected this request');
+ _sm.gotoState(stateInactive); // Reset to inactive state.
+ break;
+ }
+
+ debug('User accepted this request');
+ _sm.pause();
+ aP2pCommand.p2pGetGroupCapab(_savedConfig.address, function (gc) {
+ let isPeeGroupOwner = gc & GROUP_CAPAB_GROUP_OWNER;
+ _sm.gotoState(isPeeGroupOwner ? stateGroupAdding : stateReinvoking);
+ });
+
+ break;
+
+ case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
+ debug('Confirmation timeout!');
+ _sm.gotoState(stateInactive);
+ break;
+
+ default:
+ return false;
+ } // End of switch.
+
+ return true;
+ },
+
+ exit: function() {
+ this.timeoutTimer.cancel();
+ this.timeoutTimer = null;
+ }
+ });
+
+ var stateGroupAdding = _sm.makeState("GROUP_ADDING", {
+ timeoutTimer: null,
+
+ enter: function() {
+ let self = this;
+
+ _observer.onConnecting(_savedConfig);
+
+ _sm.pause();
+ aP2pCommand.p2pGroupAdd(_savedConfig.netId, function (success) {
+ if (!success) {
+ _sm.gotoState(stateInactive);
+ return;
+ }
+ // Waiting for EVENT_P2P_GROUP_STARTED.
+ self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
+ _sm.resume();
+ });
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.id) {
+ case EVENT_P2P_GROUP_STARTED:
+ _sm.pause();
+ handleGroupStarted(aEvent.info, function (success) {
+ _sm.resume();
+ });
+ break;
+
+ case EVENT_P2P_GO_NEG_FAILURE:
+ debug('Negotiation failure. Go back to inactive state');
+ _sm.gotoState(stateInactive);
+ break;
+
+ case EVENT_TIMEOUT_CONNECTING:
+ debug('Connecting timeout! Go back to inactive state');
+ _sm.gotoState(stateInactive);
+ break;
+
+ case EVENT_P2P_GROUP_FORMATION_SUCCESS:
+ case EVENT_P2P_GO_NEG_SUCCESS:
+ break;
+
+ case EVENT_P2P_GROUP_FORMATION_FAILURE:
+ debug('Group formation failure');
+ _sm.gotoState(stateInactive);
+ break;
+
+ case EVENT_P2P_GROUP_REMOVED:
+ debug('Received P2P-GROUP-REMOVED due to previous failed handleGroupdStarted()');
+ _removedGroupInfo = {
+ role: aEvent.info.role,
+ ifname: aEvent.info.ifname
+ };
+ _sm.gotoState(stateDisconnecting);
+ break;
+
+ default:
+ return false;
+ } // End of switch.
+
+ return true;
+ },
+
+ exit: function() {
+ this.timeoutTimer.cancel();
+ this.timeoutTimer = null;
+ }
+ });
+
+ var stateReinvoking = _sm.makeState("REINVOKING", {
+ timeoutTimer: null,
+
+ enter: function() {
+ let self = this;
+
+ _observer.onConnecting(_savedConfig);
+ _sm.pause();
+ aP2pCommand.p2pReinvoke(_savedConfig.netId, _savedConfig.address, function(success) {
+ if (!success) {
+ _sm.gotoState(stateInactive);
+ return;
+ }
+ // Waiting for EVENT_P2P_GROUP_STARTED.
+ self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
+ _sm.resume();
+ });
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.id) {
+ case EVENT_P2P_GROUP_STARTED:
+ _sm.pause();
+ handleGroupStarted(aEvent.info, function(success) {
+ _sm.resume();
+ });
+ break;
+
+ case EVENT_P2P_GO_NEG_FAILURE:
+ debug('Negotiation failure. Go back to inactive state');
+ _sm.gotoState(stateInactive);
+ break;
+
+ case EVENT_TIMEOUT_CONNECTING:
+ debug('Connecting timeout! Go back to inactive state');
+ _sm.gotoState(stateInactive);
+ break;
+
+ case EVENT_P2P_GROUP_FORMATION_SUCCESS:
+ case EVENT_P2P_GO_NEG_SUCCESS:
+ break;
+
+ case EVENT_P2P_GROUP_FORMATION_FAILURE:
+ debug('Group formation failure');
+ _sm.gotoState(stateInactive);
+ break;
+
+ case EVENT_P2P_GROUP_REMOVED:
+ debug('Received P2P-GROUP-REMOVED due to previous failed handleGroupdStarted()');
+ _removedGroupInfo = {
+ role: aEvent.info.role,
+ ifname: aEvent.info.ifname
+ };
+ _sm.gotoState(stateDisconnecting);
+ break;
+
+ default:
+ return false;
+ } // End of switch.
+
+ return true;
+ },
+
+ exit: function() {
+ this.timeoutTimer.cancel();
+ }
+ });
+
+ var stateProvisionDiscovery = _sm.makeState("PROVISION_DISCOVERY", {
+ enter: function() {
+ function onDiscoveryCommandSent(success) {
+ if (!success) {
+ _sm.gotoState(stateInactive);
+ debug('Failed to send p2p_prov_disc. Go back to inactive state.');
+ return;
+ }
+
+ debug('p2p_prov_disc has been sent.');
+
+ _sm.resume();
+ // Waiting for EVENT_P2P_PROV_DISC_PBC_RESP or
+ // EVENT_P2P_PROV_DISC_SHOW_PIN or
+ // EVENT_P2P_PROV_DISC_ENTER_PIN.
+ }
+
+ _sm.pause();
+ aP2pCommand.p2pProvDiscovery(_savedConfig.address,
+ toPeerWpsMethod(_savedConfig.wpsMethod),
+ onDiscoveryCommandSent);
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.id) {
+ case EVENT_P2P_PROV_DISC_PBC_RESP:
+ _sm.gotoState(stateConnecting); // No need for local user grant.
+ break;
+ case EVENT_P2P_PROV_DISC_SHOW_PIN:
+ case EVENT_P2P_PROV_DISC_ENTER_PIN:
+ if (aEvent.info.wpsMethod !== _savedConfig.wpsMethod) {
+ debug('Unmatched wps method: ' + aEvent.info.wpsMethod + ":" + _savedConfig.wpsMethod);
+ }
+ if (EVENT_P2P_PROV_DISC_SHOW_PIN === aEvent.id) {
+ _savedConfig.pin = aEvent.info.pin;
+ }
+ _sm.gotoState(stateWaitingForConfirmation);
+ break;
+
+ case EVENT_P2P_PROV_DISC_FAILURE:
+ _sm.gotoState(stateInactive);
+ break;
+
+ default:
+ return false;
+ } // End of switch.
+ return true;
+ }
+ });
+
+ // We are going to connect to the peer.
+ // |_savedConfig| is supposed to have been filled properly.
+ var stateConnecting = _sm.makeState("CONNECTING", {
+ timeoutTimer: null,
+
+ enter: function() {
+ let self = this;
+
+ if (null === _savedConfig.goIntent) {
+ _savedConfig.goIntent = DEFAULT_GO_INTENT;
+ }
+
+ _observer.onConnecting(_savedConfig);
+
+ let wpsMethodWithPin;
+ if (WPS_METHOD_KEYPAD === _savedConfig.wpsMethod ||
+ WPS_METHOD_DISPLAY === _savedConfig.wpsMethod) {
+ // e.g. '12345678 display or '12345678 keypad'.
+ wpsMethodWithPin = (_savedConfig.pin + ' ' + _savedConfig.wpsMethod);
+ } else {
+ // e.g. 'pbc'.
+ wpsMethodWithPin = _savedConfig.wpsMethod;
+ }
+
+ _sm.pause();
+
+ aP2pCommand.p2pGetGroupCapab(_savedConfig.address, function(gc) {
+ debug('group capabilities of ' + _savedConfig.address + ': ' + gc);
+
+ let isPeerGroupOwner = gc & GROUP_CAPAB_GROUP_OWNER;
+ let config = { address: _savedConfig.address,
+ wpsMethodWithPin: wpsMethodWithPin,
+ goIntent: _savedConfig.goIntent,
+ joinExistingGroup: isPeerGroupOwner };
+
+ aP2pCommand.p2pConnect(config, function (success) {
+ if (!success) {
+ debug('Failed to send p2p_connect');
+ _sm.gotoState(stateInactive);
+ return;
+ }
+ debug('Waiting for EVENT_P2P_GROUP_STARTED.');
+ self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
+ _sm.resume();
+ });
+ });
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.id) {
+ case EVENT_P2P_GROUP_STARTED:
+ _sm.pause();
+ handleGroupStarted(aEvent.info, function (success) {
+ _sm.resume();
+ });
+ break;
+
+ case EVENT_P2P_GO_NEG_FAILURE:
+ debug('Negotiation failure. Go back to inactive state');
+ _sm.gotoState(stateInactive);
+ break;
+
+ case EVENT_TIMEOUT_CONNECTING:
+ debug('Connecting timeout! Go back to inactive state');
+ _sm.gotoState(stateInactive);
+ break;
+
+ case EVENT_P2P_GROUP_FORMATION_SUCCESS:
+ case EVENT_P2P_GO_NEG_SUCCESS:
+ break;
+
+ case EVENT_P2P_GROUP_FORMATION_FAILURE:
+ debug('Group formation failure');
+ _sm.gotoState(stateInactive);
+ break;
+
+ case EVENT_P2P_GROUP_REMOVED:
+ debug('Received P2P-GROUP-REMOVED due to previous failed ' +
+ 'handleGroupdStarted()');
+ _removedGroupInfo = {
+ role: aEvent.info.role,
+ ifname: aEvent.info.ifname
+ };
+ _sm.gotoState(stateDisconnecting);
+ break;
+
+ default:
+ return false;
+ } // End of switch.
+
+ return true;
+ },
+
+ exit: function() {
+ this.timeoutTimer.cancel();
+ }
+ });
+
+ var stateConnected = _sm.makeState("CONNECTED", {
+ groupOwner: null,
+
+ enter: function() {
+ this.groupOwner = {
+ macAddress: _groupInfo.goAddress,
+ ipAddress: _groupInfo.networkInterface.info.gateways[0],
+ passphrase: _groupInfo.passphrase,
+ ssid: _groupInfo.ssid,
+ freq: _groupInfo.freq,
+ isLocal: _groupInfo.isGroupOwner
+ };
+
+ if (!_groupInfo.isGroupOwner) {
+ _observer.onConnected(this.groupOwner, _savedConfig);
+ } else {
+ // If I am a group owner, notify onConnected until EVENT_AP_STA_CONNECTED
+ // is received.
+ }
+
+ _removedGroupInfo = null;
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.id) {
+ case EVENT_AP_STA_CONNECTED:
+ if (_groupInfo.isGroupOwner) {
+ _observer.onConnected(this.groupOwner, _savedConfig);
+ }
+ break;
+
+ case EVENT_P2P_GROUP_REMOVED:
+ _removedGroupInfo = {
+ role: aEvent.info.role,
+ ifname: aEvent.info.ifname
+ };
+ _sm.gotoState(stateDisconnecting);
+ break;
+
+ case EVENT_AP_STA_DISCONNECTED:
+ debug('Client disconnected: ' + aEvent.info.address);
+
+ // Now we suppose it's the only client. Remove my group.
+ _sm.pause();
+ aP2pCommand.p2pGroupRemove(_groupInfo.ifname, function (success) {
+ debug('Requested to remove p2p group. Wait for EVENT_P2P_GROUP_REMOVED.');
+ _sm.resume();
+ });
+ break;
+
+ case EVENT_P2P_CMD_DISCONNECT:
+ // Since we only support single connection, we can ignore
+ // the given peer address.
+ _sm.pause();
+ aP2pCommand.p2pGroupRemove(_groupInfo.ifname, function(success) {
+ aEvent.info.onDoDisconnect(true);
+ _sm.resume();
+ });
+
+ debug('Sent disconnect command. Wait for EVENT_P2P_GROUP_REMOVED.');
+ break;
+
+ case EVENT_P2P_PROV_DISC_PBC_REQ:
+ case EVENT_P2P_PROV_DISC_SHOW_PIN:
+ case EVENT_P2P_PROV_DISC_ENTER_PIN:
+ debug('Someone is trying to connect to me: ' + JSON.stringify(aEvent.info));
+
+ _savedConfig = {
+ name: aEvent.info.name,
+ address: aEvent.info.address,
+ wpsMethod: aEvent.info.wpsMethod,
+ pin: aEvent.info.pin
+ };
+
+ _sm.gotoState(stateWaitingForJoiningConfirmation);
+ break;
+
+ default:
+ return false;
+ } // end of switch
+ return true;
+ }
+ });
+
+ var stateWaitingForJoiningConfirmation = _sm.makeState("WAITING_FOR_JOINING_CONFIRMATION", {
+ timeoutTimer: null,
+
+ enter: function() {
+ gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
+ this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
+ },
+
+ handleEvent: function (aEvent) {
+ switch (aEvent.id) {
+ case EVENT_P2P_SET_PAIRING_CONFIRMATION:
+ if (!aEvent.info.accepted) {
+ debug('User rejected invitation!');
+ _sm.gotoState(stateConnected);
+ break;
+ }
+
+ let onWpsCommandSent = function(success) {
+ _observer.onConnecting(_savedConfig);
+ _sm.gotoState(stateConnected);
+ };
+
+ _sm.pause();
+ if (WPS_METHOD_PBC === _savedConfig.wpsMethod) {
+ aP2pCommand.wpsPbc(onWpsCommandSent, _groupInfo.ifname);
+ } else {
+ let detail = { pin: _savedConfig.pin, iface: _groupInfo.ifname };
+ aP2pCommand.wpsPin(detail, onWpsCommandSent);
+ }
+ break;
+
+ case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
+ debug('WAITING_FOR_JOINING_CONFIRMATION timeout!');
+ _sm.gotoState(stateConnected);
+ break;
+
+ default:
+ return false;
+ } // End of switch.
+ return true;
+ },
+
+ exit: function() {
+ this.timeoutTimer.cancel();
+ this.timeoutTimer = null;
+ }
+ });
+
+ var stateDisconnecting = _sm.makeState("DISCONNECTING", {
+ enter: function() {
+ _sm.pause();
+ handleGroupRemoved(_removedGroupInfo, function (success) {
+ if (!success) {
+ debug('Failed to handle group removed event. What can I do?');
+ }
+ _sm.gotoState(stateInactive);
+ });
+ },
+
+ handleEvent: function(aEvent) {
+ return false; // We will not receive any event in this state.
+ }
+ });
+
+ var stateDisabling = _sm.makeState("DISABLING", {
+ enter: function() {
+ _sm.pause();
+ aNetUtil.stopDhcpServer(function (success) { // Stopping DHCP server is harmless.
+ debug('Stop DHCP server result: ' + success);
+ aP2pCommand.p2pDisable(function(success) {
+ debug('P2P function disabled');
+ closeSupplicantConnectionIfNeeded(function() {
+ debug('Supplicant connection closed');
+ gNetworkService.disableInterface(P2P_INTERFACE_NAME, function (success){
+ debug('Disabled interface: ' + P2P_INTERFACE_NAME);
+ _onDisabled(true);
+ _observer.onDisabled();
+ _sm.gotoState(stateDisabled);
+ });
+ });
+ });
+ });
+
+ function closeSupplicantConnectionIfNeeded(callback) {
+ // No need to connect to supplicant on KK. Call back directly.
+ if (aP2pCommand.getSdkVersion() >= 19) {
+ callback();
+ return;
+ }
+ aP2pCommand.closeSupplicantConnection(callback);
+ }
+ },
+
+ handleEvent: function(aEvent) {
+ return false; // We will not receive any event in this state.
+ }
+ });
+
+ //----------------------------------------------------------
+ // Helper functions.
+ //----------------------------------------------------------
+
+ // Handle 'P2P_GROUP_STARTED' event. Note that this function
+ // will also do the state transitioning and error handling.
+ //
+ // @param aInfo Information carried by "P2P_GROUP_STARTED" event:
+ // .role: P2P_ROLE_GO or P2P_ROLE_CLIENT
+ // .ssid:
+ // .freq:
+ // .passphrase: Used to connect to GO for legacy device.
+ // .goAddress:
+ // .ifname: e.g. p2p-p2p0
+ //
+ // @param aCallback Callback function.
+ function handleGroupStarted(aInfo, aCallback) {
+ debug('handleGroupStarted: ' + JSON.stringify(aInfo));
+
+ function onSuccess()
+ {
+ _sm.gotoState(stateConnected);
+ aCallback(true);
+ }
+
+ function onFailure()
+ {
+ debug('Failed to handleGroupdStarted(). Remove the group...');
+ aP2pCommand.p2pGroupRemove(aInfo.ifname, function (success) {
+ aCallback(false);
+
+ if (success) {
+ return; // Stay in current state and wait for EVENT_P2P_GROUP_REMOVED.
+ }
+
+ debug('p2pGroupRemove command error!');
+ _sm.gotoState(stateInactive);
+ });
+ }
+
+ // Save this group information.
+ _groupInfo = aInfo;
+ _groupInfo.isGroupOwner = (P2P_ROLE_GO === aInfo.role);
+
+ if (_groupInfo.isGroupOwner) {
+ debug('Group owner. Start DHCP server');
+ let dhcpServerConfig = { ifname: aInfo.ifname,
+ startIp: GO_DHCP_SERVER_IP_RANGE.startIp,
+ endIp: GO_DHCP_SERVER_IP_RANGE.endIp,
+ serverIp: GO_NETWORK_INTERFACE.ip,
+ maskLength: GO_NETWORK_INTERFACE.maskLength };
+
+ aNetUtil.startDhcpServer(dhcpServerConfig, function (success) {
+ if (!success) {
+ debug('Failed to start DHCP server');
+ onFailure();
+ return;
+ }
+
+ // Update p2p network interface.
+ _p2pNetworkInterface.info.state = Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED;
+ _p2pNetworkInterface.info.ips = [GO_NETWORK_INTERFACE.ip];
+ _p2pNetworkInterface.info.prefixLengths = [GO_NETWORK_INTERFACE.maskLength];
+ _p2pNetworkInterface.info.gateways = [GO_NETWORK_INTERFACE.ip];
+ handleP2pNetworkInterfaceStateChanged();
+
+ _groupInfo.networkInterface = _p2pNetworkInterface;
+
+ debug('Everything is done. Happy p2p GO~');
+ onSuccess();
+ });
+
+ return;
+ }
+
+ // We are the client.
+
+ debug("Client. Request IP from DHCP server on interface: " + _groupInfo.ifname);
+
+ aNetUtil.runDhcp(aInfo.ifname, 0, function(dhcpData) {
+ if(!dhcpData || !dhcpData.info) {
+ debug('Failed to run DHCP client');
+ onFailure();
+ return;
+ }
+
+ // Save network interface.
+ debug("DHCP request success: " + JSON.stringify(dhcpData.info));
+
+ // Update p2p network interface.
+ let maskLength =
+ netHelpers.getMaskLength(netHelpers.stringToIP(dhcpData.info.mask_str));
+ if (!maskLength) {
+ maskLength = 32; // max prefix for IPv4.
+ }
+ _p2pNetworkInterface.info.state = Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED;
+ _p2pNetworkInterface.info.ips = [dhcpData.info.ipaddr_str];
+ _p2pNetworkInterface.info.prefixLengths = [maskLength];
+ if (typeof dhcpData.info.dns1_str == "string" &&
+ dhcpData.info.dns1_str.length) {
+ _p2pNetworkInterface.info.dnses.push(dhcpData.info.dns1_str);
+ }
+ if (typeof dhcpData.info.dns2_str == "string" &&
+ dhcpData.info.dns2_str.length) {
+ _p2pNetworkInterface.info.dnses.push(dhcpData.info.dns2_str);
+ }
+ _p2pNetworkInterface.info.gateways = [dhcpData.info.gateway_str];
+ handleP2pNetworkInterfaceStateChanged();
+
+ _groupInfo.networkInterface = _p2pNetworkInterface;
+
+ debug('Happy p2p client~');
+ onSuccess();
+ });
+ }
+
+ function resetP2pNetworkInterface() {
+ _p2pNetworkInterface.info.state = Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED;
+ _p2pNetworkInterface.info.ips = [];
+ _p2pNetworkInterface.info.prefixLengths = [];
+ _p2pNetworkInterface.info.dnses = [];
+ _p2pNetworkInterface.info.gateways = [];
+ }
+
+ function registerP2pNetworkInteface() {
+ if (!_p2pNetworkInterface.registered) {
+ resetP2pNetworkInterface();
+ gNetworkManager.registerNetworkInterface(_p2pNetworkInterface);
+ _p2pNetworkInterface.registered = true;
+ }
+ }
+
+ function unregisterP2pNetworkInteface() {
+ if (_p2pNetworkInterface.registered) {
+ resetP2pNetworkInterface();
+ gNetworkManager.unregisterNetworkInterface(_p2pNetworkInterface);
+ _p2pNetworkInterface.registered = false;
+ }
+ }
+
+ function handleP2pNetworkInterfaceStateChanged() {
+ gNetworkManager.updateNetworkInterface(_p2pNetworkInterface);
+ }
+
+ // Handle 'P2P_GROUP_STARTED' event.
+ //
+ // @param aInfo information carried by "P2P_GROUP_REMOVED" event:
+ // .ifname
+ // .role: "GO" or "client".
+ //
+ // @param aCallback Callback function.
+ function handleGroupRemoved(aInfo, aCallback) {
+ if (!_groupInfo) {
+ debug('No group info. Why?');
+ aCallback(true);
+ return;
+ }
+ if (_groupInfo.ifname !== aInfo.ifname ||
+ _groupInfo.role !== aInfo.role) {
+ debug('Unmatched group info: ' + JSON.stringify(_groupInfo) +
+ ' v.s. ' + JSON.stringify(aInfo));
+ }
+
+ // Update p2p network interface.
+ _p2pNetworkInterface.info.state = Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED;
+ handleP2pNetworkInterfaceStateChanged();
+
+ if (P2P_ROLE_GO === aInfo.role) {
+ aNetUtil.stopDhcpServer(function(success) {
+ debug('Stop DHCP server result: ' + success);
+ aCallback(true);
+ });
+ } else {
+ aNetUtil.stopDhcp(aInfo.ifname, function() {
+ aCallback(true);
+ });
+ }
+ }
+
+ // Non state-specific event handler.
+ function handleEventCommon(aEvent) {
+ switch (aEvent.id) {
+ case EVENT_P2P_DEVICE_FOUND:
+ _observer.onPeerFound(aEvent.info);
+ break;
+
+ case EVENT_P2P_DEVICE_LOST:
+ _observer.onPeerLost(aEvent.info);
+ break;
+
+ case EVENT_P2P_CMD_DISABLE:
+ _onDisabled = aEvent.info.onDisabled;
+ _sm.gotoState(stateDisabling);
+ break;
+
+ case EVENT_P2P_CMD_ENABLE_SCAN:
+ if (_scanBlocked) {
+ _scanPostponded = true;
+ aEvent.info.callback(true);
+ break;
+ }
+ aP2pCommand.p2pEnableScan(P2P_SCAN_TIMEOUT_SEC, aEvent.info.callback);
+ break;
+
+ case EVENT_P2P_CMD_DISABLE_SCAN:
+ aP2pCommand.p2pDisableScan(aEvent.info.callback);
+ break;
+
+ case EVENT_P2P_FIND_STOPPED:
+ break;
+
+ case EVENT_P2P_CMD_BLOCK_SCAN:
+ _scanBlocked = true;
+ aP2pCommand.p2pDisableScan(function(success) {});
+ break;
+
+ case EVENT_P2P_CMD_UNBLOCK_SCAN:
+ _scanBlocked = false;
+ if (_scanPostponded) {
+ aP2pCommand.p2pEnableScan(P2P_SCAN_TIMEOUT_SEC, function(success) {});
+ }
+ break;
+
+ case EVENT_P2P_CMD_CONNECT:
+ case EVENT_P2P_CMD_DISCONNECT:
+ debug("The current state couldn't handle connect/disconnect request. Ignore it.");
+ break;
+
+ default:
+ return false;
+ } // End of switch.
+ return true;
+ }
+
+ function isInP2pManagedState(aState) {
+ let p2pManagedStates = [stateWaitingForConfirmation,
+ stateWaitingForNegReq,
+ stateProvisionDiscovery,
+ stateWaitingForInvitationConfirmation,
+ stateGroupAdding,
+ stateReinvoking,
+ stateConnecting,
+ stateConnected,
+ stateDisconnecting];
+
+ for (let i = 0; i < p2pManagedStates.length; i++) {
+ if (aState === p2pManagedStates[i]) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function initTimeoutTimer(aTimeoutMs, aTimeoutEvent) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ function onTimerFired() {
+ _sm.sendEvent({ id: aTimeoutEvent });
+ timer = null;
+ }
+ timer.initWithCallback(onTimerFired.bind(this), aTimeoutMs,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+ }
+
+ // Converts local WPS method to peer WPS method.
+ function toPeerWpsMethod(aLocalWpsMethod) {
+ switch (aLocalWpsMethod) {
+ case WPS_METHOD_DISPLAY:
+ return WPS_METHOD_KEYPAD;
+ case WPS_METHOD_KEYPAD:
+ return WPS_METHOD_DISPLAY;
+ case WPS_METHOD_PBC:
+ return WPS_METHOD_PBC;
+ default:
+ return WPS_METHOD_PBC; // Use "push button" as the default method.
+ }
+ }
+
+ return p2pSm;
+}
+
+this.WifiP2pManager.INTERFACE_NAME = P2P_INTERFACE_NAME;
diff --git a/dom/wifi/WifiP2pWorkerObserver.jsm b/dom/wifi/WifiP2pWorkerObserver.jsm
new file mode 100644
index 000000000..d04e8db5e
--- /dev/null
+++ b/dom/wifi/WifiP2pWorkerObserver.jsm
@@ -0,0 +1,319 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+const CONNECTION_STATUS_DISCONNECTED = "disconnected";
+const CONNECTION_STATUS_CONNECTING = "connecting";
+const CONNECTION_STATUS_CONNECTED = "connected";
+const CONNECTION_STATUS_DISCONNECTING = "disconnecting";
+
+const DEBUG = false;
+
+this.EXPORTED_SYMBOLS = ["WifiP2pWorkerObserver"];
+
+// WifiP2pWorkerObserver resides in WifiWorker to handle DOM message
+// by either 1) returning internally maintained information or
+// 2) delegating to aDomMsgResponder. It is also responsible
+// for observing events from WifiP2pManager and dispatch to DOM.
+//
+// @param aDomMsgResponder handles DOM messages, including
+// - setScanEnabled
+// - connect
+// - disconnect
+// - setPairingConfirmation
+// The instance is actually WifiP2pManager.
+this.WifiP2pWorkerObserver = function(aDomMsgResponder) {
+ function debug(aMsg) {
+ if (DEBUG) {
+ dump('-------------- WifiP2pWorkerObserver: ' + aMsg);
+ }
+ }
+
+ // Private member variables.
+ let _localDevice;
+ let _peerList = {}; // List of P2pDevice.
+ let _domManagers = [];
+ let _enabled = false;
+ let _groupOwner = null;
+ let _currentPeer = null;
+
+ // Constructor of P2pDevice. It will be exposed to DOM.
+ //
+ // @param aPeer object representing a P2P device:
+ // .name: string for the device name.
+ // .address: Mac address.
+ // .isGroupOwner: boolean to indicate if this device is the group owner.
+ // .wpsCapabilities: array of string of {"pbc", "display", "keypad"}.
+ function P2pDevice(aPeer) {
+ this.address = aPeer.address;
+ this.name = (aPeer.name ? aPeer.name : aPeer.address);
+ this.isGroupOwner = aPeer.isGroupOwner;
+ this.wpsCapabilities = aPeer.wpsCapabilities;
+ this.connectionStatus = CONNECTION_STATUS_DISCONNECTED;
+
+ // Since this object will be exposed to web, defined the exposed
+ // properties here.
+ this.__exposedProps__ = {
+ address: "r",
+ name: "r",
+ isGroupOwner: "r",
+ wpsCapabilities: "r",
+ connectionStatus: "r"
+ };
+ }
+
+ // Constructor of P2pGroupOwner.
+ //
+ // @param aGroupOwner:
+ // .macAddress
+ // .ipAddress
+ // .passphrase
+ // .ssid
+ // .freq
+ // .isLocal
+ function P2pGroupOwner(aGroupOwner) {
+ this.macAddress = aGroupOwner.macAddress; // The identifier to get further information.
+ this.ipAddress = aGroupOwner.ipAddress;
+ this.passphrase = aGroupOwner.passphrase;
+ this.ssid = aGroupOwner.ssid; // e.g. DIRECT-xy.
+ this.freq = aGroupOwner.freq;
+ this.isLocal = aGroupOwner.isLocal;
+
+ let detail = _peerList[aGroupOwner.macAddress];
+ if (detail) {
+ this.name = detail.name;
+ this.wpsCapabilities = detail.wpsCapabilities;
+ } else if (_localDevice.address === this.macAddress) {
+ this.name = _localDevice.name;
+ this.wpsCapabilities = _localDevice.wpsCapabilities;
+ } else {
+ debug("We don't know this group owner: " + aGroupOwner.macAddress);
+ this.name = aGroupOwner.macAddress;
+ this.wpsCapabilities = [];
+ }
+ }
+
+ function fireEvent(aMessage, aData) {
+ debug('domManager: ' + JSON.stringify(_domManagers));
+ _domManagers.forEach(function(manager) {
+ // Note: We should never have a dead message manager here because we
+ // observe our child message managers shutting down below.
+ manager.sendAsyncMessage("WifiP2pManager:" + aMessage, aData);
+ });
+ }
+
+ function addDomManager(aMsg) {
+ if (-1 === _domManagers.indexOf(aMsg.manager)) {
+ _domManagers.push(aMsg.manager);
+ }
+ }
+
+ function returnMessage(aMessage, aSuccess, aData, aMsg) {
+ let rMsg = aMessage + ":Return:" + (aSuccess ? "OK" : "NO");
+ aMsg.manager.sendAsyncMessage(rMsg,
+ { data: aData, rid: aMsg.rid, mid: aMsg.mid });
+ }
+
+ function handlePeerListUpdated() {
+ fireEvent("onpeerinfoupdate", {});
+ }
+
+ // Return a literal object as the constructed object.
+ return {
+ onLocalDeviceChanged: function(aDevice) {
+ _localDevice = aDevice;
+ debug('Local device updated to: ' + JSON.stringify(_localDevice));
+ },
+
+ onEnabled: function() {
+ _peerList = [];
+ _enabled = true;
+ fireEvent("p2pUp", {});
+ },
+
+ onDisbaled: function() {
+ _enabled = false;
+ fireEvent("p2pDown", {});
+ },
+
+ onPeerFound: function(aPeer) {
+ let newFoundPeer = new P2pDevice(aPeer);
+ let origianlPeer = _peerList[aPeer.address];
+ _peerList[aPeer.address] = newFoundPeer;
+ if (origianlPeer) {
+ newFoundPeer.connectionStatus = origianlPeer.connectionStatus;
+ }
+ handlePeerListUpdated();
+ },
+
+ onPeerLost: function(aPeer) {
+ let lostPeer = _peerList[aPeer.address];
+ if (!lostPeer) {
+ debug('Unknown peer lost: ' + aPeer.address);
+ return;
+ }
+ delete _peerList[aPeer.address];
+ handlePeerListUpdated();
+ },
+
+ onConnecting: function(aPeer) {
+ let peer = _peerList[aPeer.address];
+ if (!peer) {
+ debug('Unknown peer connecting: ' + aPeer.address);
+ peer = new P2pDevice(aPeer);
+ _peerList[aPeer.address] = peer;
+ handlePeerListUpdated();
+ }
+ peer.connectionStatus = CONNECTION_STATUS_CONNECTING;
+
+ fireEvent('onconnecting', { peer: peer });
+ },
+
+ onConnected: function(aGroupOwner, aPeer) {
+ let go = new P2pGroupOwner(aGroupOwner);
+ let peer = _peerList[aPeer.address];
+ if (!peer) {
+ debug('Unknown peer connected: ' + aPeer.address);
+ peer = new P2pDevice(aPeer);
+ _peerList[aPeer.address] = peer;
+ handlePeerListUpdated();
+ }
+ peer.connectionStatus = CONNECTION_STATUS_CONNECTED;
+ peer.isGroupOwner = (aPeer.address === aGroupOwner.address);
+
+ _groupOwner = go;
+ _currentPeer = peer;
+
+ fireEvent('onconnected', { groupOwner: go, peer: peer });
+ },
+
+ onDisconnected: function(aPeer) {
+ let peer = _peerList[aPeer.address];
+ if (!peer) {
+ debug('Unknown peer disconnected: ' + aPeer.address);
+ return;
+ }
+
+ peer.connectionStatus = CONNECTION_STATUS_DISCONNECTED;
+
+ _groupOwner = null;
+ _currentPeer = null;
+
+ fireEvent('ondisconnected', { peer: peer });
+ },
+
+ getObservedDOMMessages: function() {
+ return [
+ "WifiP2pManager:getState",
+ "WifiP2pManager:getPeerList",
+ "WifiP2pManager:setScanEnabled",
+ "WifiP2pManager:connect",
+ "WifiP2pManager:disconnect",
+ "WifiP2pManager:setPairingConfirmation",
+ "WifiP2pManager:setDeviceName"
+ ];
+ },
+
+ onDOMMessage: function(aMessage) {
+ let msg = aMessage.data || {};
+ msg.manager = aMessage.target;
+
+ if ("child-process-shutdown" === aMessage.name) {
+ let i;
+ if (-1 !== (i = _domManagers.indexOf(msg.manager))) {
+ _domManagers.splice(i, 1);
+ }
+ return;
+ }
+
+ if (!aMessage.target.assertPermission("wifi-manage")) {
+ return;
+ }
+
+ switch (aMessage.name) {
+ case "WifiP2pManager:getState": // A new DOM manager is created.
+ addDomManager(msg);
+ return { // Synchronous call. Simply return it.
+ enabled: _enabled,
+ groupOwner: _groupOwner,
+ currentPeer: _currentPeer
+ };
+
+ case "WifiP2pManager:setScanEnabled":
+ {
+ let enabled = msg.data;
+
+ aDomMsgResponder.setScanEnabled(enabled, function(success) {
+ returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+ });
+ }
+ break;
+
+ case "WifiP2pManager:getPeerList":
+ {
+ // Convert the object to an array.
+ let peerArray = [];
+ for (let key in _peerList) {
+ if (_peerList.hasOwnProperty(key)) {
+ peerArray.push(_peerList[key]);
+ }
+ }
+
+ returnMessage(aMessage.name, true, peerArray, msg);
+ }
+ break;
+
+ case "WifiP2pManager:connect":
+ {
+ let peer = msg.data;
+
+ let onDoConnect = function(success) {
+ returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+ };
+
+ aDomMsgResponder.connect(peer.address, peer.wpsMethod,
+ peer.goIntent, onDoConnect);
+ }
+ break;
+
+ case "WifiP2pManager:disconnect":
+ {
+ let address = msg.data;
+
+ aDomMsgResponder.disconnect(address, function(success) {
+ returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+ });
+ }
+ break;
+
+ case "WifiP2pManager:setPairingConfirmation":
+ {
+ let result = msg.data;
+ aDomMsgResponder.setPairingConfirmation(result);
+ returnMessage(aMessage.name, true, true, msg);
+ }
+ break;
+
+ case "WifiP2pManager:setDeviceName":
+ {
+ let newDeviceName = msg.data;
+ aDomMsgResponder.setDeviceName(newDeviceName, function(success) {
+ returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+ });
+ }
+ break;
+
+ default:
+ if (0 === aMessage.name.indexOf("WifiP2pManager:")) {
+ debug("DOM WifiP2pManager message not handled: " + aMessage.name);
+ }
+ } // End of switch.
+ }
+ };
+};
diff --git a/dom/wifi/WifiProxyService.cpp b/dom/wifi/WifiProxyService.cpp
new file mode 100644
index 000000000..0ff5097af
--- /dev/null
+++ b/dom/wifi/WifiProxyService.cpp
@@ -0,0 +1,357 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WifiProxyService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsXULAppAPI.h"
+#include "WifiUtils.h"
+
+#ifdef MOZ_TASK_TRACER
+#include "GeckoTaskTracer.h"
+using namespace mozilla::tasktracer;
+#endif
+
+#define NS_WIFIPROXYSERVICE_CID \
+ { 0xc6c9be7e, 0x744f, 0x4222, {0xb2, 0x03, 0xcd, 0x55, 0xdf, 0xc8, 0xbc, 0x12} }
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+// The singleton Wifi service, to be used on the main thread.
+static StaticRefPtr<WifiProxyService> gWifiProxyService;
+
+// The singleton supplicant class, that can be used on any thread.
+static UniquePtr<WpaSupplicant> gWpaSupplicant;
+
+// Runnable used dispatch the WaitForEvent result on the main thread.
+class WifiEventDispatcher : public Runnable
+{
+public:
+ WifiEventDispatcher(const nsAString& aEvent, const nsACString& aInterface)
+ : mEvent(aEvent)
+ , mInterface(aInterface)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ gWifiProxyService->DispatchWifiEvent(mEvent, mInterface);
+ return NS_OK;
+ }
+
+private:
+ nsString mEvent;
+ nsCString mInterface;
+};
+
+// Runnable used to call WaitForEvent on the event thread.
+class EventRunnable : public Runnable
+{
+public:
+ EventRunnable(const nsACString& aInterface)
+ : mInterface(aInterface)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ nsAutoString event;
+ gWpaSupplicant->WaitForEvent(event, mInterface);
+ if (!event.IsEmpty()) {
+#ifdef MOZ_TASK_TRACER
+ // Make wifi initialization events to be the source events of TaskTracer,
+ // and originate the rest correlation tasks from here.
+ AutoSourceEvent taskTracerEvent(SourceEventType::Wifi);
+ AddLabel("%s %s", mInterface.get(), NS_ConvertUTF16toUTF8(event).get());
+#endif
+ nsCOMPtr<nsIRunnable> runnable = new WifiEventDispatcher(event, mInterface);
+ NS_DispatchToMainThread(runnable);
+ }
+ return NS_OK;
+ }
+
+private:
+ nsCString mInterface;
+};
+
+// Runnable used dispatch the Command result on the main thread.
+class WifiResultDispatcher : public Runnable
+{
+public:
+ WifiResultDispatcher(WifiResultOptions& aResult, const nsACString& aInterface)
+ : mResult(aResult)
+ , mInterface(aInterface)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ gWifiProxyService->DispatchWifiResult(mResult, mInterface);
+ return NS_OK;
+ }
+
+private:
+ WifiResultOptions mResult;
+ nsCString mInterface;
+};
+
+// Runnable used to call SendCommand on the control thread.
+class ControlRunnable : public Runnable
+{
+public:
+ ControlRunnable(CommandOptions aOptions, const nsACString& aInterface)
+ : mOptions(aOptions)
+ , mInterface(aInterface)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ WifiResultOptions result;
+ if (gWpaSupplicant->ExecuteCommand(mOptions, result, mInterface)) {
+ nsCOMPtr<nsIRunnable> runnable = new WifiResultDispatcher(result, mInterface);
+ NS_DispatchToMainThread(runnable);
+ }
+ return NS_OK;
+ }
+private:
+ CommandOptions mOptions;
+ nsCString mInterface;
+};
+
+NS_IMPL_ISUPPORTS(WifiProxyService, nsIWifiProxyService)
+
+WifiProxyService::WifiProxyService()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!gWifiProxyService);
+}
+
+WifiProxyService::~WifiProxyService()
+{
+ MOZ_ASSERT(!gWifiProxyService);
+}
+
+already_AddRefed<WifiProxyService>
+WifiProxyService::FactoryCreate()
+{
+ if (!XRE_IsParentProcess()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gWifiProxyService) {
+ gWifiProxyService = new WifiProxyService();
+ ClearOnShutdown(&gWifiProxyService);
+
+ gWpaSupplicant = MakeUnique<WpaSupplicant>();
+ ClearOnShutdown(&gWpaSupplicant);
+ }
+
+ RefPtr<WifiProxyService> service = gWifiProxyService.get();
+ return service.forget();
+}
+
+NS_IMETHODIMP
+WifiProxyService::Start(nsIWifiEventListener* aListener,
+ const char ** aInterfaces,
+ uint32_t aNumOfInterfaces)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aListener);
+
+#if ANDROID_VERSION >= 19
+ // KK changes the way mux'ing/demux'ing different supplicant interfaces
+ // (e.g. wlan0/p2p0) from multi-sockets to single socket embedded with
+ // prefixed interface name (e.g. IFNAME=wlan0 xxxxxx). Therefore, we use
+ // the first given interface as the global interface for KK.
+ aNumOfInterfaces = 1;
+#endif
+
+ nsresult rv;
+
+ // Since EventRunnable runs in the manner of blocking, we have to
+ // spin a thread for each interface.
+ // (See the WpaSupplicant::WaitForEvent)
+ mEventThreadList.SetLength(aNumOfInterfaces);
+ for (uint32_t i = 0; i < aNumOfInterfaces; i++) {
+ mEventThreadList[i].mInterface = aInterfaces[i];
+ rv = NS_NewThread(getter_AddRefs(mEventThreadList[i].mThread));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't create wifi event thread");
+ Shutdown();
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ rv = NS_NewThread(getter_AddRefs(mControlThread));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't create wifi control thread");
+ Shutdown();
+ return NS_ERROR_FAILURE;
+ }
+
+ mListener = aListener;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WifiProxyService::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ for (size_t i = 0; i < mEventThreadList.Length(); i++) {
+ if (mEventThreadList[i].mThread) {
+ mEventThreadList[i].mThread->Shutdown();
+ mEventThreadList[i].mThread = nullptr;
+ }
+ }
+
+ mEventThreadList.Clear();
+
+ if (mControlThread) {
+ mControlThread->Shutdown();
+ mControlThread = nullptr;
+ }
+
+ mListener = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WifiProxyService::SendCommand(JS::Handle<JS::Value> aOptions,
+ const nsACString& aInterface,
+ JSContext* aCx)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ WifiCommandOptions options;
+
+ if (!options.Init(aCx, aOptions)) {
+ NS_WARNING("Bad dictionary passed to WifiProxyService::SendCommand");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mControlThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Dispatch the command to the control thread.
+ CommandOptions commandOptions(options);
+ nsCOMPtr<nsIRunnable> runnable = new ControlRunnable(commandOptions, aInterface);
+ mControlThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WifiProxyService::WaitForEvent(const nsACString& aInterface)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+#if ANDROID_VERSION >= 19
+ // We will only have one global interface for KK.
+ if (!mEventThreadList.IsEmpty()) {
+ nsCOMPtr<nsIRunnable> runnable = new EventRunnable(aInterface);
+ mEventThreadList[0].mThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL);
+ return NS_OK;
+ }
+#else
+ // Dispatch to the event thread which has the given interface name
+ for (size_t i = 0; i < mEventThreadList.Length(); i++) {
+ if (mEventThreadList[i].mInterface.Equals(aInterface)) {
+ nsCOMPtr<nsIRunnable> runnable = new EventRunnable(aInterface);
+ mEventThreadList[i].mThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL);
+ return NS_OK;
+ }
+ }
+#endif
+
+ return NS_ERROR_FAILURE;
+}
+
+void
+WifiProxyService::DispatchWifiResult(const WifiResultOptions& aOptions, const nsACString& aInterface)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mozilla::AutoSafeJSContext cx;
+ JS::Rooted<JS::Value> val(cx);
+
+ if (!ToJSValue(cx, aOptions, &val)) {
+ return;
+ }
+
+ if (mListener) {
+ // Call the listener with a JS value.
+ mListener->OnCommand(val, aInterface);
+ }
+}
+
+void
+WifiProxyService::DispatchWifiEvent(const nsAString& aEvent, const nsACString& aInterface)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+#if ANDROID_VERSION < 19
+ mListener->OnWaitEvent(aEvent, aInterface);
+#else
+ // The interface might be embedded in the event string such as
+ // "IFNAME=wlan0 CTRL-EVENT-BSS-ADDED 65 3c:94:d5:7c:11:8b".
+ // Parse the interface name from the event string and use p2p0
+ // as the default interface if "IFNAME" is not found.
+ nsAutoString event;
+ nsAutoString embeddedInterface(NS_LITERAL_STRING("p2p0"));
+ if (StringBeginsWith(aEvent, NS_LITERAL_STRING("IFNAME"))) {
+ int32_t ifnameFrom = aEvent.FindChar('=') + 1;
+ int32_t ifnameTo = aEvent.FindChar(' ') - 1;
+ embeddedInterface = Substring(aEvent, ifnameFrom, ifnameTo - ifnameFrom + 1);
+ event = Substring(aEvent, aEvent.FindChar(' ') + 1);
+ }
+ else {
+ event = aEvent;
+ }
+ mListener->OnWaitEvent(event, NS_ConvertUTF16toUTF8(embeddedInterface));
+#endif
+}
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WifiProxyService,
+ WifiProxyService::FactoryCreate)
+
+NS_DEFINE_NAMED_CID(NS_WIFIPROXYSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kWifiProxyServiceCIDs[] = {
+ { &kNS_WIFIPROXYSERVICE_CID, false, nullptr, WifiProxyServiceConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWifiProxyServiceContracts[] = {
+ { "@mozilla.org/wifi/service;1", &kNS_WIFIPROXYSERVICE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kWifiProxyServiceModule = {
+ mozilla::Module::kVersion,
+ kWifiProxyServiceCIDs,
+ kWifiProxyServiceContracts,
+ nullptr
+};
+
+} // namespace mozilla
+
+NSMODULE_DEFN(WifiProxyServiceModule) = &kWifiProxyServiceModule;
diff --git a/dom/wifi/WifiProxyService.h b/dom/wifi/WifiProxyService.h
new file mode 100644
index 000000000..406be47de
--- /dev/null
+++ b/dom/wifi/WifiProxyService.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WifiProxyService_h
+#define WifiProxyService_h
+
+#include "nsIWifiService.h"
+#include "nsCOMPtr.h"
+#include "nsThread.h"
+#include "mozilla/dom/WifiOptionsBinding.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+class WifiProxyService final : public nsIWifiProxyService
+{
+private:
+ struct EventThreadListEntry
+ {
+ nsCOMPtr<nsIThread> mThread;
+ nsCString mInterface;
+ };
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWIFIPROXYSERVICE
+
+ static already_AddRefed<WifiProxyService>
+ FactoryCreate();
+
+ void DispatchWifiEvent(const nsAString& aEvent, const nsACString& aInterface);
+ void DispatchWifiResult(const mozilla::dom::WifiResultOptions& aOptions,
+ const nsACString& aInterface);
+
+private:
+ WifiProxyService();
+ ~WifiProxyService();
+
+ nsTArray<EventThreadListEntry> mEventThreadList;
+ nsCOMPtr<nsIThread> mControlThread;
+ nsCOMPtr<nsIWifiEventListener> mListener;
+};
+
+} // namespace mozilla
+
+#endif // WifiProxyService_h
diff --git a/dom/wifi/WifiUtils.cpp b/dom/wifi/WifiUtils.cpp
new file mode 100644
index 000000000..2526c9ea4
--- /dev/null
+++ b/dom/wifi/WifiUtils.cpp
@@ -0,0 +1,521 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WifiUtils.h"
+#include <dlfcn.h>
+#include <errno.h>
+#include <cutils/properties.h>
+#include "prinit.h"
+#include "mozilla/Sprintf.h"
+#include "js/CharacterEncoding.h"
+
+using namespace mozilla::dom;
+
+#define BUFFER_SIZE 4096
+#define COMMAND_SIZE 256
+
+// Intentionally not trying to dlclose() this handle. That's playing
+// Russian roulette with security bugs.
+static void* sWifiLib;
+static PRCallOnceType sInitWifiLib;
+
+static PRStatus
+InitWifiLib()
+{
+ sWifiLib = dlopen("/system/lib/libhardware_legacy.so", RTLD_LAZY);
+ // We might fail to open the hardware lib. That's OK.
+ return PR_SUCCESS;
+}
+
+static void*
+GetSharedLibrary()
+{
+ PR_CallOnce(&sInitWifiLib, InitWifiLib);
+ return sWifiLib;
+}
+
+static bool
+GetWifiP2pSupported()
+{
+ char propP2pSupported[PROPERTY_VALUE_MAX];
+ property_get("ro.moz.wifi.p2p_supported", propP2pSupported, "0");
+ return (0 == strcmp(propP2pSupported, "1"));
+}
+
+static int
+hex2num(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return -1;
+}
+
+static int
+hex2byte(const char* hex)
+{
+ int a, b;
+ a = hex2num(*hex++);
+ if (a < 0)
+ return -1;
+ b = hex2num(*hex++);
+ if (b < 0)
+ return -1;
+ return (a << 4) | b;
+}
+
+// This function is equivalent to printf_decode() at src/utils/common.c in
+// the supplicant.
+
+static uint32_t
+convertToBytes(char* buf, uint32_t maxlen, const char* str)
+{
+ const char *pos = str;
+ uint32_t len = 0;
+ int val;
+
+ while (*pos) {
+ if (len == maxlen)
+ break;
+ switch (*pos) {
+ case '\\':
+ pos++;
+ switch (*pos) {
+ case '\\':
+ buf[len++] = '\\';
+ pos++;
+ break;
+ case '"':
+ buf[len++] = '"';
+ pos++;
+ break;
+ case 'n':
+ buf[len++] = '\n';
+ pos++;
+ break;
+ case 'r':
+ buf[len++] = '\r';
+ pos++;
+ break;
+ case 't':
+ buf[len++] = '\t';
+ pos++;
+ break;
+ case 'e':
+ buf[len++] = '\e';
+ pos++;
+ break;
+ case 'x':
+ pos++;
+ val = hex2byte(pos);
+ if (val < 0) {
+ val = hex2num(*pos);
+ if (val < 0)
+ break;
+ buf[len++] = val;
+ pos++;
+ } else {
+ buf[len++] = val;
+ pos += 2;
+ }
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ val = *pos++ - '0';
+ if (*pos >= '0' && *pos <= '7')
+ val = val * 8 + (*pos++ - '0');
+ if (*pos >= '0' && *pos <= '7')
+ val = val * 8 + (*pos++ - '0');
+ buf[len++] = val;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ buf[len++] = *pos++;
+ break;
+ }
+ }
+ return len;
+}
+
+// This is the same algorithm as in InflateUTF8StringToBuffer with Copy and
+// while ignoring invalids.
+// https://mxr.mozilla.org/mozilla-central/source/js/src/vm/CharacterEncoding.cpp#231
+
+static const uint32_t REPLACE_UTF8 = 0xFFFD;
+
+static void
+LossyConvertUTF8toUTF16(const char* aInput, uint32_t aLength, nsAString& aOut)
+{
+ JS::UTF8Chars src(aInput, aLength);
+
+ char16_t dst[aLength]; // Allocating for worst case.
+
+ // Count how many char16_t characters are needed in the inflated string.
+ // |i| is the index into |src|, and |j| is the the index into |dst|.
+ size_t srclen = src.length();
+ uint32_t j = 0;
+ for (uint32_t i = 0; i < srclen; i++, j++) {
+ uint32_t v = uint32_t(src[i]);
+ if (v == uint32_t('\0') && i < srclen - 1) {
+ // If the leading byte is '\0' and it's not the last byte,
+ // just ignore it to prevent from being truncated. This could
+ // be caused by |convertToBytes| (e.g. \x00 would be converted to '\0')
+ j--;
+ continue;
+ }
+ if (!(v & 0x80)) {
+ // ASCII code unit. Simple copy.
+ dst[j] = char16_t(v);
+ } else {
+ // Non-ASCII code unit. Determine its length in bytes (n).
+ uint32_t n = 1;
+ while (v & (0x80 >> n))
+ n++;
+
+ #define INVALID(report, arg, n2) \
+ do { \
+ n = n2; \
+ goto invalidMultiByteCodeUnit; \
+ } while (0)
+
+ // Check the leading byte.
+ if (n < 2 || n > 4)
+ INVALID(ReportInvalidCharacter, i, 1);
+
+ // Check that |src| is large enough to hold an n-byte code unit.
+ if (i + n > srclen)
+ INVALID(ReportBufferTooSmall, /* dummy = */ 0, 1);
+
+ // Check the second byte. From Unicode Standard v6.2, Table 3-7
+ // Well-Formed UTF-8 Byte Sequences.
+ if ((v == 0xE0 && ((uint8_t)src[i + 1] & 0xE0) != 0xA0) || // E0 A0~BF
+ (v == 0xED && ((uint8_t)src[i + 1] & 0xE0) != 0x80) || // ED 80~9F
+ (v == 0xF0 && ((uint8_t)src[i + 1] & 0xF0) == 0x80) || // F0 90~BF
+ (v == 0xF4 && ((uint8_t)src[i + 1] & 0xF0) != 0x80)) // F4 80~8F
+ {
+ INVALID(ReportInvalidCharacter, i, 1);
+ }
+
+ // Check the continuation bytes.
+ for (uint32_t m = 1; m < n; m++)
+ if ((src[i + m] & 0xC0) != 0x80)
+ INVALID(ReportInvalidCharacter, i, m);
+
+ // Determine the code unit's length in char16_t units and act accordingly.
+ v = JS::Utf8ToOneUcs4Char((uint8_t *)&src[i], n);
+ if (v < 0x10000) {
+ // The n-byte UTF8 code unit will fit in a single char16_t.
+ dst[j] = char16_t(v);
+ } else {
+ v -= 0x10000;
+ if (v <= 0xFFFFF) {
+ // The n-byte UTF8 code unit will fit in two char16_t units.
+ dst[j] = char16_t((v >> 10) + 0xD800);
+ j++;
+ dst[j] = char16_t((v & 0x3FF) + 0xDC00);
+ } else {
+ // The n-byte UTF8 code unit won't fit in two char16_t units.
+ INVALID(ReportTooBigCharacter, v, 1);
+ }
+ }
+
+ invalidMultiByteCodeUnit:
+ // Move i to the last byte of the multi-byte code unit; the loop
+ // header will do the final i++ to move to the start of the next
+ // code unit.
+ i += n - 1;
+ }
+ }
+
+ dst[j] = 0;
+ aOut = dst;
+}
+
+// Helper to check we have loaded the hardware shared library.
+#define CHECK_HWLIB(ret) \
+ void* hwLib = GetSharedLibrary(); \
+ if (!hwLib) { \
+ NS_WARNING("No /system/lib/libhardware_legacy.so"); \
+ return ret; \
+ }
+
+#define DEFAULT_IMPL(name, ret, args...) \
+ DEFINE_DLFUNC(name, ret, args...) \
+ ret do_##name(args) { \
+ USE_DLFUNC(name) \
+ return name(args); \
+ }
+
+// ICS implementation.
+class ICSWpaSupplicantImpl : public WpaSupplicantImpl
+{
+public:
+ DEFAULT_IMPL(wifi_load_driver, int32_t, )
+ DEFAULT_IMPL(wifi_unload_driver, int32_t, )
+
+ DEFINE_DLFUNC(wifi_wait_for_event, int32_t, char*, size_t)
+ int32_t do_wifi_wait_for_event(const char *iface, char *buf, size_t len) {
+ USE_DLFUNC(wifi_wait_for_event)
+ return wifi_wait_for_event(buf, len);
+ }
+
+ DEFINE_DLFUNC(wifi_command, int32_t, const char*, char*, size_t*)
+ int32_t do_wifi_command(const char* iface, const char* cmd, char* buf, size_t* len) {
+ USE_DLFUNC(wifi_command)
+ return wifi_command(cmd, buf, len);
+ }
+
+ DEFINE_DLFUNC(wifi_start_supplicant, int32_t, )
+ int32_t do_wifi_start_supplicant(int32_t) {
+ USE_DLFUNC(wifi_start_supplicant)
+ return wifi_start_supplicant();
+ }
+
+ DEFINE_DLFUNC(wifi_stop_supplicant, int32_t)
+ int32_t do_wifi_stop_supplicant(int32_t) {
+ USE_DLFUNC(wifi_stop_supplicant)
+ return wifi_stop_supplicant();
+ }
+
+ DEFINE_DLFUNC(wifi_connect_to_supplicant, int32_t, )
+ int32_t do_wifi_connect_to_supplicant(const char* iface) {
+ USE_DLFUNC(wifi_connect_to_supplicant)
+ return wifi_connect_to_supplicant();
+ }
+
+ DEFINE_DLFUNC(wifi_close_supplicant_connection, void, )
+ void do_wifi_close_supplicant_connection(const char* iface) {
+ USE_DLFUNC(wifi_close_supplicant_connection)
+ return wifi_close_supplicant_connection();
+ }
+};
+
+// JB implementation.
+// We only redefine the methods that have a different signature than on ICS.
+class JBWpaSupplicantImpl : public ICSWpaSupplicantImpl
+{
+public:
+ DEFINE_DLFUNC(wifi_wait_for_event, int32_t, const char*, char*, size_t)
+ int32_t do_wifi_wait_for_event(const char* iface, char* buf, size_t len) {
+ USE_DLFUNC(wifi_wait_for_event)
+ return wifi_wait_for_event(iface, buf, len);
+ }
+
+ DEFINE_DLFUNC(wifi_command, int32_t, const char*, const char*, char*, size_t*)
+ int32_t do_wifi_command(const char* iface, const char* cmd, char* buf, size_t* len) {
+ USE_DLFUNC(wifi_command)
+ return wifi_command(iface, cmd, buf, len);
+ }
+
+ DEFINE_DLFUNC(wifi_start_supplicant, int32_t, int32_t)
+ int32_t do_wifi_start_supplicant(int32_t arg) {
+ USE_DLFUNC(wifi_start_supplicant)
+ return wifi_start_supplicant(arg);
+ }
+
+ DEFINE_DLFUNC(wifi_stop_supplicant, int32_t, int32_t)
+ int32_t do_wifi_stop_supplicant(int32_t arg) {
+ USE_DLFUNC(wifi_stop_supplicant)
+ return wifi_stop_supplicant(arg);
+ }
+
+ DEFINE_DLFUNC(wifi_connect_to_supplicant, int32_t, const char*)
+ int32_t do_wifi_connect_to_supplicant(const char* iface) {
+ USE_DLFUNC(wifi_connect_to_supplicant)
+ return wifi_connect_to_supplicant(iface);
+ }
+
+ DEFINE_DLFUNC(wifi_close_supplicant_connection, void, const char*)
+ void do_wifi_close_supplicant_connection(const char* iface) {
+ USE_DLFUNC(wifi_close_supplicant_connection)
+ wifi_close_supplicant_connection(iface);
+ }
+};
+
+// KK implementation.
+// We only redefine the methods that have a different signature than on ICS.
+class KKWpaSupplicantImpl : public ICSWpaSupplicantImpl
+{
+public:
+ DEFINE_DLFUNC(wifi_start_supplicant, int32_t, int32_t)
+ int32_t do_wifi_start_supplicant(int32_t arg) {
+ USE_DLFUNC(wifi_start_supplicant)
+ return wifi_start_supplicant(arg);
+ }
+
+ DEFINE_DLFUNC(wifi_stop_supplicant, int32_t, int32_t)
+ int32_t do_wifi_stop_supplicant(int32_t arg) {
+ USE_DLFUNC(wifi_stop_supplicant)
+ return wifi_stop_supplicant(arg);
+ }
+
+ DEFINE_DLFUNC(wifi_command, int32_t, const char*, char*, size_t*)
+ int32_t do_wifi_command(const char* iface, const char* cmd, char* buf, size_t* len) {
+ char command[COMMAND_SIZE];
+ if (!strcmp(iface, "p2p0")) {
+ // Commands for p2p0 interface don't need prefix
+ SprintfLiteral(command, "%s", cmd);
+ }
+ else {
+ SprintfLiteral(command, "IFNAME=%s %s", iface, cmd);
+ }
+ USE_DLFUNC(wifi_command)
+ return wifi_command(command, buf, len);
+ }
+};
+
+// Concrete class to use to access the wpa supplicant.
+WpaSupplicant::WpaSupplicant()
+{
+ char propVersion[PROPERTY_VALUE_MAX];
+ property_get("ro.build.version.sdk", propVersion, "0");
+ mSdkVersion = strtol(propVersion, nullptr, 10);
+
+ if (mSdkVersion < 16) {
+ mImpl = MakeUnique<ICSWpaSupplicantImpl>();
+ } else if (mSdkVersion < 19) {
+ mImpl = MakeUnique<JBWpaSupplicantImpl>();
+ } else {
+ mImpl = MakeUnique<KKWpaSupplicantImpl>();
+ }
+ mWifiHotspotUtils = MakeUnique<WifiHotspotUtils>();
+};
+
+void WpaSupplicant::WaitForEvent(nsAString& aEvent, const nsCString& aInterface)
+{
+ CHECK_HWLIB()
+
+ char buffer[BUFFER_SIZE];
+ int32_t ret = mImpl->do_wifi_wait_for_event(aInterface.get(), buffer, BUFFER_SIZE);
+ CheckBuffer(buffer, ret, aEvent);
+}
+
+#define GET_CHAR(prop) NS_ConvertUTF16toUTF8(aOptions.prop).get()
+
+/**
+ * Make a subnet mask.
+ */
+uint32_t WpaSupplicant::MakeMask(uint32_t len) {
+ uint32_t mask = 0;
+ for (uint32_t i = 0; i < len; ++i) {
+ mask |= (0x80000000 >> i);
+ }
+ return ntohl(mask);
+}
+
+bool WpaSupplicant::ExecuteCommand(CommandOptions aOptions,
+ WifiResultOptions& aResult,
+ const nsCString& aInterface)
+{
+ CHECK_HWLIB(false)
+
+ if (!mWifiHotspotUtils->GetSharedLibrary()) {
+ return false;
+ }
+
+ // Always correlate the opaque ids.
+ aResult.mId = aOptions.mId;
+
+ if (aOptions.mCmd.EqualsLiteral("command")) {
+ size_t len = BUFFER_SIZE - 1;
+ char buffer[BUFFER_SIZE];
+ NS_ConvertUTF16toUTF8 request(aOptions.mRequest);
+ aResult.mStatus = mImpl->do_wifi_command(aInterface.get(), request.get(), buffer, &len);
+ nsString value;
+ if (aResult.mStatus == 0) {
+ if (buffer[len - 1] == '\n') { // remove trailing new lines.
+ len--;
+ }
+ buffer[len] = '\0';
+ CheckBuffer(buffer, len, value);
+ }
+ aResult.mReply = value;
+ } else if (aOptions.mCmd.EqualsLiteral("close_supplicant_connection")) {
+ mImpl->do_wifi_close_supplicant_connection(aInterface.get());
+ } else if (aOptions.mCmd.EqualsLiteral("load_driver")) {
+ aResult.mStatus = mImpl->do_wifi_load_driver();
+ } else if (aOptions.mCmd.EqualsLiteral("unload_driver")) {
+ aResult.mStatus = mImpl->do_wifi_unload_driver();
+ } else if (aOptions.mCmd.EqualsLiteral("start_supplicant")) {
+ aResult.mStatus = mImpl->do_wifi_start_supplicant(GetWifiP2pSupported() ? 1 : 0);
+ } else if (aOptions.mCmd.EqualsLiteral("stop_supplicant")) {
+ aResult.mStatus = mImpl->do_wifi_stop_supplicant(0);
+ } else if (aOptions.mCmd.EqualsLiteral("connect_to_supplicant")) {
+ aResult.mStatus = mImpl->do_wifi_connect_to_supplicant(aInterface.get());
+ } else if (aOptions.mCmd.EqualsLiteral("hostapd_command")) {
+ size_t len = BUFFER_SIZE - 1;
+ char buffer[BUFFER_SIZE];
+ NS_ConvertUTF16toUTF8 request(aOptions.mRequest);
+ aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_command(request.get(),
+ buffer,
+ &len);
+ nsString value;
+ if (aResult.mStatus == 0) {
+ if (buffer[len - 1] == '\n') { // remove trailing new lines.
+ len--;
+ }
+ buffer[len] = '\0';
+ CheckBuffer(buffer, len, value);
+ }
+ aResult.mReply = value;
+ } else if (aOptions.mCmd.EqualsLiteral("hostapd_get_stations")) {
+ aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_get_stations();
+ } else if (aOptions.mCmd.EqualsLiteral("connect_to_hostapd")) {
+ aResult.mStatus = mWifiHotspotUtils->do_wifi_connect_to_hostapd();
+ } else if (aOptions.mCmd.EqualsLiteral("close_hostapd_connection")) {
+ aResult.mStatus = mWifiHotspotUtils->do_wifi_close_hostapd_connection();
+
+ } else {
+ NS_WARNING("WpaSupplicant::ExecuteCommand : Unknown command");
+ printf_stderr("WpaSupplicant::ExecuteCommand : Unknown command: %s",
+ NS_ConvertUTF16toUTF8(aOptions.mCmd).get());
+ return false;
+ }
+
+ return true;
+}
+
+// Checks the buffer and do the utf processing.
+void
+WpaSupplicant::CheckBuffer(char* buffer, int32_t length,
+ nsAString& aEvent)
+{
+ if (length <= 0 || length >= (BUFFER_SIZE - 1)) {
+ NS_WARNING("WpaSupplicant::CheckBuffer: Invalid buffer length");
+ return;
+ }
+
+ if (mSdkVersion < 18) {
+ buffer[length] = 0;
+ LossyConvertUTF8toUTF16(buffer, length, aEvent);
+ return;
+ }
+
+ // After Android JB4.3, the SSIDs have been converted into printable form.
+ // In most of cases, SSIDs do not use unprintable characters, but IEEE 802.11
+ // standard does not limit the used character set, so anything could be used
+ // in an SSID. Convert it to raw data form here.
+ char bytesBuffer[BUFFER_SIZE];
+ uint32_t bytes = convertToBytes(bytesBuffer, length, buffer);
+ if (bytes <= 0 || bytes >= BUFFER_SIZE) {
+ NS_WARNING("WpaSupplicant::CheckBuffer: Invalid bytesbuffer length");
+ return;
+ }
+ bytesBuffer[bytes] = 0;
+ LossyConvertUTF8toUTF16(bytesBuffer, bytes, aEvent);
+}
diff --git a/dom/wifi/WifiUtils.h b/dom/wifi/WifiUtils.h
new file mode 100644
index 000000000..a83ba9c15
--- /dev/null
+++ b/dom/wifi/WifiUtils.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Abstraction on top of the wifi support from libhardware_legacy that we
+ * use to talk to the wpa_supplicant.
+ */
+
+#ifndef WifiUtils_h
+#define WifiUtils_h
+
+#include "nsString.h"
+#include "mozilla/dom/WifiOptionsBinding.h"
+#include "mozilla/UniquePtr.h"
+#include "WifiHotspotUtils.h"
+
+// Needed to add a copy constructor to WifiCommandOptions.
+struct CommandOptions
+{
+public:
+ CommandOptions(const mozilla::dom::WifiCommandOptions& aOther) {
+
+#define COPY_OPT_FIELD(prop, defaultValue) \
+ if (aOther.prop.WasPassed()) { \
+ prop = aOther.prop.Value(); \
+ } else { \
+ prop = defaultValue; \
+ }
+
+#define COPY_FIELD(prop) prop = aOther.prop;
+ COPY_FIELD(mId)
+ COPY_FIELD(mCmd)
+ COPY_OPT_FIELD(mRequest, EmptyString())
+
+#undef COPY_OPT_FIELD
+#undef COPY_FIELD
+ }
+
+ // All the fields, not Optional<> anymore to get copy constructors.
+ nsString mCmd;
+ int32_t mId;
+ nsString mRequest;
+};
+
+// Abstract class that exposes libhardware_legacy functions we need for
+// wifi management.
+// We use the ICS signatures here since they are likely more future-proof.
+class WpaSupplicantImpl
+{
+public:
+ // Suppress warning from |UniquePtr|
+ virtual ~WpaSupplicantImpl() {}
+
+ virtual int32_t
+ do_wifi_wait_for_event(const char *iface, char *buf, size_t len) = 0; // KK == ICS != JB
+
+ virtual int32_t
+ do_wifi_command(const char* iface, const char* cmd, char* buff, size_t* len) = 0; // KK == ICS != JB
+
+ virtual int32_t
+ do_wifi_load_driver() = 0;
+
+ virtual int32_t
+ do_wifi_unload_driver() = 0;
+
+ virtual int32_t
+ do_wifi_start_supplicant(int32_t) = 0; // ICS != JB == KK
+
+ virtual int32_t
+ do_wifi_stop_supplicant(int32_t) = 0; //ICS != JB == KK
+
+ virtual int32_t
+ do_wifi_connect_to_supplicant(const char* iface) = 0; // KK == ICS != JB
+
+ virtual void
+ do_wifi_close_supplicant_connection(const char* iface) = 0; // KK == ICS != JB
+};
+
+// Concrete class to use to access the wpa supplicant.
+class WpaSupplicant final
+{
+public:
+ WpaSupplicant();
+
+ // Use nsCString as the type of aInterface to guarantee it's
+ // null-terminated so that we can pass it to c API without
+ // conversion
+ void WaitForEvent(nsAString& aEvent, const nsCString& aInterface);
+ bool ExecuteCommand(CommandOptions aOptions,
+ mozilla::dom::WifiResultOptions& result,
+ const nsCString& aInterface);
+
+private:
+ UniquePtr<WpaSupplicantImpl> mImpl;
+ UniquePtr<WifiHotspotUtils> mWifiHotspotUtils;
+
+ uint32_t mSdkVersion;
+
+protected:
+ void CheckBuffer(char* buffer, int32_t length, nsAString& aEvent);
+ uint32_t MakeMask(uint32_t len);
+};
+
+#endif // WifiUtils_h
diff --git a/dom/wifi/WifiWorker.h b/dom/wifi/WifiWorker.h
new file mode 100644
index 000000000..2cd89c3e7
--- /dev/null
+++ b/dom/wifi/WifiWorker.h
@@ -0,0 +1,9 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define NS_WIFIWORKER_CID \
+{ 0xA14E8977, 0xD259, 0x433A, \
+ { 0xA8, 0x8D, 0x58, 0xDD, 0x44, 0x65, 0x7E, 0x5B } }
diff --git a/dom/wifi/WifiWorker.js b/dom/wifi/WifiWorker.js
new file mode 100644
index 000000000..243ba8b97
--- /dev/null
+++ b/dom/wifi/WifiWorker.js
@@ -0,0 +1,3928 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/WifiCommand.jsm");
+Cu.import("resource://gre/modules/WifiNetUtil.jsm");
+Cu.import("resource://gre/modules/WifiP2pManager.jsm");
+Cu.import("resource://gre/modules/WifiP2pWorkerObserver.jsm");
+
+var DEBUG = false; // set to true to show debug messages.
+
+const WIFIWORKER_CONTRACTID = "@mozilla.org/wifi/worker;1";
+const WIFIWORKER_CID = Components.ID("{a14e8977-d259-433a-a88d-58dd44657e5b}");
+
+const WIFIWORKER_WORKER = "resource://gre/modules/wifi_worker.js";
+
+const kMozSettingsChangedObserverTopic = "mozsettings-changed";
+
+const MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
+const MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
+const MAX_RETRIES_ON_DHCP_FAILURE = 2;
+
+// Settings DB path for wifi
+const SETTINGS_WIFI_ENABLED = "wifi.enabled";
+const SETTINGS_WIFI_DEBUG_ENABLED = "wifi.debugging.enabled";
+// Settings DB path for Wifi tethering.
+const SETTINGS_WIFI_TETHERING_ENABLED = "tethering.wifi.enabled";
+const SETTINGS_WIFI_SSID = "tethering.wifi.ssid";
+const SETTINGS_WIFI_SECURITY_TYPE = "tethering.wifi.security.type";
+const SETTINGS_WIFI_SECURITY_PASSWORD = "tethering.wifi.security.password";
+const SETTINGS_WIFI_IP = "tethering.wifi.ip";
+const SETTINGS_WIFI_PREFIX = "tethering.wifi.prefix";
+const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip";
+const SETTINGS_WIFI_DHCPSERVER_ENDIP = "tethering.wifi.dhcpserver.endip";
+const SETTINGS_WIFI_DNS1 = "tethering.wifi.dns1";
+const SETTINGS_WIFI_DNS2 = "tethering.wifi.dns2";
+
+// Settings DB path for USB tethering.
+const SETTINGS_USB_DHCPSERVER_STARTIP = "tethering.usb.dhcpserver.startip";
+const SETTINGS_USB_DHCPSERVER_ENDIP = "tethering.usb.dhcpserver.endip";
+
+// Default value for WIFI tethering.
+const DEFAULT_WIFI_IP = "192.168.1.1";
+const DEFAULT_WIFI_PREFIX = "24";
+const DEFAULT_WIFI_DHCPSERVER_STARTIP = "192.168.1.10";
+const DEFAULT_WIFI_DHCPSERVER_ENDIP = "192.168.1.30";
+const DEFAULT_WIFI_SSID = "FirefoxHotspot";
+const DEFAULT_WIFI_SECURITY_TYPE = "open";
+const DEFAULT_WIFI_SECURITY_PASSWORD = "1234567890";
+const DEFAULT_DNS1 = "8.8.8.8";
+const DEFAULT_DNS2 = "8.8.4.4";
+
+// Default value for USB tethering.
+const DEFAULT_USB_DHCPSERVER_STARTIP = "192.168.0.10";
+const DEFAULT_USB_DHCPSERVER_ENDIP = "192.168.0.30";
+
+const WIFI_FIRMWARE_AP = "AP";
+const WIFI_FIRMWARE_STATION = "STA";
+const WIFI_SECURITY_TYPE_NONE = "open";
+const WIFI_SECURITY_TYPE_WPA_PSK = "wpa-psk";
+const WIFI_SECURITY_TYPE_WPA2_PSK = "wpa2-psk";
+
+const NETWORK_INTERFACE_UP = "up";
+const NETWORK_INTERFACE_DOWN = "down";
+
+const DEFAULT_WLAN_INTERFACE = "wlan0";
+
+const DRIVER_READY_WAIT = 2000;
+
+const SUPP_PROP = "init.svc.wpa_supplicant";
+const WPA_SUPPLICANT = "wpa_supplicant";
+const DHCP_PROP = "init.svc.dhcpcd";
+const DHCP = "dhcpcd";
+
+const MODE_ESS = 0;
+const MODE_IBSS = 1;
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
+ "@mozilla.org/network/manager;1",
+ "nsINetworkManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+ "@mozilla.org/network/service;1",
+ "nsINetworkService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
+ "@mozilla.org/settingsService;1",
+ "nsISettingsService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gTetheringService",
+ "@mozilla.org/tethering/service;1",
+ "nsITetheringService");
+
+// A note about errors and error handling in this file:
+// The libraries that we use in this file are intended for C code. For
+// C code, it is natural to return -1 for errors and 0 for success.
+// Therefore, the code that interacts directly with the worker uses this
+// convention (note: command functions do get boolean results since the
+// command always succeeds and we do a string/boolean check for the
+// expected results).
+var WifiManager = (function() {
+ var manager = {};
+
+ function getStartupPrefs() {
+ return {
+ sdkVersion: parseInt(libcutils.property_get("ro.build.version.sdk"), 10),
+ unloadDriverEnabled: libcutils.property_get("ro.moz.wifi.unloaddriver") === "1",
+ schedScanRecovery: libcutils.property_get("ro.moz.wifi.sched_scan_recover") === "false" ? false : true,
+ driverDelay: libcutils.property_get("ro.moz.wifi.driverDelay"),
+ p2pSupported: libcutils.property_get("ro.moz.wifi.p2p_supported") === "1",
+ eapSimSupported: libcutils.property_get("ro.moz.wifi.eapsim_supported") === "1",
+ ibssSupported: libcutils.property_get("ro.moz.wifi.ibss_supported", "true") === "true",
+ ifname: libcutils.property_get("wifi.interface")
+ };
+ }
+
+ let {sdkVersion, unloadDriverEnabled, schedScanRecovery,
+ driverDelay, p2pSupported, eapSimSupported, ibssSupported, ifname} = getStartupPrefs();
+
+ let capabilities = {
+ security: ["OPEN", "WEP", "WPA-PSK", "WPA-EAP"],
+ eapMethod: ["PEAP", "TTLS", "TLS"],
+ eapPhase2: ["MSCHAPV2"],
+ certificate: ["SERVER"],
+ mode: [MODE_ESS]
+ };
+ if (eapSimSupported) {
+ capabilities.eapMethod.unshift("SIM");
+ }
+ if (ibssSupported) {
+ capabilities.mode.push(MODE_IBSS);
+ }
+
+ let wifiListener = {
+ onWaitEvent: function(event, iface) {
+ if (manager.ifname === iface && handleEvent(event)) {
+ waitForEvent(iface);
+ } else if (p2pSupported) {
+ // Please refer to
+ // http://androidxref.com/4.4.2_r1/xref/frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java#519
+ // for interface event mux/demux rules. In short words, both
+ // 'p2p0' and 'p2p-' should go to Wifi P2P state machine.
+ if (WifiP2pManager.INTERFACE_NAME === iface || -1 !== iface.indexOf('p2p-')) {
+ // If the connection is closed, wifi.c::wifi_wait_for_event()
+ // will still return 'CTRL-EVENT-TERMINATING - connection closed'
+ // rather than blocking. So when we see this special event string,
+ // just return immediately.
+ const TERMINATED_EVENT = 'CTRL-EVENT-TERMINATING - connection closed';
+ if (-1 !== event.indexOf(TERMINATED_EVENT)) {
+ return;
+ }
+ p2pManager.handleEvent(event);
+ waitForEvent(iface);
+ }
+ }
+ },
+
+ onCommand: function(event, iface) {
+ onmessageresult(event, iface);
+ }
+ }
+
+ manager.ifname = ifname;
+ manager.connectToSupplicant = false;
+ // Emulator build runs to here.
+ // The debug() should only be used after WifiManager.
+ if (!ifname) {
+ manager.ifname = DEFAULT_WLAN_INTERFACE;
+ }
+ manager.schedScanRecovery = schedScanRecovery;
+ manager.driverDelay = driverDelay ? parseInt(driverDelay, 10) : DRIVER_READY_WAIT;
+
+ // Regular Wifi stuff.
+ var netUtil = WifiNetUtil(controlMessage);
+ var wifiCommand = WifiCommand(controlMessage, manager.ifname, sdkVersion);
+
+ // Wifi P2P stuff
+ var p2pManager;
+ if (p2pSupported) {
+ let p2pCommand = WifiCommand(controlMessage, WifiP2pManager.INTERFACE_NAME, sdkVersion);
+ p2pManager = WifiP2pManager(p2pCommand, netUtil);
+ }
+
+ let wifiService = Cc["@mozilla.org/wifi/service;1"];
+ if (wifiService) {
+ wifiService = wifiService.getService(Ci.nsIWifiProxyService);
+ let interfaces = [manager.ifname];
+ if (p2pSupported) {
+ interfaces.push(WifiP2pManager.INTERFACE_NAME);
+ }
+ wifiService.start(wifiListener, interfaces, interfaces.length);
+ } else {
+ debug("No wifi service component available!");
+ }
+
+ // Callbacks to invoke when a reply arrives from the wifi service.
+ var controlCallbacks = Object.create(null);
+ var idgen = 0;
+
+ function controlMessage(obj, callback) {
+ var id = idgen++;
+ obj.id = id;
+ if (callback) {
+ controlCallbacks[id] = callback;
+ }
+ wifiService.sendCommand(obj, obj.iface);
+ }
+
+ let onmessageresult = function(data, iface) {
+ var id = data.id;
+ var callback = controlCallbacks[id];
+ if (callback) {
+ callback(data);
+ delete controlCallbacks[id];
+ }
+ }
+
+ // Polling the status worker
+ var recvErrors = 0;
+
+ function waitForEvent(iface) {
+ wifiService.waitForEvent(iface);
+ }
+
+ // Commands to the control worker.
+
+ var driverLoaded = false;
+
+ function loadDriver(callback) {
+ if (driverLoaded) {
+ callback(0);
+ return;
+ }
+
+ wifiCommand.loadDriver(function (status) {
+ driverLoaded = (status >= 0);
+ callback(status)
+ });
+ }
+
+ function unloadDriver(type, callback) {
+ if (!unloadDriverEnabled) {
+ // Unloading drivers is generally unnecessary and
+ // can trigger bugs in some drivers.
+ // On properly written drivers, bringing the interface
+ // down powers down the interface.
+ if (type === WIFI_FIRMWARE_STATION) {
+ notify("supplicantlost", { success: true });
+ }
+ callback(0);
+ return;
+ }
+
+ wifiCommand.unloadDriver(function(status) {
+ driverLoaded = (status < 0);
+ if (type === WIFI_FIRMWARE_STATION) {
+ notify("supplicantlost", { success: true });
+ }
+ callback(status);
+ });
+ }
+
+ // A note about background scanning:
+ // Normally, background scanning shouldn't be necessary as wpa_supplicant
+ // has the capability to automatically schedule its own scans at appropriate
+ // intervals. However, with some drivers, this appears to get stuck after
+ // three scans, so we enable the driver's background scanning to work around
+ // that when we're not connected to any network. This ensures that we'll
+ // automatically reconnect to networks if one falls out of range.
+ var reEnableBackgroundScan = false;
+
+ // NB: This is part of the internal API.
+ manager.backgroundScanEnabled = false;
+ function setBackgroundScan(enable, callback) {
+ var doEnable = (enable === "ON");
+ if (doEnable === manager.backgroundScanEnabled) {
+ callback(false, true);
+ return;
+ }
+
+ manager.backgroundScanEnabled = doEnable;
+ wifiCommand.setBackgroundScan(manager.backgroundScanEnabled, callback);
+ }
+
+ var scanModeActive = false;
+
+ function scan(forceActive, callback) {
+ if (forceActive && !scanModeActive) {
+ // Note: we ignore errors from doSetScanMode.
+ wifiCommand.doSetScanMode(true, function(ignore) {
+ setBackgroundScan("OFF", function(turned, ignore) {
+ reEnableBackgroundScan = turned;
+ manager.handlePreWifiScan();
+ wifiCommand.scan(function(ok) {
+ wifiCommand.doSetScanMode(false, function(ignore) {
+ // The result of scanCommand is the result of the actual SCAN
+ // request.
+ callback(ok);
+ });
+ });
+ });
+ });
+ return;
+ }
+ manager.handlePreWifiScan();
+ wifiCommand.scan(callback);
+ }
+
+ var debugEnabled = false;
+
+ function syncDebug() {
+ if (debugEnabled !== DEBUG) {
+ let wanted = DEBUG;
+ wifiCommand.setLogLevel(wanted ? "DEBUG" : "INFO", function(ok) {
+ if (ok)
+ debugEnabled = wanted;
+ });
+ if (p2pSupported && p2pManager) {
+ p2pManager.setDebug(DEBUG);
+ }
+ }
+ }
+
+ function getDebugEnabled(callback) {
+ wifiCommand.getLogLevel(function(level) {
+ if (level === null) {
+ debug("Unable to get wpa_supplicant's log level");
+ callback(false);
+ return;
+ }
+
+ var lines = level.split("\n");
+ for (let i = 0; i < lines.length; ++i) {
+ let match = /Current level: (.*)/.exec(lines[i]);
+ if (match) {
+ debugEnabled = match[1].toLowerCase() === "debug";
+ callback(true);
+ return;
+ }
+ }
+
+ // If we're here, we didn't get the current level.
+ callback(false);
+ });
+ }
+
+ function setScanMode(setActive, callback) {
+ scanModeActive = setActive;
+ wifiCommand.doSetScanMode(setActive, callback);
+ }
+
+ var httpProxyConfig = Object.create(null);
+
+ /**
+ * Given a network, configure http proxy when using wifi.
+ * @param network A network object to update http proxy
+ * @param info Info should have following field:
+ * - httpProxyHost ip address of http proxy.
+ * - httpProxyPort port of http proxy, set 0 to use default port 8080.
+ * @param callback callback function.
+ */
+ function configureHttpProxy(network, info, callback) {
+ if (!network)
+ return;
+
+ let networkKey = getNetworkKey(network);
+
+ if (!info || info.httpProxyHost === "") {
+ delete httpProxyConfig[networkKey];
+ } else {
+ httpProxyConfig[networkKey] = network;
+ httpProxyConfig[networkKey].httpProxyHost = info.httpProxyHost;
+ httpProxyConfig[networkKey].httpProxyPort = info.httpProxyPort;
+ }
+
+ callback(true);
+ }
+
+ function getHttpProxyNetwork(network) {
+ if (!network)
+ return null;
+
+ let networkKey = getNetworkKey(network);
+ return httpProxyConfig[networkKey];
+ }
+
+ function setHttpProxy(network) {
+ if (!network)
+ return;
+
+ // If we got here, arg network must be the currentNetwork, so we just update
+ // WifiNetworkInterface correspondingly and notify NetworkManager.
+ WifiNetworkInterface.httpProxyHost = network.httpProxyHost;
+ WifiNetworkInterface.httpProxyPort = network.httpProxyPort;
+
+ if (WifiNetworkInterface.info.state ==
+ Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
+ gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
+ }
+ }
+
+ var staticIpConfig = Object.create(null);
+ function setStaticIpMode(network, info, callback) {
+ let setNetworkKey = getNetworkKey(network);
+ let curNetworkKey = null;
+ let currentNetwork = Object.create(null);
+ currentNetwork.netId = manager.connectionInfo.id;
+
+ manager.getNetworkConfiguration(currentNetwork, function () {
+ curNetworkKey = getNetworkKey(currentNetwork);
+
+ // Add additional information to static ip configuration
+ // It is used to compatiable with information dhcp callback.
+ info.ipaddr = netHelpers.stringToIP(info.ipaddr_str);
+ info.gateway = netHelpers.stringToIP(info.gateway_str);
+ info.mask_str = netHelpers.ipToString(netHelpers.makeMask(info.maskLength));
+
+ // Optional
+ info.dns1 = netHelpers.stringToIP(info.dns1_str);
+ info.dns2 = netHelpers.stringToIP(info.dns2_str);
+ info.proxy = netHelpers.stringToIP(info.proxy_str);
+
+ staticIpConfig[setNetworkKey] = info;
+
+ // If the ssid of current connection is the same as configured ssid
+ // It means we need update current connection to use static IP address.
+ if (setNetworkKey == curNetworkKey) {
+ // Use configureInterface directly doesn't work, the network interface
+ // and routing table is changed but still cannot connect to network
+ // so the workaround here is disable interface the enable again to
+ // trigger network reconnect with static ip.
+ gNetworkService.disableInterface(manager.ifname, function (ok) {
+ gNetworkService.enableInterface(manager.ifname, function (ok) {
+ callback(ok);
+ });
+ });
+ return;
+ }
+
+ callback(true);
+ });
+ }
+
+ var dhcpInfo = null;
+
+ function runStaticIp(ifname, key) {
+ debug("Run static ip");
+
+ // Read static ip information from settings.
+ let staticIpInfo;
+
+ if (!(key in staticIpConfig))
+ return;
+
+ staticIpInfo = staticIpConfig[key];
+
+ // Stop dhcpd when use static IP
+ if (dhcpInfo != null) {
+ netUtil.stopDhcp(manager.ifname, function() {});
+ }
+
+ // Set ip, mask length, gateway, dns to network interface
+ gNetworkService.configureInterface( { ifname: ifname,
+ ipaddr: staticIpInfo.ipaddr,
+ mask: staticIpInfo.maskLength,
+ gateway: staticIpInfo.gateway,
+ dns1: staticIpInfo.dns1,
+ dns2: staticIpInfo.dns2 }, function (data) {
+ netUtil.runIpConfig(ifname, staticIpInfo, function(data) {
+ dhcpInfo = data.info;
+ notify("networkconnected", data);
+ });
+ });
+ }
+
+ var suppressEvents = false;
+ function notify(eventName, eventObject) {
+ if (suppressEvents)
+ return;
+ var handler = manager["on" + eventName];
+ if (handler) {
+ if (!eventObject)
+ eventObject = ({});
+ handler.call(eventObject);
+ }
+ }
+
+ function notifyStateChange(fields) {
+ // If we're already in the COMPLETED state, we might receive events from
+ // the supplicant that tell us that we're re-authenticating or reminding
+ // us that we're associated to a network. In those cases, we don't need to
+ // do anything, so just ignore them.
+ if (manager.state === "COMPLETED" &&
+ fields.state !== "DISCONNECTED" &&
+ fields.state !== "INTERFACE_DISABLED" &&
+ fields.state !== "INACTIVE" &&
+ fields.state !== "SCANNING") {
+ return false;
+ }
+
+ // Stop background scanning if we're trying to connect to a network.
+ if (manager.backgroundScanEnabled &&
+ (fields.state === "ASSOCIATING" ||
+ fields.state === "ASSOCIATED" ||
+ fields.state === "FOUR_WAY_HANDSHAKE" ||
+ fields.state === "GROUP_HANDSHAKE" ||
+ fields.state === "COMPLETED")) {
+ setBackgroundScan("OFF", function() {});
+ }
+
+ fields.prevState = manager.state;
+ // Detect wpa_supplicant's loop iterations.
+ manager.supplicantLoopDetection(fields.prevState, fields.state);
+ notify("statechange", fields);
+
+ // Don't update state when and after disabling.
+ if (manager.state === "DISABLING" ||
+ manager.state === "UNINITIALIZED") {
+ return false;
+ }
+
+ manager.state = fields.state;
+ return true;
+ }
+
+ function parseStatus(status) {
+ if (status === null) {
+ debug("Unable to get wpa supplicant's status");
+ return;
+ }
+
+ var ssid;
+ var bssid;
+ var state;
+ var ip_address;
+ var id;
+
+ var lines = status.split("\n");
+ for (let i = 0; i < lines.length; ++i) {
+ let [key, value] = lines[i].split("=");
+ switch (key) {
+ case "wpa_state":
+ state = value;
+ break;
+ case "ssid":
+ ssid = value;
+ break;
+ case "bssid":
+ bssid = value;
+ break;
+ case "ip_address":
+ ip_address = value;
+ break;
+ case "id":
+ id = value;
+ break;
+ }
+ }
+
+ if (bssid && ssid) {
+ manager.connectionInfo.bssid = bssid;
+ manager.connectionInfo.ssid = ssid;
+ manager.connectionInfo.id = id;
+ }
+
+ if (ip_address)
+ dhcpInfo = { ip_address: ip_address };
+
+ notifyStateChange({ state: state, fromStatus: true });
+
+ // If we parse the status and the supplicant has already entered the
+ // COMPLETED state, then we need to set up DHCP right away.
+ if (state === "COMPLETED")
+ onconnected();
+ }
+
+ // try to connect to the supplicant
+ var connectTries = 0;
+ var retryTimer = null;
+ function connectCallback(ok) {
+ if (ok === 0) {
+ // Tell the event worker to start waiting for events.
+ retryTimer = null;
+ connectTries = 0;
+ recvErrors = 0;
+ manager.connectToSupplicant = true;
+ didConnectSupplicant(function(){});
+ return;
+ }
+ if (connectTries++ < 5) {
+ // Try again in 1 seconds.
+ if (!retryTimer)
+ retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ retryTimer.initWithCallback(function(timer) {
+ wifiCommand.connectToSupplicant(connectCallback);
+ }, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
+ return;
+ }
+
+ retryTimer = null;
+ connectTries = 0;
+ notify("supplicantlost", { success: false });
+ }
+
+ manager.connectionDropped = function(callback) {
+ // Reset network interface when connection drop
+ gNetworkService.configureInterface( { ifname: manager.ifname,
+ ipaddr: 0,
+ mask: 0,
+ gateway: 0,
+ dns1: 0,
+ dns2: 0 }, function (data) {
+ });
+
+ // If we got disconnected, kill the DHCP client in preparation for
+ // reconnection.
+ gNetworkService.resetConnections(manager.ifname, function() {
+ netUtil.stopDhcp(manager.ifname, function() {
+ callback();
+ });
+ });
+ }
+
+ manager.start = function() {
+ debug("detected SDK version " + sdkVersion);
+ wifiCommand.connectToSupplicant(connectCallback);
+ }
+
+ let dhcpRequestGen = 0;
+
+ function onconnected() {
+ // For now we do our own DHCP. In the future, this should be handed
+ // off to the Network Manager.
+ let currentNetwork = Object.create(null);
+ currentNetwork.netId = manager.connectionInfo.id;
+
+ manager.getNetworkConfiguration(currentNetwork, function (){
+ let key = getNetworkKey(currentNetwork);
+ if (staticIpConfig &&
+ (key in staticIpConfig) &&
+ staticIpConfig[key].enabled) {
+ debug("Run static ip");
+ runStaticIp(manager.ifname, key);
+ return;
+ }
+ netUtil.runDhcp(manager.ifname, dhcpRequestGen++, function(data, gen) {
+ dhcpInfo = data.info;
+ debug('dhcpRequestGen: ' + dhcpRequestGen + ', gen: ' + gen);
+ if (!dhcpInfo) {
+ if (gen + 1 < dhcpRequestGen) {
+ debug('Do not bother younger DHCP request.');
+ return;
+ }
+ if (++manager.dhcpFailuresCount >= MAX_RETRIES_ON_DHCP_FAILURE) {
+ manager.dhcpFailuresCount = 0;
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ return;
+ }
+ // NB: We have to call disconnect first. Otherwise, we only reauth with
+ // the existing AP and don't retrigger DHCP.
+ manager.disconnect(function() {
+ manager.reassociate(function(){});
+ });
+ return;
+ }
+
+ manager.dhcpFailuresCount = 0;
+ notify("networkconnected", data);
+ });
+ });
+ }
+
+ var supplicantStatesMap = (sdkVersion >= 15) ?
+ ["DISCONNECTED", "INTERFACE_DISABLED", "INACTIVE", "SCANNING",
+ "AUTHENTICATING", "ASSOCIATING", "ASSOCIATED", "FOUR_WAY_HANDSHAKE",
+ "GROUP_HANDSHAKE", "COMPLETED"]
+ :
+ ["DISCONNECTED", "INACTIVE", "SCANNING", "ASSOCIATING",
+ "ASSOCIATED", "FOUR_WAY_HANDSHAKE", "GROUP_HANDSHAKE",
+ "COMPLETED", "DORMANT", "UNINITIALIZED"];
+
+ var driverEventMap = { STOPPED: "driverstopped", STARTED: "driverstarted", HANGED: "driverhung" };
+
+ manager.getNetworkId = function (ssid, callback) {
+ manager.getConfiguredNetworks(function(networks) {
+ if (!networks) {
+ debug("Unable to get configured networks");
+ return callback(null);
+ }
+ for (let net in networks) {
+ let network = networks[net];
+ // Trying to get netId from
+ // 1. network matching SSID if SSID is provided.
+ // 2. current network if no SSID is provided, it's not guaranteed that
+ // current network matches requested SSID.
+ if ((!ssid && network.status === "CURRENT") ||
+ (ssid && network.ssid && ssid === dequote(network.ssid))) {
+ return callback(net);
+ }
+ }
+ callback(null);
+ });
+ }
+
+ function handleWpaEapEvents(event) {
+ if (event.indexOf("CTRL-EVENT-EAP-FAILURE") !== -1) {
+ if (event.indexOf("EAP authentication failed") !== -1) {
+ notify("passwordmaybeincorrect");
+ if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ manager.authenticationFailuresCount = 0;
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ }
+ }
+ return true;
+ }
+ if (event.indexOf("CTRL-EVENT-EAP-TLS-CERT-ERROR") !== -1) {
+ // Cert Error
+ notify("passwordmaybeincorrect");
+ if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ manager.authenticationFailuresCount = 0;
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ }
+ return true;
+ }
+ if (event.indexOf("CTRL-EVENT-EAP-STARTED") !== -1) {
+ notifyStateChange({ state: "AUTHENTICATING" });
+ return true;
+ }
+ return true;
+ }
+
+ // Handle events sent to us by the event worker.
+ function handleEvent(event) {
+ debug("Event coming in: " + event);
+ if (event.indexOf("CTRL-EVENT-") !== 0 && event.indexOf("WPS") !== 0) {
+ // Handle connection fail exception on WEP-128, while password length
+ // is not 5 nor 13 bytes.
+ if (event.indexOf("Association request to the driver failed") !== -1) {
+ notify("passwordmaybeincorrect");
+ if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ manager.authenticationFailuresCount = 0;
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ }
+ return true;
+ }
+
+ if (event.indexOf("WPA:") == 0 &&
+ event.indexOf("pre-shared key may be incorrect") != -1) {
+ notify("passwordmaybeincorrect");
+ }
+
+ // This is ugly, but we need to grab the SSID here. BSSID is not guaranteed
+ // to be provided, so don't grab BSSID here.
+ var match = /Trying to associate with.*SSID[ =]'(.*)'/.exec(event);
+ if (match) {
+ debug("Matched: " + match[1] + "\n");
+ manager.connectionInfo.ssid = match[1];
+ }
+ return true;
+ }
+
+ var space = event.indexOf(" ");
+ var eventData = event.substr(0, space + 1);
+ if (eventData.indexOf("CTRL-EVENT-STATE-CHANGE") === 0) {
+ // Parse the event data.
+ var fields = {};
+ var tokens = event.substr(space + 1).split(" ");
+ for (var n = 0; n < tokens.length; ++n) {
+ var kv = tokens[n].split("=");
+ if (kv.length === 2)
+ fields[kv[0]] = kv[1];
+ }
+ if (!("state" in fields))
+ return true;
+ fields.state = supplicantStatesMap[fields.state];
+
+ // The BSSID field is only valid in the ASSOCIATING and ASSOCIATED
+ // states, except when we "reauth", except this seems to depend on the
+ // driver, so simply check to make sure that we don't have a null BSSID.
+ if (fields.BSSID !== "00:00:00:00:00:00")
+ manager.connectionInfo.bssid = fields.BSSID;
+
+ if (notifyStateChange(fields) && fields.state === "COMPLETED") {
+ onconnected();
+ }
+ return true;
+ }
+ if (eventData.indexOf("CTRL-EVENT-DRIVER-STATE") === 0) {
+ var handlerName = driverEventMap[eventData];
+ if (handlerName)
+ notify(handlerName);
+ return true;
+ }
+ if (eventData.indexOf("CTRL-EVENT-TERMINATING") === 0) {
+ // As long the monitor socket is not closed and we haven't seen too many
+ // recv errors yet, we will keep going for a bit longer.
+ if (event.indexOf("connection closed") === -1 &&
+ event.indexOf("recv error") !== -1 && ++recvErrors < 10)
+ return true;
+
+ notifyStateChange({ state: "DISCONNECTED", BSSID: null, id: -1 });
+
+ // If the supplicant is terminated as commanded, the supplicant lost
+ // notification will be sent after driver unloaded. In such case, the
+ // manager state will be "DISABLING" or "UNINITIALIZED".
+ // So if supplicant terminated with incorrect manager state, implying
+ // unexpected condition, we should notify supplicant lost here.
+ if (manager.state !== "DISABLING" && manager.state !== "UNINITIALIZED") {
+ notify("supplicantlost", { success: true });
+ }
+
+ if (manager.stopSupplicantCallback) {
+ cancelWaitForTerminateEventTimer();
+ // It's possible that the terminating event triggered by timer comes
+ // earlier than the event from wpa_supplicant. Since
+ // stopSupplicantCallback contains async. callbacks, swap it to local
+ // to prevent calling the callback twice.
+ let stopSupplicantCallback = manager.stopSupplicantCallback.bind(manager);
+ manager.stopSupplicantCallback = null;
+ stopSupplicantCallback();
+ stopSupplicantCallback = null;
+ }
+ return false;
+ }
+ if (eventData.indexOf("CTRL-EVENT-DISCONNECTED") === 0) {
+ var token = event.split(" ")[1];
+ var bssid = token.split("=")[1];
+ if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ manager.authenticationFailuresCount = 0;
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ }
+ manager.connectionInfo.bssid = null;
+ manager.connectionInfo.ssid = null;
+ manager.connectionInfo.id = -1;
+ return true;
+ }
+ if (eventData.indexOf("CTRL-EVENT-CONNECTED") === 0) {
+ // Format: CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]
+ var bssid = event.split(" ")[4];
+
+ var keyword = "id=";
+ var id = event.substr(event.indexOf(keyword) + keyword.length).split(" ")[0];
+ // Read current BSSID here, it will always being provided.
+ manager.connectionInfo.id = id;
+ manager.connectionInfo.bssid = bssid;
+ return true;
+ }
+ if (eventData.indexOf("CTRL-EVENT-SCAN-RESULTS") === 0) {
+ debug("Notifying of scan results available");
+ if (reEnableBackgroundScan) {
+ reEnableBackgroundScan = false;
+ setBackgroundScan("ON", function() {});
+ }
+ manager.handlePostWifiScan();
+ notify("scanresultsavailable");
+ return true;
+ }
+ if (eventData.indexOf("CTRL-EVENT-EAP") === 0) {
+ return handleWpaEapEvents(event);
+ }
+ if (eventData.indexOf("CTRL-EVENT-ASSOC-REJECT") === 0) {
+ debug("CTRL-EVENT-ASSOC-REJECT: network error");
+ notify("passwordmaybeincorrect");
+ if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ manager.authenticationFailuresCount = 0;
+ debug("CTRL-EVENT-ASSOC-REJECT: disconnect network");
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ }
+ return true;
+ }
+ if (eventData.indexOf("WPS-TIMEOUT") === 0) {
+ notifyStateChange({ state: "WPS_TIMEOUT", BSSID: null, id: -1 });
+ return true;
+ }
+ if (eventData.indexOf("WPS-FAIL") === 0) {
+ notifyStateChange({ state: "WPS_FAIL", BSSID: null, id: -1 });
+ return true;
+ }
+ if (eventData.indexOf("WPS-OVERLAP-DETECTED") === 0) {
+ notifyStateChange({ state: "WPS_OVERLAP_DETECTED", BSSID: null, id: -1 });
+ return true;
+ }
+ // Unknown event.
+ return true;
+ }
+
+ function setPowerSavingMode(enabled) {
+ let mode = enabled ? "AUTO" : "ACTIVE";
+ // Some wifi drivers may not implement this command. Set power mode
+ // even if suspend optimization command failed.
+ manager.setSuspendOptimizations(enabled, function(ok) {
+ manager.setPowerMode(mode, function() {});
+ });
+ }
+
+ function didConnectSupplicant(callback) {
+ waitForEvent(manager.ifname);
+
+ // Load up the supplicant state.
+ getDebugEnabled(function(ok) {
+ syncDebug();
+ });
+ wifiCommand.status(function(status) {
+ parseStatus(status);
+ notify("supplicantconnection");
+ callback();
+ });
+ // WPA supplicant already connected.
+ manager.setPowerSavingMode(true);
+ if (p2pSupported) {
+ manager.enableP2p(function(success) {});
+ }
+ }
+
+ function prepareForStartup(callback) {
+ let status = libcutils.property_get(DHCP_PROP + "_" + manager.ifname);
+ if (status !== "running") {
+ tryStopSupplicant();
+ return;
+ }
+ manager.connectionDropped(function() {
+ tryStopSupplicant();
+ });
+
+ // Ignore any errors and kill any currently-running supplicants. On some
+ // phones, stopSupplicant won't work for a supplicant that we didn't
+ // start, so we hand-roll it here.
+ function tryStopSupplicant () {
+ let status = libcutils.property_get(SUPP_PROP);
+ if (status !== "running") {
+ callback();
+ return;
+ }
+ suppressEvents = true;
+ wifiCommand.killSupplicant(function() {
+ gNetworkService.disableInterface(manager.ifname, function (ok) {
+ suppressEvents = false;
+ callback();
+ });
+ });
+ }
+ }
+
+ // Initial state.
+ manager.state = "UNINITIALIZED";
+ manager.tetheringState = "UNINITIALIZED";
+ manager.supplicantStarted = false;
+ manager.connectionInfo = { ssid: null, bssid: null, id: -1 };
+ manager.authenticationFailuresCount = 0;
+ manager.loopDetectionCount = 0;
+ manager.dhcpFailuresCount = 0;
+ manager.stopSupplicantCallback = null;
+
+ manager.__defineGetter__("enabled", function() {
+ switch (manager.state) {
+ case "UNINITIALIZED":
+ case "INITIALIZING":
+ case "DISABLING":
+ return false;
+ default:
+ return true;
+ }
+ });
+
+ var waitForTerminateEventTimer = null;
+ function cancelWaitForTerminateEventTimer() {
+ if (waitForTerminateEventTimer) {
+ waitForTerminateEventTimer.cancel();
+ waitForTerminateEventTimer = null;
+ }
+ };
+ function createWaitForTerminateEventTimer(onTimeout) {
+ if (waitForTerminateEventTimer) {
+ return;
+ }
+ waitForTerminateEventTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ waitForTerminateEventTimer.initWithCallback(onTimeout,
+ 4000,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ };
+
+ var waitForDriverReadyTimer = null;
+ function cancelWaitForDriverReadyTimer() {
+ if (waitForDriverReadyTimer) {
+ waitForDriverReadyTimer.cancel();
+ waitForDriverReadyTimer = null;
+ }
+ };
+ function createWaitForDriverReadyTimer(onTimeout) {
+ waitForDriverReadyTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ waitForDriverReadyTimer.initWithCallback(onTimeout,
+ manager.driverDelay,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ };
+
+ // Public interface of the wifi service.
+ manager.setWifiEnabled = function(enabled, callback) {
+ if (enabled === manager.isWifiEnabled(manager.state)) {
+ callback("no change");
+ return;
+ }
+
+ if (enabled) {
+ manager.state = "INITIALIZING";
+ // Register as network interface.
+ WifiNetworkInterface.info.name = manager.ifname;
+ if (!WifiNetworkInterface.registered) {
+ gNetworkManager.registerNetworkInterface(WifiNetworkInterface);
+ WifiNetworkInterface.registered = true;
+ }
+ WifiNetworkInterface.info.state =
+ Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED;
+ WifiNetworkInterface.info.ips = [];
+ WifiNetworkInterface.info.prefixLengths = [];
+ WifiNetworkInterface.info.gateways = [];
+ WifiNetworkInterface.info.dnses = [];
+ gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
+
+ prepareForStartup(function() {
+ loadDriver(function (status) {
+ if (status < 0) {
+ callback(status);
+ manager.state = "UNINITIALIZED";
+ return;
+ }
+ // This command is mandatory for Nexus 4. But some devices like
+ // Galaxy S2 don't support it. Continue to start wpa_supplicant
+ // even if we fail to set wifi operation mode to station.
+ gNetworkService.setWifiOperationMode(manager.ifname,
+ WIFI_FIRMWARE_STATION,
+ function (status) {
+
+ function startSupplicantInternal() {
+ wifiCommand.startSupplicant(function (status) {
+ if (status < 0) {
+ unloadDriver(WIFI_FIRMWARE_STATION, function() {
+ callback(status);
+ });
+ manager.state = "UNINITIALIZED";
+ return;
+ }
+
+ manager.supplicantStarted = true;
+ gNetworkService.enableInterface(manager.ifname, function (ok) {
+ callback(ok ? 0 : -1);
+ });
+ });
+ }
+
+ function doStartSupplicant() {
+ cancelWaitForDriverReadyTimer();
+
+ if (!manager.connectToSupplicant) {
+ startSupplicantInternal();
+ return;
+ }
+ wifiCommand.closeSupplicantConnection(function () {
+ manager.connectToSupplicant = false;
+ // closeSupplicantConnection() will trigger onsupplicantlost
+ // and set manager.state to "UNINITIALIZED", we have to
+ // restore it here.
+ manager.state = "INITIALIZING";
+ startSupplicantInternal();
+ });
+ }
+ // Driver startup on certain platforms takes longer than it takes for us
+ // to return from loadDriver, so wait 2 seconds before starting
+ // the supplicant to give it a chance to start.
+ if (manager.driverDelay > 0) {
+ createWaitForDriverReadyTimer(doStartSupplicant);
+ } else {
+ doStartSupplicant();
+ }
+ });
+ });
+ });
+ } else {
+ manager.state = "DISABLING";
+ // Note these following calls ignore errors. If we fail to kill the
+ // supplicant gracefully, then we need to continue telling it to die
+ // until it does.
+ let doDisableWifi = function() {
+ manager.stopSupplicantCallback = (function () {
+ wifiCommand.stopSupplicant(function (status) {
+ wifiCommand.closeSupplicantConnection(function() {
+ manager.connectToSupplicant = false;
+ manager.state = "UNINITIALIZED";
+ gNetworkService.disableInterface(manager.ifname, function (ok) {
+ unloadDriver(WIFI_FIRMWARE_STATION, callback);
+ });
+ });
+ });
+ }).bind(this);
+
+ let terminateEventCallback = (function() {
+ handleEvent("CTRL-EVENT-TERMINATING");
+ }).bind(this);
+ createWaitForTerminateEventTimer(terminateEventCallback);
+
+ // We are going to terminate the connection between wpa_supplicant.
+ // Stop the polling timer immediately to prevent connection info update
+ // command blocking in control thread until socket timeout.
+ notify("stopconnectioninfotimer");
+
+ wifiCommand.terminateSupplicant(function (ok) {
+ manager.connectionDropped(function () {
+ });
+ });
+ }
+
+ if (p2pSupported) {
+ p2pManager.setEnabled(false, { onDisabled: doDisableWifi });
+ } else {
+ doDisableWifi();
+ }
+ }
+ }
+
+ var wifiHotspotStatusTimer = null;
+ function cancelWifiHotspotStatusTimer() {
+ if (wifiHotspotStatusTimer) {
+ wifiHotspotStatusTimer.cancel();
+ wifiHotspotStatusTimer = null;
+ }
+ }
+
+ function createWifiHotspotStatusTimer(onTimeout) {
+ wifiHotspotStatusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ wifiHotspotStatusTimer.init(onTimeout, 5000, Ci.nsITimer.TYPE_REPEATING_SLACK);
+ }
+
+ // Get wifi interface and load wifi driver when enable Ap mode.
+ manager.setWifiApEnabled = function(enabled, configuration, callback) {
+ if (enabled === manager.isWifiTetheringEnabled(manager.tetheringState)) {
+ callback("no change");
+ return;
+ }
+
+ if (enabled) {
+ manager.tetheringState = "INITIALIZING";
+ loadDriver(function (status) {
+ if (status < 0) {
+ callback();
+ manager.tetheringState = "UNINITIALIZED";
+ if (wifiHotspotStatusTimer) {
+ cancelWifiHotspotStatusTimer();
+ wifiCommand.closeHostapdConnection(function(result) {
+ });
+ }
+ return;
+ }
+
+ function getWifiHotspotStatus() {
+ wifiCommand.hostapdGetStations(function(result) {
+ notify("stationinfoupdate", {station: result});
+ });
+ }
+
+ function doStartWifiTethering() {
+ cancelWaitForDriverReadyTimer();
+ WifiNetworkInterface.info.name =
+ libcutils.property_get("wifi.tethering.interface", manager.ifname);
+ gTetheringService.setWifiTethering(enabled,
+ WifiNetworkInterface.info.name,
+ configuration, function(result) {
+ if (result) {
+ manager.tetheringState = "UNINITIALIZED";
+ } else {
+ manager.tetheringState = "COMPLETED";
+ wifiCommand.connectToHostapd(function(result) {
+ if (result) {
+ return;
+ }
+ // Create a timer to track the connection status.
+ createWifiHotspotStatusTimer(getWifiHotspotStatus);
+ });
+ }
+ // Pop out current request.
+ callback();
+ // Should we fire a dom event if we fail to set wifi tethering ?
+ debug("Enable Wifi tethering result: " + (result ? result : "successfully"));
+ });
+ }
+
+ // Driver startup on certain platforms takes longer than it takes
+ // for us to return from loadDriver, so wait 2 seconds before
+ // turning on Wifi tethering.
+ createWaitForDriverReadyTimer(doStartWifiTethering);
+ });
+ } else {
+ cancelWifiHotspotStatusTimer();
+ gTetheringService.setWifiTethering(enabled, WifiNetworkInterface,
+ configuration, function(result) {
+ // Should we fire a dom event if we fail to set wifi tethering ?
+ debug("Disable Wifi tethering result: " + (result ? result : "successfully"));
+ // Unload wifi driver even if we fail to control wifi tethering.
+ unloadDriver(WIFI_FIRMWARE_AP, function(status) {
+ if (status < 0) {
+ debug("Fail to unload wifi driver");
+ }
+ manager.tetheringState = "UNINITIALIZED";
+ callback();
+ });
+ });
+ }
+ }
+
+ manager.disconnect = wifiCommand.disconnect;
+ manager.reconnect = wifiCommand.reconnect;
+ manager.reassociate = wifiCommand.reassociate;
+
+ var networkConfigurationFields = [
+ {name: "ssid", type: "string"},
+ {name: "bssid", type: "string"},
+ {name: "psk", type: "string"},
+ {name: "wep_key0", type: "string"},
+ {name: "wep_key1", type: "string"},
+ {name: "wep_key2", type: "string"},
+ {name: "wep_key3", type: "string"},
+ {name: "wep_tx_keyidx", type: "integer"},
+ {name: "priority", type: "integer"},
+ {name: "key_mgmt", type: "string"},
+ {name: "scan_ssid", type: "string"},
+ {name: "disabled", type: "integer"},
+ {name: "identity", type: "string"},
+ {name: "password", type: "string"},
+ {name: "auth_alg", type: "string"},
+ {name: "phase1", type: "string"},
+ {name: "phase2", type: "string"},
+ {name: "eap", type: "string"},
+ {name: "pin", type: "string"},
+ {name: "pcsc", type: "string"},
+ {name: "ca_cert", type: "string"},
+ {name: "subject_match", type: "string"},
+ {name: "client_cert", type: "string"},
+ {name: "private_key", type: "stirng"},
+ {name: "engine", type: "integer"},
+ {name: "engine_id", type: "string"},
+ {name: "key_id", type: "string"},
+ {name: "frequency", type: "integer"},
+ {name: "mode", type: "integer"}
+ ];
+ // These fields are only handled in IBSS (aka ad-hoc) mode
+ var ibssNetworkConfigurationFields = [
+ "frequency", "mode"
+ ];
+
+ manager.getNetworkConfiguration = function(config, callback) {
+ var netId = config.netId;
+ var done = 0;
+ for (var n = 0; n < networkConfigurationFields.length; ++n) {
+ let fieldName = networkConfigurationFields[n].name;
+ let fieldType = networkConfigurationFields[n].type;
+ wifiCommand.getNetworkVariable(netId, fieldName, function(value) {
+ if (value !== null) {
+ if (fieldType === "integer") {
+ config[fieldName] = parseInt(value, 10);
+ } else if ( fieldName == "ssid" && value[0] != '"' ) {
+ // SET_NETWORK will set a quoted ssid to wpa_supplicant.
+ // But if ssid contains non-ascii char, it will be converted into utf-8.
+ // For example: "Testçš„wifi" --> 54657374e79a8477696669
+ // When GET_NETWORK receive a un-quoted utf-8 ssid, it must be decoded and quoted.
+ config[fieldName] = quote(decodeURIComponent(value.replace(/[0-9a-f]{2}/g, '%$&')));
+ } else {
+ // value is string type by default.
+ config[fieldName] = value;
+ }
+ }
+ if (++done == networkConfigurationFields.length)
+ callback(config);
+ });
+ }
+ }
+ manager.setNetworkConfiguration = function(config, callback) {
+ var netId = config.netId;
+ var done = 0;
+ var errors = 0;
+
+ function hasValidProperty(name) {
+ return ((name in config) &&
+ config[name] != null &&
+ (["password", "wep_key0", "psk"].indexOf(name) === -1 ||
+ config[name] !== '*'));
+ }
+
+ function getModeFromConfig() {
+ /* we use the mode from the config, or ESS as default */
+ return hasValidProperty("mode") ? config["mode"] : MODE_ESS;
+ }
+
+ var mode = getModeFromConfig();
+
+ function validForMode(name, mode) {
+ /* all fields are valid for IBSS */
+ return (mode == MODE_IBSS) ||
+ /* IBSS fields are not valid for ESS */
+ ((mode == MODE_ESS) && !(name in ibssNetworkConfigurationFields));
+ }
+
+ for (var n = 0; n < networkConfigurationFields.length; ++n) {
+ let fieldName = networkConfigurationFields[n].name;
+ if (!hasValidProperty(fieldName) || !validForMode(fieldName, mode)) {
+ ++done;
+ } else {
+ wifiCommand.setNetworkVariable(netId, fieldName, config[fieldName], function(ok) {
+ if (!ok)
+ ++errors;
+ if (++done == networkConfigurationFields.length)
+ callback(errors == 0);
+ });
+ }
+ }
+ // If config didn't contain any of the fields we want, don't lose the error callback.
+ if (done == networkConfigurationFields.length)
+ callback(false);
+ }
+ manager.getConfiguredNetworks = function(callback) {
+ wifiCommand.listNetworks(function (reply) {
+ var networks = Object.create(null);
+ var lines = reply ? reply.split("\n") : 0;
+ if (lines.length <= 1) {
+ // We need to make sure we call the callback even if there are no
+ // configured networks.
+ callback(networks);
+ return;
+ }
+
+ var done = 0;
+ var errors = 0;
+ for (var n = 1; n < lines.length; ++n) {
+ var result = lines[n].split("\t");
+ var netId = parseInt(result[0], 10);
+ var config = networks[netId] = { netId: netId };
+ switch (result[3]) {
+ case "[CURRENT]":
+ config.status = "CURRENT";
+ break;
+ case "[DISABLED]":
+ config.status = "DISABLED";
+ break;
+ default:
+ config.status = "ENABLED";
+ break;
+ }
+ manager.getNetworkConfiguration(config, function (ok) {
+ if (!ok)
+ ++errors;
+ if (++done == lines.length - 1) {
+ if (errors) {
+ // If an error occured, delete the new netId.
+ wifiCommand.removeNetwork(netId, function() {
+ callback(null);
+ });
+ } else {
+ callback(networks);
+ }
+ }
+ });
+ }
+ });
+ }
+ manager.addNetwork = function(config, callback) {
+ wifiCommand.addNetwork(function (netId) {
+ config.netId = netId;
+ manager.setNetworkConfiguration(config, function (ok) {
+ if (!ok) {
+ wifiCommand.removeNetwork(netId, function() { callback(false); });
+ return;
+ }
+
+ callback(ok);
+ });
+ });
+ }
+ manager.updateNetwork = function(config, callback) {
+ manager.setNetworkConfiguration(config, callback);
+ }
+ manager.removeNetwork = function(netId, callback) {
+ wifiCommand.removeNetwork(netId, callback);
+ }
+
+ manager.saveConfig = function(callback) {
+ wifiCommand.saveConfig(callback);
+ }
+ manager.enableNetwork = function(netId, disableOthers, callback) {
+ if (p2pSupported) {
+ // We have to stop wifi direct scan before associating to an AP.
+ // Otherwise we will get a "REJECT" wpa supplicant event.
+ p2pManager.setScanEnabled(false, function(success) {});
+ }
+ wifiCommand.enableNetwork(netId, disableOthers, callback);
+ }
+ manager.disableNetwork = function(netId, callback) {
+ wifiCommand.disableNetwork(netId, callback);
+ }
+ manager.getMacAddress = wifiCommand.getMacAddress;
+ manager.getScanResults = wifiCommand.scanResults;
+ manager.setScanMode = function(mode, callback) {
+ setScanMode(mode === "active", callback); // Use our own version.
+ }
+ manager.setBackgroundScan = setBackgroundScan; // Use our own version.
+ manager.scan = scan; // Use our own version.
+ manager.wpsPbc = wifiCommand.wpsPbc;
+ manager.wpsPin = wifiCommand.wpsPin;
+ manager.wpsCancel = wifiCommand.wpsCancel;
+ manager.setPowerMode = (sdkVersion >= 16)
+ ? wifiCommand.setPowerModeJB
+ : wifiCommand.setPowerModeICS;
+ manager.setPowerSavingMode = setPowerSavingMode;
+ manager.getHttpProxyNetwork = getHttpProxyNetwork;
+ manager.setHttpProxy = setHttpProxy;
+ manager.configureHttpProxy = configureHttpProxy;
+ manager.setSuspendOptimizations = (sdkVersion >= 16)
+ ? wifiCommand.setSuspendOptimizationsJB
+ : wifiCommand.setSuspendOptimizationsICS;
+ manager.setStaticIpMode = setStaticIpMode;
+ manager.getRssiApprox = wifiCommand.getRssiApprox;
+ manager.getLinkSpeed = wifiCommand.getLinkSpeed;
+ manager.getDhcpInfo = function() { return dhcpInfo; }
+ manager.getConnectionInfo = (sdkVersion >= 15)
+ ? wifiCommand.getConnectionInfoICS
+ : wifiCommand.getConnectionInfoGB;
+
+ manager.ensureSupplicantDetached = aCallback => {
+ if (!manager.enabled) {
+ aCallback();
+ return;
+ }
+ wifiCommand.closeSupplicantConnection(aCallback);
+ };
+
+ manager.isHandShakeState = function(state) {
+ switch (state) {
+ case "AUTHENTICATING":
+ case "ASSOCIATING":
+ case "ASSOCIATED":
+ case "FOUR_WAY_HANDSHAKE":
+ case "GROUP_HANDSHAKE":
+ return true;
+ case "DORMANT":
+ case "COMPLETED":
+ case "DISCONNECTED":
+ case "INTERFACE_DISABLED":
+ case "INACTIVE":
+ case "SCANNING":
+ case "UNINITIALIZED":
+ case "INVALID":
+ case "CONNECTED":
+ default:
+ return false;
+ }
+ }
+
+ manager.isWifiEnabled = function(state) {
+ switch (state) {
+ case "UNINITIALIZED":
+ case "DISABLING":
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ manager.isWifiTetheringEnabled = function(state) {
+ switch (state) {
+ case "UNINITIALIZED":
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ manager.syncDebug = syncDebug;
+ manager.stateOrdinal = function(state) {
+ return supplicantStatesMap.indexOf(state);
+ }
+ manager.supplicantLoopDetection = function(prevState, state) {
+ var isPrevStateInHandShake = manager.isHandShakeState(prevState);
+ var isStateInHandShake = manager.isHandShakeState(state);
+
+ if (isPrevStateInHandShake) {
+ if (isStateInHandShake) {
+ // Increase the count only if we are in the loop.
+ if (manager.stateOrdinal(state) > manager.stateOrdinal(prevState)) {
+ manager.loopDetectionCount++;
+ }
+ if (manager.loopDetectionCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ manager.loopDetectionCount = 0;
+ }
+ }
+ } else {
+ // From others state to HandShake state. Reset the count.
+ if (isStateInHandShake) {
+ manager.loopDetectionCount = 0;
+ }
+ }
+ }
+
+ manager.handlePreWifiScan = function() {
+ if (p2pSupported) {
+ // Before doing regular wifi scan, we have to disable wifi direct
+ // scan first. Otherwise we will never get the scan result.
+ p2pManager.blockScan();
+ }
+ };
+
+ manager.handlePostWifiScan = function() {
+ if (p2pSupported) {
+ // After regular wifi scanning, we should restore the restricted
+ // wifi direct scan.
+ p2pManager.unblockScan();
+ }
+ };
+
+ //
+ // Public APIs for P2P.
+ //
+
+ manager.p2pSupported = function() {
+ return p2pSupported;
+ };
+
+ manager.getP2pManager = function() {
+ return p2pManager;
+ };
+
+ manager.enableP2p = function(callback) {
+ p2pManager.setEnabled(true, {
+ onSupplicantConnected: function() {
+ waitForEvent(WifiP2pManager.INTERFACE_NAME);
+ },
+
+ onEnabled: function(success) {
+ callback(success);
+ }
+ });
+ };
+
+ manager.getCapabilities = function() {
+ return capabilities;
+ }
+
+ // Cert Services
+ let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"];
+ if (wifiCertService) {
+ wifiCertService = wifiCertService.getService(Ci.nsIWifiCertService);
+ wifiCertService.start(wifiListener);
+ } else {
+ debug("No wifi CA service component available");
+ }
+
+ manager.importCert = function(caInfo, callback) {
+ var id = idgen++;
+ if (callback) {
+ controlCallbacks[id] = callback;
+ }
+
+ wifiCertService.importCert(id, caInfo.certBlob, caInfo.certPassword,
+ caInfo.certNickname);
+ }
+
+ manager.deleteCert = function(caInfo, callback) {
+ var id = idgen++;
+ if (callback) {
+ controlCallbacks[id] = callback;
+ }
+
+ wifiCertService.deleteCert(id, caInfo.certNickname);
+ }
+
+ manager.sdkVersion = function() {
+ return sdkVersion;
+ }
+
+ return manager;
+})();
+
+// Get unique key for a network, now the key is created by escape(SSID)+Security.
+// So networks of same SSID but different security mode can be identified.
+function getNetworkKey(network)
+{
+ var ssid = "",
+ encryption = "OPEN";
+
+ if ("security" in network) {
+ // manager network object, represents an AP
+ // object structure
+ // {
+ // .ssid : SSID of AP
+ // .security[] : "WPA-PSK" for WPA-PSK
+ // "WPA-EAP" for WPA-EAP
+ // "WEP" for WEP
+ // "" for OPEN
+ // other keys
+ // }
+
+ var security = network.security;
+ ssid = network.ssid;
+
+ for (let j = 0; j < security.length; j++) {
+ if (security[j] === "WPA-PSK") {
+ encryption = "WPA-PSK";
+ break;
+ } else if (security[j] === "WPA-EAP") {
+ encryption = "WPA-EAP";
+ break;
+ } else if (security[j] === "WEP") {
+ encryption = "WEP";
+ break;
+ }
+ }
+ } else if ("key_mgmt" in network) {
+ // configure network object, represents a network
+ // object structure
+ // {
+ // .ssid : SSID of network, quoted
+ // .key_mgmt : Encryption type
+ // "WPA-PSK" for WPA-PSK
+ // "WPA-EAP" for WPA-EAP
+ // "NONE" for WEP/OPEN
+ // .auth_alg : Encryption algorithm(WEP mode only)
+ // "OPEN_SHARED" for WEP
+ // other keys
+ // }
+ var key_mgmt = network.key_mgmt,
+ auth_alg = network.auth_alg;
+ ssid = dequote(network.ssid);
+
+ if (key_mgmt == "WPA-PSK") {
+ encryption = "WPA-PSK";
+ } else if (key_mgmt.indexOf("WPA-EAP") != -1) {
+ encryption = "WPA-EAP";
+ } else if (key_mgmt == "NONE" && auth_alg === "OPEN SHARED") {
+ encryption = "WEP";
+ }
+ }
+
+ // ssid here must be dequoted, and it's safer to esacpe it.
+ // encryption won't be empty and always be assigned one of the followings :
+ // "OPEN"/"WEP"/"WPA-PSK"/"WPA-EAP".
+ // So for a invalid network object, the returned key will be "OPEN".
+ return escape(ssid) + encryption;
+}
+
+function getMode(flags) {
+ if (/\[IBSS/.test(flags))
+ return MODE_IBSS;
+
+ return MODE_ESS;
+}
+
+function getKeyManagement(flags) {
+ var types = [];
+ if (!flags)
+ return types;
+
+ if (/\[WPA2?-PSK/.test(flags))
+ types.push("WPA-PSK");
+ if (/\[WPA2?-EAP/.test(flags))
+ types.push("WPA-EAP");
+ if (/\[WEP/.test(flags))
+ types.push("WEP");
+ return types;
+}
+
+function getCapabilities(flags) {
+ var types = [];
+ if (!flags)
+ return types;
+
+ if (/\[WPS/.test(flags))
+ types.push("WPS");
+ return types;
+}
+
+// These constants shamelessly ripped from WifiManager.java
+// strength is the value returned by scan_results. It is nominally in dB. We
+// transform it into a percentage for clients looking to simply show a
+// relative indication of the strength of a network.
+const MIN_RSSI = -100;
+const MAX_RSSI = -55;
+
+function calculateSignal(strength) {
+ // Some wifi drivers represent their signal strengths as 8-bit integers, so
+ // in order to avoid negative numbers, they add 256 to the actual values.
+ // While we don't *know* that this is the case here, we make an educated
+ // guess.
+ if (strength > 0)
+ strength -= 256;
+
+ if (strength <= MIN_RSSI)
+ return 0;
+ if (strength >= MAX_RSSI)
+ return 100;
+ return Math.floor(((strength - MIN_RSSI) / (MAX_RSSI - MIN_RSSI)) * 100);
+}
+
+function Network(ssid, mode, frequency, security, password, capabilities) {
+ this.ssid = ssid;
+ this.mode = mode;
+ this.frequency = frequency;
+ this.security = security;
+
+ if (typeof password !== "undefined")
+ this.password = password;
+ if (capabilities !== undefined)
+ this.capabilities = capabilities;
+ // TODO connected here as well?
+
+ this.__exposedProps__ = Network.api;
+}
+
+Network.api = {
+ ssid: "r",
+ mode: "r",
+ frequency: "r",
+ security: "r",
+ capabilities: "r",
+ known: "r",
+
+ password: "rw",
+ keyManagement: "rw",
+ psk: "rw",
+ identity: "rw",
+ wep: "rw",
+ hidden: "rw",
+ eap: "rw",
+ pin: "rw",
+ phase1: "rw",
+ phase2: "rw",
+ serverCertificate: "rw",
+ userCertificate: "rw"
+};
+
+// Note: We never use ScanResult.prototype, so the fact that it's unrelated to
+// Network.prototype is OK.
+function ScanResult(ssid, bssid, frequency, flags, signal) {
+ Network.call(this, ssid, getMode(flags), frequency,
+ getKeyManagement(flags), undefined, getCapabilities(flags));
+ this.bssid = bssid;
+ this.signalStrength = signal;
+ this.relSignalStrength = calculateSignal(Number(signal));
+
+ this.__exposedProps__ = ScanResult.api;
+}
+
+// XXX This should probably live in the DOM-facing side, but it's hard to do
+// there, so we stick this here.
+ScanResult.api = {
+ bssid: "r",
+ signalStrength: "r",
+ relSignalStrength: "r",
+ connected: "r"
+};
+
+for (let i in Network.api) {
+ ScanResult.api[i] = Network.api[i];
+}
+
+function quote(s) {
+ return '"' + s + '"';
+}
+
+function dequote(s) {
+ if (s[0] != '"' || s[s.length - 1] != '"')
+ throw "Invalid argument, not a quoted string: " + s;
+ return s.substr(1, s.length - 2);
+}
+
+function isWepHexKey(s) {
+ if (s.length != 10 && s.length != 26 && s.length != 58)
+ return false;
+ return !/[^a-fA-F0-9]/.test(s);
+}
+
+
+var WifiNetworkInterface = {
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
+
+ registered: false,
+
+ // nsINetworkInterface
+
+ NETWORK_STATE_UNKNOWN: Ci.nsINetworkInfo.NETWORK_STATE_UNKNOWN,
+ NETWORK_STATE_CONNECTING: Ci.nsINetworkInfo.CONNECTING,
+ NETWORK_STATE_CONNECTED: Ci.nsINetworkInfo.CONNECTED,
+ NETWORK_STATE_DISCONNECTING: Ci.nsINetworkInfo.DISCONNECTING,
+ NETWORK_STATE_DISCONNECTED: Ci.nsINetworkInfo.DISCONNECTED,
+
+ NETWORK_TYPE_WIFI: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI,
+ NETWORK_TYPE_MOBILE: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE,
+ NETWORK_TYPE_MOBILE_MMS: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_MMS,
+ NETWORK_TYPE_MOBILE_SUPL: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_SUPL,
+
+ info: {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo]),
+
+ state: Ci.nsINetworkInfo.NETWORK_STATE_UNKNOWN,
+
+ type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI,
+
+ name: null,
+
+ ips: [],
+
+ prefixLengths: [],
+
+ dnses: [],
+
+ gateways: [],
+
+ getAddresses: function (ips, prefixLengths) {
+ ips.value = this.ips.slice();
+ prefixLengths.value = this.prefixLengths.slice();
+
+ return this.ips.length;
+ },
+
+ getGateways: function (count) {
+ if (count) {
+ count.value = this.gateways.length;
+ }
+ return this.gateways.slice();
+ },
+
+ getDnses: function (count) {
+ if (count) {
+ count.value = this.dnses.length;
+ }
+ return this.dnses.slice();
+ }
+ },
+
+ httpProxyHost: null,
+
+ httpProxyPort: null
+};
+
+function WifiScanResult() {}
+
+// TODO Make the difference between a DOM-based network object and our
+// networks objects much clearer.
+var netToDOM;
+var netFromDOM;
+
+function WifiWorker() {
+ var self = this;
+
+ this._mm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ const messages = ["WifiManager:getNetworks", "WifiManager:getKnownNetworks",
+ "WifiManager:associate", "WifiManager:forget",
+ "WifiManager:wps", "WifiManager:getState",
+ "WifiManager:setPowerSavingMode",
+ "WifiManager:setHttpProxy",
+ "WifiManager:setStaticIpMode",
+ "WifiManager:importCert",
+ "WifiManager:getImportedCerts",
+ "WifiManager:deleteCert",
+ "WifiManager:setWifiEnabled",
+ "WifiManager:setWifiTethering",
+ "child-process-shutdown"];
+
+ messages.forEach((function(msgName) {
+ this._mm.addMessageListener(msgName, this);
+ }).bind(this));
+
+ Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+
+ this.wantScanResults = [];
+
+ this._needToEnableNetworks = false;
+ this._highestPriority = -1;
+
+ // Networks is a map from SSID -> a scan result.
+ this.networks = Object.create(null);
+
+ // ConfiguredNetworks is a map from SSID -> our view of a network. It only
+ // lists networks known to the wpa_supplicant. The SSID field (and other
+ // fields) are quoted for ease of use with WifiManager commands.
+ // Note that we don't have to worry about escaping embedded quotes since in
+ // all cases, the supplicant will take the last quotation that we pass it as
+ // the end of the string.
+ this.configuredNetworks = Object.create(null);
+ this._addingNetworks = Object.create(null);
+
+ this.currentNetwork = null;
+ this.ipAddress = "";
+ this.macAddress = null;
+
+ this._lastConnectionInfo = null;
+ this._connectionInfoTimer = null;
+ this._reconnectOnDisconnect = false;
+
+ // Create p2pObserver and assign to p2pManager.
+ if (WifiManager.p2pSupported()) {
+ this._p2pObserver = WifiP2pWorkerObserver(WifiManager.getP2pManager());
+ WifiManager.getP2pManager().setObserver(this._p2pObserver);
+
+ // Add DOM message observerd by p2pObserver to the message listener as well.
+ this._p2pObserver.getObservedDOMMessages().forEach((function(msgName) {
+ this._mm.addMessageListener(msgName, this);
+ }).bind(this));
+ }
+
+ // Users of instances of nsITimer should keep a reference to the timer until
+ // it is no longer needed in order to assure the timer is fired.
+ this._callbackTimer = null;
+
+ // XXX On some phones (Otoro and Unagi) the wifi driver doesn't play nicely
+ // with the automatic scans that wpa_supplicant does (it appears that the
+ // driver forgets that it's returned scan results and then refuses to try to
+ // rescan. In order to detect this case we start a timer when we enter the
+ // SCANNING state and reset it whenever we either get scan results or leave
+ // the SCANNING state. If the timer fires, we assume that we are stuck and
+ // forceably try to unstick the supplican, also turning on background
+ // scanning to avoid having to constantly poke the supplicant.
+
+ // How long we wait is controlled by the SCAN_STUCK_WAIT constant.
+ const SCAN_STUCK_WAIT = 12000;
+ this._scanStuckTimer = null;
+ this._turnOnBackgroundScan = false;
+
+ function startScanStuckTimer() {
+ if (WifiManager.schedScanRecovery) {
+ self._scanStuckTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ self._scanStuckTimer.initWithCallback(scanIsStuck, SCAN_STUCK_WAIT,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+ }
+
+ function scanIsStuck() {
+ // Uh-oh, we've waited too long for scan results. Disconnect (which
+ // guarantees that we leave the SCANNING state and tells wpa_supplicant to
+ // wait for our next command) ensure that background scanning is on and
+ // then try again.
+ debug("Determined that scanning is stuck, turning on background scanning!");
+ WifiManager.handlePostWifiScan();
+ WifiManager.disconnect(function(ok) {});
+ self._turnOnBackgroundScan = true;
+ }
+
+ // A list of requests to turn wifi on or off.
+ this._stateRequests = [];
+
+ // Given a connection status network, takes a network from
+ // self.configuredNetworks and prepares it for the DOM.
+ netToDOM = function(net) {
+ if (!net) {
+ return null;
+ }
+ var ssid = dequote(net.ssid);
+ var mode = net.mode;
+ var frequency = net.frequency;
+ var security = (net.key_mgmt === "NONE" && net.wep_key0) ? ["WEP"] :
+ (net.key_mgmt && net.key_mgmt !== "NONE") ? [net.key_mgmt.split(" ")[0]] :
+ [];
+ var password;
+ if (("psk" in net && net.psk) ||
+ ("password" in net && net.password) ||
+ ("wep_key0" in net && net.wep_key0)) {
+ password = "*";
+ }
+
+ var pub = new Network(ssid, mode, frequency, security, password);
+ if (net.identity)
+ pub.identity = dequote(net.identity);
+ if ("netId" in net)
+ pub.known = true;
+ if (net.scan_ssid === 1)
+ pub.hidden = true;
+ if ("ca_cert" in net && net.ca_cert &&
+ net.ca_cert.indexOf("keystore://WIFI_SERVERCERT_" === 0)) {
+ pub.serverCertificate = net.ca_cert.substr(27);
+ }
+ if(net.subject_match) {
+ pub.subjectMatch = net.subject_match;
+ }
+ if ("client_cert" in net && net.client_cert &&
+ net.client_cert.indexOf("keystore://WIFI_USERCERT_" === 0)) {
+ pub.userCertificate = net.client_cert.substr(25);
+ }
+ return pub;
+ };
+
+ netFromDOM = function(net, configured) {
+ // Takes a network from the DOM and makes it suitable for insertion into
+ // self.configuredNetworks (that is calling addNetwork will do the right
+ // thing).
+ // NB: Modifies net in place: safe since we don't share objects between
+ // the dom and the chrome code.
+
+ // Things that are useful for the UI but not to us.
+ delete net.bssid;
+ delete net.signalStrength;
+ delete net.relSignalStrength;
+ delete net.security;
+ delete net.capabilities;
+
+ if (!configured)
+ configured = {};
+
+ net.ssid = quote(net.ssid);
+
+ let wep = false;
+ if ("keyManagement" in net) {
+ if (net.keyManagement === "WEP") {
+ wep = true;
+ net.keyManagement = "NONE";
+ } else if (net.keyManagement === "WPA-EAP") {
+ net.keyManagement += " IEEE8021X";
+ }
+
+ configured.key_mgmt = net.key_mgmt = net.keyManagement; // WPA2-PSK, WPA-PSK, etc.
+ delete net.keyManagement;
+ } else {
+ configured.key_mgmt = net.key_mgmt = "NONE";
+ }
+
+ if (net.hidden) {
+ configured.scan_ssid = net.scan_ssid = 1;
+ delete net.hidden;
+ }
+
+ function checkAssign(name, checkStar) {
+ if (name in net) {
+ let value = net[name];
+ if (!value || (checkStar && value === '*')) {
+ if (name in configured)
+ net[name] = configured[name];
+ else
+ delete net[name];
+ } else {
+ configured[name] = net[name] = quote(value);
+ }
+ }
+ }
+
+ checkAssign("psk", true);
+ checkAssign("identity", false);
+ checkAssign("password", true);
+ if (wep && net.wep && net.wep != '*') {
+ configured.wep_key0 = net.wep_key0 = isWepHexKey(net.wep) ? net.wep : quote(net.wep);
+ configured.auth_alg = net.auth_alg = "OPEN SHARED";
+ }
+
+ function hasValidProperty(name) {
+ return ((name in net) && net[name] != null);
+ }
+
+ if (hasValidProperty("eap")) {
+ if (hasValidProperty("pin")) {
+ net.pin = quote(net.pin);
+ }
+
+ if (hasValidProperty("phase1"))
+ net.phase1 = quote(net.phase1);
+
+ if (hasValidProperty("phase2")) {
+ if (net.phase2 === "TLS") {
+ net.phase2 = quote("autheap=" + net.phase2);
+ } else { // PAP, MSCHAPV2, etc.
+ net.phase2 = quote("auth=" + net.phase2);
+ }
+ }
+
+ if (hasValidProperty("serverCertificate"))
+ net.ca_cert = quote("keystore://WIFI_SERVERCERT_" + net.serverCertificate);
+
+ if (hasValidProperty("subjectMatch"))
+ net.subject_match = quote(net.subjectMatch);
+
+ if (hasValidProperty("userCertificate")) {
+ let userCertName = "WIFI_USERCERT_" + net.userCertificate;
+ net.client_cert = quote("keystore://" + userCertName);
+
+ let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"].
+ getService(Ci.nsIWifiCertService);
+ if (wifiCertService.hasPrivateKey(userCertName)) {
+ if (WifiManager.sdkVersion() >= 19) {
+ // Use openssol engine instead of keystore protocol after Kitkat.
+ net.engine = 1;
+ net.engine_id = quote("keystore");
+ net.key_id = quote("WIFI_USERKEY_" + net.userCertificate);
+ } else {
+ net.private_key = quote("keystore://WIFI_USERKEY_" + net.userCertificate);
+ }
+ }
+ }
+ }
+
+ return net;
+ };
+
+ WifiManager.onsupplicantconnection = function() {
+ debug("Connected to supplicant");
+ // Give it a state other than UNINITIALIZED, INITIALIZING or DISABLING
+ // defined in getter function of WifiManager.enabled. It implies that
+ // we are ready to accept dom request from applications.
+ WifiManager.state = "DISCONNECTED";
+ self._reloadConfiguredNetworks(function(ok) {
+ // Prime this.networks.
+ if (!ok)
+ return;
+
+ // The select network command we used in associate() disables others networks.
+ // Enable them here to make sure wpa_supplicant helps to connect to known
+ // network automatically.
+ self._enableAllNetworks();
+ WifiManager.saveConfig(function() {})
+ });
+
+ // Notify everybody, even if they didn't ask us to come up.
+ WifiManager.getMacAddress(function (mac) {
+ self.macAddress = mac;
+ debug("Got mac: " + mac);
+ self._fireEvent("wifiUp", { macAddress: mac });
+ self.requestDone();
+ });
+
+ if (WifiManager.state === "SCANNING")
+ startScanStuckTimer();
+ };
+
+ WifiManager.onsupplicantlost = function() {
+ WifiManager.supplicantStarted = false;
+ WifiManager.state = "UNINITIALIZED";
+ debug("Supplicant died!");
+
+ // Notify everybody, even if they didn't ask us to come up.
+ self._fireEvent("wifiDown", {});
+ self.requestDone();
+ };
+
+ WifiManager.onpasswordmaybeincorrect = function() {
+ WifiManager.authenticationFailuresCount++;
+ };
+
+ WifiManager.ondisconnected = function() {
+ // We may fail to establish the connection, re-enable the
+ // rest of our networks.
+ if (self._needToEnableNetworks) {
+ self._enableAllNetworks();
+ self._needToEnableNetworks = false;
+ }
+
+ let connectionInfo = this.connectionInfo;
+ WifiManager.getNetworkId(connectionInfo.ssid, function(netId) {
+ // Trying to get netId from current network.
+ if (!netId &&
+ self.currentNetwork && self.currentNetwork.ssid &&
+ dequote(self.currentNetwork.ssid) == connectionInfo.ssid &&
+ typeof self.currentNetwork.netId !== "undefined") {
+ netId = self.currentNetwork.netId;
+ }
+ if (netId) {
+ WifiManager.disableNetwork(netId, function() {});
+ }
+ });
+ self._fireEvent("onconnectingfailed", {network: netToDOM(self.currentNetwork)});
+ }
+
+ WifiManager.onstatechange = function() {
+ debug("State change: " + this.prevState + " -> " + this.state);
+
+ if (self._connectionInfoTimer &&
+ this.state !== "CONNECTED" &&
+ this.state !== "COMPLETED") {
+ self._stopConnectionInfoTimer();
+ }
+
+ if (this.state !== "SCANNING" &&
+ self._scanStuckTimer) {
+ self._scanStuckTimer.cancel();
+ self._scanStuckTimer = null;
+ }
+
+ switch (this.state) {
+ case "DORMANT":
+ // The dormant state is a bad state to be in since we won't
+ // automatically connect. Try to knock us out of it. We only
+ // hit this state when we've failed to run DHCP, so trying
+ // again isn't the worst thing we can do. Eventually, we'll
+ // need to detect if we're looping in this state and bail out.
+ WifiManager.reconnect(function(){});
+ break;
+ case "ASSOCIATING":
+ // id has not yet been filled in, so we can only report the ssid and
+ // bssid. mode and frequency are simply made up.
+ self.currentNetwork =
+ { bssid: WifiManager.connectionInfo.bssid,
+ ssid: quote(WifiManager.connectionInfo.ssid),
+ mode: MODE_ESS,
+ frequency: 0};
+ WifiManager.getNetworkConfiguration(self.currentNetwork, function (){
+ // Notify again because we get complete network information.
+ self._fireEvent("onconnecting", { network: netToDOM(self.currentNetwork) });
+ });
+ break;
+ case "ASSOCIATED":
+ // set to full power mode when ready to do 4 way handsharke.
+ WifiManager.setPowerSavingMode(false);
+ if (!self.currentNetwork) {
+ self.currentNetwork =
+ { bssid: WifiManager.connectionInfo.bssid,
+ ssid: quote(WifiManager.connectionInfo.ssid) };
+ }
+ self.currentNetwork.netId = this.id;
+ WifiManager.getNetworkConfiguration(self.currentNetwork, function (){
+ // Notify again because we get complete network information.
+ self._fireEvent("onconnecting", { network: netToDOM(self.currentNetwork) });
+ });
+ break;
+ case "COMPLETED":
+ // Now that we've successfully completed the connection, re-enable the
+ // rest of our networks.
+ // XXX Need to do this eventually if the user entered an incorrect
+ // password. For now, we require user interaction to break the loop and
+ // select a better network!
+ if (self._needToEnableNetworks) {
+ self._enableAllNetworks();
+ self._needToEnableNetworks = false;
+ }
+
+ var _oncompleted = function() {
+ // The full authentication process is completed, reset the count.
+ WifiManager.authenticationFailuresCount = 0;
+ WifiManager.loopDetectionCount = 0;
+ self._startConnectionInfoTimer();
+ self._fireEvent("onassociate", { network: netToDOM(self.currentNetwork) });
+ };
+
+ // We get the ASSOCIATED event when we've associated but not connected, so
+ // wait until the handshake is complete.
+ if (this.fromStatus || !self.currentNetwork) {
+ // In this case, we connected to an already-connected wpa_supplicant,
+ // because of that we need to gather information about the current
+ // network here.
+ self.currentNetwork = { bssid: WifiManager.connectionInfo.bssid,
+ ssid: quote(WifiManager.connectionInfo.ssid),
+ netId: WifiManager.connectionInfo.id };
+ WifiManager.getNetworkConfiguration(self.currentNetwork, _oncompleted);
+ } else {
+ _oncompleted();
+ }
+ break;
+ case "CONNECTED":
+ // wifi connection complete, turn on the power saving mode.
+ WifiManager.setPowerSavingMode(true);
+ // BSSID is read after connected, update it.
+ self.currentNetwork.bssid = WifiManager.connectionInfo.bssid;
+ break;
+ case "DISCONNECTED":
+ // wpa_supplicant may give us a "DISCONNECTED" event even if
+ // we are already in "DISCONNECTED" state.
+ if ((WifiNetworkInterface.info.state ===
+ Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED) &&
+ (this.prevState === "INITIALIZING" ||
+ this.prevState === "DISCONNECTED" ||
+ this.prevState === "INTERFACE_DISABLED" ||
+ this.prevState === "INACTIVE" ||
+ this.prevState === "UNINITIALIZED")) {
+ // When in disconnected mode, need to turn on wifi power saving mode.
+ WifiManager.setPowerSavingMode(true);
+ return;
+ }
+
+ self._fireEvent("ondisconnect", {network: netToDOM(self.currentNetwork)});
+
+ self.currentNetwork = null;
+ self.ipAddress = "";
+
+ if (self._turnOnBackgroundScan) {
+ self._turnOnBackgroundScan = false;
+ WifiManager.setBackgroundScan("ON", function(did_something, ok) {
+ WifiManager.reassociate(function() {});
+ });
+ }
+
+ WifiManager.connectionDropped(function() {
+ // We've disconnected from a network because of a call to forgetNetwork.
+ // Reconnect to the next available network (if any).
+ if (self._reconnectOnDisconnect) {
+ self._reconnectOnDisconnect = false;
+ WifiManager.reconnect(function(){});
+ }
+ });
+
+ WifiNetworkInterface.info.state =
+ Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED;
+
+ // Update network infterface first then clear properties.
+ gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
+ WifiNetworkInterface.info.ips = [];
+ WifiNetworkInterface.info.prefixLengths = [];
+ WifiNetworkInterface.info.gateways = [];
+ WifiNetworkInterface.info.dnses = [];
+
+
+ break;
+ case "WPS_TIMEOUT":
+ self._fireEvent("onwpstimeout", {});
+ break;
+ case "WPS_FAIL":
+ self._fireEvent("onwpsfail", {});
+ break;
+ case "WPS_OVERLAP_DETECTED":
+ self._fireEvent("onwpsoverlap", {});
+ break;
+ case "AUTHENTICATING":
+ self._fireEvent("onauthenticating", {network: netToDOM(self.currentNetwork)});
+ break;
+ case "SCANNING":
+ // If we're already scanning in the background, we don't need to worry
+ // about getting stuck while scanning.
+ if (!WifiManager.backgroundScanEnabled && WifiManager.enabled)
+ startScanStuckTimer();
+ break;
+ }
+ };
+
+ WifiManager.onnetworkconnected = function() {
+ if (!this.info || !this.info.ipaddr_str) {
+ debug("Network information is invalid.");
+ return;
+ }
+
+ let maskLength =
+ netHelpers.getMaskLength(netHelpers.stringToIP(this.info.mask_str));
+ if (!maskLength) {
+ maskLength = 32; // max prefix for IPv4.
+ }
+
+ let netConnect = WifiManager.getHttpProxyNetwork(self.currentNetwork);
+ if (netConnect) {
+ WifiNetworkInterface.httpProxyHost = netConnect.httpProxyHost;
+ WifiNetworkInterface.httpProxyPort = netConnect.httpProxyPort;
+ }
+
+ WifiNetworkInterface.info.state =
+ Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED;
+ WifiNetworkInterface.info.ips = [this.info.ipaddr_str];
+ WifiNetworkInterface.info.prefixLengths = [maskLength];
+ WifiNetworkInterface.info.gateways = [this.info.gateway_str];
+ if (typeof this.info.dns1_str == "string" &&
+ this.info.dns1_str.length) {
+ WifiNetworkInterface.info.dnses.push(this.info.dns1_str);
+ }
+ if (typeof this.info.dns2_str == "string" &&
+ this.info.dns2_str.length) {
+ WifiNetworkInterface.info.dnses.push(this.info.dns2_str);
+ }
+ gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
+
+ self.ipAddress = this.info.ipaddr_str;
+
+ // We start the connection information timer when we associate, but
+ // don't have our IP address until here. Make sure that we fire a new
+ // connectionInformation event with the IP address the next time the
+ // timer fires.
+ self._lastConnectionInfo = null;
+ self._fireEvent("onconnect", { network: netToDOM(self.currentNetwork) });
+ };
+
+ WifiManager.onscanresultsavailable = function() {
+ if (self._scanStuckTimer) {
+ // We got scan results! We must not be stuck for now, try again.
+ self._scanStuckTimer.cancel();
+ self._scanStuckTimer.initWithCallback(scanIsStuck, SCAN_STUCK_WAIT,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+
+ if (self.wantScanResults.length === 0) {
+ debug("Scan results available, but we don't need them");
+ return;
+ }
+
+ debug("Scan results are available! Asking for them.");
+ WifiManager.getScanResults(function(r) {
+ // Failure.
+ if (!r) {
+ self.wantScanResults.forEach(function(callback) { callback(null) });
+ self.wantScanResults = [];
+ return;
+ }
+
+ let capabilities = WifiManager.getCapabilities();
+
+ // Now that we have scan results, there's no more need to continue
+ // scanning. Ignore any errors from this command.
+ WifiManager.setScanMode("inactive", function() {});
+ let lines = r.split("\n");
+ // NB: Skip the header line.
+ self.networksArray = [];
+ for (let i = 1; i < lines.length; ++i) {
+ // bssid / frequency / signal level / flags / ssid
+ var match = /([\S]+)\s+([\S]+)\s+([\S]+)\s+(\[[\S]+\])?\s(.*)/.exec(lines[i]);
+
+ if (match && match[5]) {
+ let ssid = match[5],
+ bssid = match[1],
+ frequency = match[2],
+ signalLevel = match[3],
+ flags = match[4];
+
+ /* Skip networks with unknown or unsupported modes. */
+ if (capabilities.mode.indexOf(getMode(flags)) == -1)
+ continue;
+
+ // If this is the first time that we've seen this SSID in the scan
+ // results, add it to the list along with any other information.
+ // Also, we use the highest signal strength that we see.
+ let network = new ScanResult(ssid, bssid, frequency, flags, signalLevel);
+
+ let networkKey = getNetworkKey(network);
+ let eapIndex = -1;
+ if (networkKey in self.configuredNetworks) {
+ let known = self.configuredNetworks[networkKey];
+ network.known = true;
+
+ if ("identity" in known && known.identity)
+ network.identity = dequote(known.identity);
+
+ // Note: we don't hand out passwords here! The * marks that there
+ // is a password that we're hiding.
+ if (("psk" in known && known.psk) ||
+ ("password" in known && known.password) ||
+ ("wep_key0" in known && known.wep_key0)) {
+ network.password = "*";
+ }
+ }
+
+ self.networksArray.push(network);
+ if (network.bssid === WifiManager.connectionInfo.bssid)
+ network.connected = true;
+
+ let signal = calculateSignal(Number(match[3]));
+ if (signal > network.relSignalStrength)
+ network.relSignalStrength = signal;
+ } else if (!match) {
+ debug("Match didn't find anything for: " + lines[i]);
+ }
+ }
+
+ self.wantScanResults.forEach(function(callback) { callback(self.networksArray) });
+ self.wantScanResults = [];
+ });
+ };
+
+ WifiManager.onstationinfoupdate = function() {
+ self._fireEvent("stationinfoupdate", { station: this.station });
+ };
+
+ WifiManager.onstopconnectioninfotimer = function() {
+ self._stopConnectionInfoTimer();
+ };
+
+ // Read the 'wifi.enabled' setting in order to start with a known
+ // value at boot time. The handle() will be called after reading.
+ //
+ // nsISettingsServiceCallback implementation.
+ var initWifiEnabledCb = {
+ handle: function handle(aName, aResult) {
+ if (aName !== SETTINGS_WIFI_ENABLED)
+ return;
+ if (aResult === null)
+ aResult = true;
+ self.handleWifiEnabled(aResult);
+ },
+ handleError: function handleError(aErrorMessage) {
+ debug("Error reading the 'wifi.enabled' setting. Default to wifi on.");
+ self.handleWifiEnabled(true);
+ }
+ };
+
+ var initWifiDebuggingEnabledCb = {
+ handle: function handle(aName, aResult) {
+ if (aName !== SETTINGS_WIFI_DEBUG_ENABLED)
+ return;
+ if (aResult === null)
+ aResult = false;
+ DEBUG = aResult;
+ updateDebug();
+ },
+ handleError: function handleError(aErrorMessage) {
+ debug("Error reading the 'wifi.debugging.enabled' setting. Default to debugging off.");
+ DEBUG = false;
+ updateDebug();
+ }
+ };
+
+ this.initTetheringSettings();
+
+ let lock = gSettingsService.createLock();
+ lock.get(SETTINGS_WIFI_ENABLED, initWifiEnabledCb);
+ lock.get(SETTINGS_WIFI_DEBUG_ENABLED, initWifiDebuggingEnabledCb);
+
+ lock.get(SETTINGS_WIFI_SSID, this);
+ lock.get(SETTINGS_WIFI_SECURITY_TYPE, this);
+ lock.get(SETTINGS_WIFI_SECURITY_PASSWORD, this);
+ lock.get(SETTINGS_WIFI_IP, this);
+ lock.get(SETTINGS_WIFI_PREFIX, this);
+ lock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
+ lock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
+ lock.get(SETTINGS_WIFI_DNS1, this);
+ lock.get(SETTINGS_WIFI_DNS2, this);
+ lock.get(SETTINGS_WIFI_TETHERING_ENABLED, this);
+
+ lock.get(SETTINGS_USB_DHCPSERVER_STARTIP, this);
+ lock.get(SETTINGS_USB_DHCPSERVER_ENDIP, this);
+
+ this._wifiTetheringSettingsToRead = [SETTINGS_WIFI_SSID,
+ SETTINGS_WIFI_SECURITY_TYPE,
+ SETTINGS_WIFI_SECURITY_PASSWORD,
+ SETTINGS_WIFI_IP,
+ SETTINGS_WIFI_PREFIX,
+ SETTINGS_WIFI_DHCPSERVER_STARTIP,
+ SETTINGS_WIFI_DHCPSERVER_ENDIP,
+ SETTINGS_WIFI_DNS1,
+ SETTINGS_WIFI_DNS2,
+ SETTINGS_WIFI_TETHERING_ENABLED,
+ SETTINGS_USB_DHCPSERVER_STARTIP,
+ SETTINGS_USB_DHCPSERVER_ENDIP];
+}
+
+function translateState(state) {
+ switch (state) {
+ case "INTERFACE_DISABLED":
+ case "INACTIVE":
+ case "SCANNING":
+ case "DISCONNECTED":
+ default:
+ return "disconnected";
+
+ case "AUTHENTICATING":
+ case "ASSOCIATING":
+ case "ASSOCIATED":
+ case "FOUR_WAY_HANDSHAKE":
+ case "GROUP_HANDSHAKE":
+ return "connecting";
+
+ case "COMPLETED":
+ return WifiManager.getDhcpInfo() ? "connected" : "associated";
+ }
+}
+
+WifiWorker.prototype = {
+ classID: WIFIWORKER_CID,
+ classInfo: XPCOMUtils.generateCI({classID: WIFIWORKER_CID,
+ contractID: WIFIWORKER_CONTRACTID,
+ classDescription: "WifiWorker",
+ interfaces: [Ci.nsIWorkerHolder,
+ Ci.nsIWifi,
+ Ci.nsIObserver]}),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
+ Ci.nsIWifi,
+ Ci.nsIObserver,
+ Ci.nsISettingsServiceCallback]),
+
+ disconnectedByWifi: false,
+
+ disconnectedByWifiTethering: false,
+
+ _wifiTetheringSettingsToRead: [],
+
+ _oldWifiTetheringEnabledState: null,
+
+ tetheringSettings: {},
+
+ initTetheringSettings: function initTetheringSettings() {
+ this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = null;
+ this.tetheringSettings[SETTINGS_WIFI_SSID] = DEFAULT_WIFI_SSID;
+ this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE] = DEFAULT_WIFI_SECURITY_TYPE;
+ this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD] = DEFAULT_WIFI_SECURITY_PASSWORD;
+ this.tetheringSettings[SETTINGS_WIFI_IP] = DEFAULT_WIFI_IP;
+ this.tetheringSettings[SETTINGS_WIFI_PREFIX] = DEFAULT_WIFI_PREFIX;
+ this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
+ this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP;
+ this.tetheringSettings[SETTINGS_WIFI_DNS1] = DEFAULT_DNS1;
+ this.tetheringSettings[SETTINGS_WIFI_DNS2] = DEFAULT_DNS2;
+
+ this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP] = DEFAULT_USB_DHCPSERVER_STARTIP;
+ this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP] = DEFAULT_USB_DHCPSERVER_ENDIP;
+ },
+
+ // Internal methods.
+ waitForScan: function(callback) {
+ this.wantScanResults.push(callback);
+ },
+
+ // In order to select a specific network, we disable the rest of the
+ // networks known to us. However, in general, we want the supplicant to
+ // connect to which ever network it thinks is best, so when we select the
+ // proper network (or fail to), we need to re-enable the rest.
+ _enableAllNetworks: function() {
+ for (let key in this.configuredNetworks) {
+ let net = this.configuredNetworks[key];
+ WifiManager.enableNetwork(net.netId, false, function(ok) {
+ net.disabled = ok ? 1 : 0;
+ });
+ }
+ },
+
+ _startConnectionInfoTimer: function() {
+ if (this._connectionInfoTimer)
+ return;
+
+ var self = this;
+ function getConnectionInformation() {
+ WifiManager.getConnectionInfo(function(connInfo) {
+ // See comments in calculateSignal for information about this.
+ if (!connInfo) {
+ self._lastConnectionInfo = null;
+ return;
+ }
+
+ let { rssi, linkspeed } = connInfo;
+ if (rssi > 0)
+ rssi -= 256;
+ if (rssi <= MIN_RSSI)
+ rssi = MIN_RSSI;
+ else if (rssi >= MAX_RSSI)
+ rssi = MAX_RSSI;
+
+ let info = { signalStrength: rssi,
+ relSignalStrength: calculateSignal(rssi),
+ linkSpeed: linkspeed,
+ ipAddress: self.ipAddress };
+ let last = self._lastConnectionInfo;
+
+ // Only fire the event if the link speed changed or the signal
+ // strength changed by more than 10%.
+ function tensPlace(percent) {
+ return (percent / 10) | 0;
+ }
+
+ if (last && last.linkSpeed === info.linkSpeed &&
+ last.ipAddress === info.ipAddress &&
+ tensPlace(last.relSignalStrength) === tensPlace(info.relSignalStrength)) {
+ return;
+ }
+
+ self._lastConnectionInfo = info;
+ debug("Firing connectioninfoupdate: " + uneval(info));
+ self._fireEvent("connectioninfoupdate", info);
+ });
+ }
+
+ // Prime our _lastConnectionInfo immediately and fire the event at the
+ // same time.
+ getConnectionInformation();
+
+ // Now, set up the timer for regular updates.
+ this._connectionInfoTimer =
+ Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._connectionInfoTimer.init(getConnectionInformation, 5000,
+ Ci.nsITimer.TYPE_REPEATING_SLACK);
+ },
+
+ _stopConnectionInfoTimer: function() {
+ if (!this._connectionInfoTimer)
+ return;
+
+ this._connectionInfoTimer.cancel();
+ this._connectionInfoTimer = null;
+ this._lastConnectionInfo = null;
+ },
+
+ _reloadConfiguredNetworks: function(callback) {
+ WifiManager.getConfiguredNetworks((function(networks) {
+ if (!networks) {
+ debug("Unable to get configured networks");
+ callback(false);
+ return;
+ }
+
+ this._highestPriority = -1;
+
+ // Convert between netId-based and ssid-based indexing.
+ for (let net in networks) {
+ let network = networks[net];
+ delete networks[net];
+
+ if (!network.ssid) {
+ WifiManager.removeNetwork(network.netId, function() {});
+ continue;
+ }
+
+ if (network.hasOwnProperty("priority") &&
+ network.priority > this._highestPriority) {
+ this._highestPriority = network.priority;
+ }
+
+ let networkKey = getNetworkKey(network);
+ // Accept latest config of same network(same SSID and same security).
+ if (networks[networkKey]) {
+ WifiManager.removeNetwork(networks[networkKey].netId, function() {});
+ }
+ networks[networkKey] = network;
+ }
+
+ this.configuredNetworks = networks;
+ callback(true);
+ }).bind(this));
+ },
+
+ // Important side effect: calls WifiManager.saveConfig.
+ _reprioritizeNetworks: function(callback) {
+ // First, sort the networks in orer of their priority.
+ var ordered = Object.getOwnPropertyNames(this.configuredNetworks);
+ let self = this;
+ ordered.sort(function(a, b) {
+ var neta = self.configuredNetworks[a],
+ netb = self.configuredNetworks[b];
+
+ // Sort unsorted networks to the end of the list.
+ if (isNaN(neta.priority))
+ return isNaN(netb.priority) ? 0 : 1;
+ if (isNaN(netb.priority))
+ return -1;
+ return netb.priority - neta.priority;
+ });
+
+ // Skip unsorted networks.
+ let newPriority = 0, i;
+ for (i = ordered.length - 1; i >= 0; --i) {
+ if (!isNaN(this.configuredNetworks[ordered[i]].priority))
+ break;
+ }
+
+ // No networks we care about?
+ if (i < 0) {
+ WifiManager.saveConfig(callback);
+ return;
+ }
+
+ // Now assign priorities from 0 to length, starting with the smallest
+ // priority and heading towards the highest (note the dependency between
+ // total and i here).
+ let done = 0, errors = 0, total = i + 1;
+ for (; i >= 0; --i) {
+ let network = this.configuredNetworks[ordered[i]];
+ network.priority = newPriority++;
+
+ // Note: networkUpdated declared below since it happens logically after
+ // this loop.
+ WifiManager.updateNetwork(network, networkUpdated);
+ }
+
+ function networkUpdated(ok) {
+ if (!ok)
+ ++errors;
+ if (++done === total) {
+ if (errors > 0) {
+ callback(false);
+ return;
+ }
+
+ WifiManager.saveConfig(function(ok) {
+ if (!ok) {
+ callback(false);
+ return;
+ }
+
+ self._reloadConfiguredNetworks(function(ok) {
+ callback(ok);
+ });
+ });
+ }
+ }
+ },
+
+ // nsIWifi
+
+ _domManagers: [],
+ _fireEvent: function(message, data) {
+ this._domManagers.forEach(function(manager) {
+ // Note: We should never have a dead message manager here because we
+ // observe our child message managers shutting down, below.
+ manager.sendAsyncMessage("WifiManager:" + message, data);
+ });
+ },
+
+ _sendMessage: function(message, success, data, msg) {
+ try {
+ msg.manager.sendAsyncMessage(message + (success ? ":OK" : ":NO"),
+ { data: data, rid: msg.rid, mid: msg.mid });
+ } catch (e) {
+ debug("sendAsyncMessage error : " + e);
+ }
+ this._splicePendingRequest(msg);
+ },
+
+ _domRequest: [],
+
+ _splicePendingRequest: function(msg) {
+ for (let i = 0; i < this._domRequest.length; i++) {
+ if (this._domRequest[i].msg === msg) {
+ this._domRequest.splice(i, 1);
+ return;
+ }
+ }
+ },
+
+ _clearPendingRequest: function() {
+ if (this._domRequest.length === 0) return;
+ this._domRequest.forEach((function(req) {
+ this._sendMessage(req.name + ":Return", false, "Wifi is disabled", req.msg);
+ }).bind(this));
+ },
+
+ receiveMessage: function MessageManager_receiveMessage(aMessage) {
+ let msg = aMessage.data || {};
+ msg.manager = aMessage.target;
+
+ if (WifiManager.p2pSupported()) {
+ // If p2pObserver returns something truthy, return it!
+ // Otherwise, continue to do the rest of tasks.
+ var p2pRet = this._p2pObserver.onDOMMessage(aMessage);
+ if (p2pRet) {
+ return p2pRet;
+ }
+ }
+
+ // Note: By the time we receive child-process-shutdown, the child process
+ // has already forgotten its permissions so we do this before the
+ // permissions check.
+ if (aMessage.name === "child-process-shutdown") {
+ let i;
+ if ((i = this._domManagers.indexOf(msg.manager)) != -1) {
+ this._domManagers.splice(i, 1);
+ }
+ for (i = this._domRequest.length - 1; i >= 0; i--) {
+ if (this._domRequest[i].msg.manager === msg.manager) {
+ this._domRequest.splice(i, 1);
+ }
+ }
+ return;
+ }
+
+ if (!aMessage.target.assertPermission("wifi-manage")) {
+ return;
+ }
+
+ // We are interested in DOMRequests only.
+ if (aMessage.name != "WifiManager:getState") {
+ this._domRequest.push({name: aMessage.name, msg:msg});
+ }
+
+ switch (aMessage.name) {
+ case "WifiManager:setWifiEnabled":
+ this.setWifiEnabled(msg);
+ break;
+ case "WifiManager:getNetworks":
+ this.getNetworks(msg);
+ break;
+ case "WifiManager:getKnownNetworks":
+ this.getKnownNetworks(msg);
+ break;
+ case "WifiManager:associate":
+ this.associate(msg);
+ break;
+ case "WifiManager:forget":
+ this.forget(msg);
+ break;
+ case "WifiManager:wps":
+ this.wps(msg);
+ break;
+ case "WifiManager:setPowerSavingMode":
+ this.setPowerSavingMode(msg);
+ break;
+ case "WifiManager:setHttpProxy":
+ this.setHttpProxy(msg);
+ break;
+ case "WifiManager:setStaticIpMode":
+ this.setStaticIpMode(msg);
+ break;
+ case "WifiManager:importCert":
+ this.importCert(msg);
+ break;
+ case "WifiManager:getImportedCerts":
+ this.getImportedCerts(msg);
+ break;
+ case "WifiManager:deleteCert":
+ this.deleteCert(msg);
+ break;
+ case "WifiManager:setWifiTethering":
+ this.setWifiTethering(msg);
+ break;
+ case "WifiManager:getState": {
+ let i;
+ if ((i = this._domManagers.indexOf(msg.manager)) === -1) {
+ this._domManagers.push(msg.manager);
+ }
+
+ let net = this.currentNetwork ? netToDOM(this.currentNetwork) : null;
+ return { network: net,
+ connectionInfo: this._lastConnectionInfo,
+ enabled: WifiManager.enabled,
+ status: translateState(WifiManager.state),
+ macAddress: this.macAddress,
+ capabilities: WifiManager.getCapabilities()};
+ }
+ }
+ },
+
+ getNetworks: function(msg) {
+ const message = "WifiManager:getNetworks:Return";
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ let sent = false;
+ let callback = (function (networks) {
+ if (sent)
+ return;
+ sent = true;
+ this._sendMessage(message, networks !== null, networks, msg);
+ }).bind(this);
+ this.waitForScan(callback);
+
+ WifiManager.scan(true, (function(ok) {
+ // If the scan command succeeded, we're done.
+ if (ok)
+ return;
+
+ // Avoid sending multiple responses.
+ if (sent)
+ return;
+
+ // Otherwise, let the client know that it failed, it's responsible for
+ // trying again in a few seconds.
+ sent = true;
+ this._sendMessage(message, false, "ScanFailed", msg);
+ }).bind(this));
+ },
+
+ getWifiScanResults: function(callback) {
+ var count = 0;
+ var timer = null;
+ var self = this;
+
+ if (!WifiManager.enabled) {
+ callback.onfailure();
+ return;
+ }
+
+ self.waitForScan(waitForScanCallback);
+ doScan();
+ function doScan() {
+ WifiManager.scan(true, (function (ok) {
+ if (!ok) {
+ if (!timer) {
+ count = 0;
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ }
+
+ if (count++ >= 3) {
+ timer = null;
+ self.wantScanResults.splice(self.wantScanResults.indexOf(waitForScanCallback), 1);
+ callback.onfailure();
+ return;
+ }
+
+ // Else it's still running, continue waiting.
+ timer.initWithCallback(doScan, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
+ return;
+ }
+ }).bind(this));
+ }
+
+ function waitForScanCallback(networks) {
+ if (networks === null) {
+ callback.onfailure();
+ return;
+ }
+
+ var wifiScanResults = new Array();
+ var net;
+ for (let net in networks) {
+ let value = networks[net];
+ wifiScanResults.push(transformResult(value));
+ }
+ callback.onready(wifiScanResults.length, wifiScanResults);
+ }
+
+ function transformResult(element) {
+ var result = new WifiScanResult();
+ result.connected = false;
+ for (let id in element) {
+ if (id === "__exposedProps__") {
+ continue;
+ }
+ if (id === "security") {
+ result[id] = 0;
+ var security = element[id];
+ for (let j = 0; j < security.length; j++) {
+ if (security[j] === "WPA-PSK") {
+ result[id] |= Ci.nsIWifiScanResult.WPA_PSK;
+ } else if (security[j] === "WPA-EAP") {
+ result[id] |= Ci.nsIWifiScanResult.WPA_EAP;
+ } else if (security[j] === "WEP") {
+ result[id] |= Ci.nsIWifiScanResult.WEP;
+ } else {
+ result[id] = 0;
+ }
+ }
+ } else {
+ result[id] = element[id];
+ }
+ }
+ return result;
+ }
+ },
+
+ getKnownNetworks: function(msg) {
+ const message = "WifiManager:getKnownNetworks:Return";
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ this._reloadConfiguredNetworks((function(ok) {
+ if (!ok) {
+ this._sendMessage(message, false, "Failed", msg);
+ return;
+ }
+
+ var networks = [];
+ for (let networkKey in this.configuredNetworks) {
+ networks.push(netToDOM(this.configuredNetworks[networkKey]));
+ }
+
+ this._sendMessage(message, true, networks, msg);
+ }).bind(this));
+ },
+
+ _setWifiEnabledCallback: function(status) {
+ if (status !== 0) {
+ this.requestDone();
+ return;
+ }
+
+ // If we're enabling ourselves, then wait until we've connected to the
+ // supplicant to notify. If we're disabling, we take care of this in
+ // supplicantlost.
+ if (WifiManager.supplicantStarted)
+ WifiManager.start();
+ },
+
+ /**
+ * Compatibility flags for detecting if Gaia is controlling wifi by settings
+ * or API, once API is called, gecko will no longer accept wifi enable
+ * control from settings.
+ * This is used to deal with compatibility issue while Gaia adopted to use
+ * API but gecko doesn't remove the settings code in time.
+ * TODO: Remove this flag in Bug 1050147
+ */
+ ignoreWifiEnabledFromSettings: false,
+ setWifiEnabled: function(msg) {
+ const message = "WifiManager:setWifiEnabled:Return";
+ let self = this;
+ let enabled = msg.data;
+
+ self.ignoreWifiEnabledFromSettings = true;
+ // No change.
+ if (enabled === WifiManager.enabled) {
+ this._sendMessage(message, true, true, msg);
+ return;
+ }
+
+ // Can't enable wifi while hotspot mode is enabled.
+ if (enabled && (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
+ WifiManager.isWifiTetheringEnabled(WifiManager.tetheringState))) {
+ self._sendMessage(message, false, "Can't enable Wifi while hotspot mode is enabled", msg);
+ return;
+ }
+
+ WifiManager.setWifiEnabled(enabled, function(ok) {
+ if (ok === 0 || ok === "no change") {
+ self._sendMessage(message, true, true, msg);
+
+ // Reply error to pending requests.
+ if (!enabled) {
+ self._clearPendingRequest();
+ } else {
+ WifiManager.start();
+ }
+ } else {
+ self._sendMessage(message, false, "Set wifi enabled failed", msg);
+ }
+ });
+ },
+
+ _setWifiEnabled: function(enabled, callback) {
+ // Reply error to pending requests.
+ if (!enabled) {
+ this._clearPendingRequest();
+ }
+
+ WifiManager.setWifiEnabled(enabled, callback);
+ },
+
+ // requestDone() must be called to before callback complete(or error)
+ // so next queue in the request quene can be executed.
+ // TODO: Remove command queue in Bug 1050147
+ queueRequest: function(data, callback) {
+ if (!callback) {
+ throw "Try to enqueue a request without callback";
+ }
+
+ let optimizeCommandList = ["setWifiEnabled", "setWifiApEnabled"];
+ if (optimizeCommandList.indexOf(data.command) != -1) {
+ this._stateRequests = this._stateRequests.filter(function(element) {
+ return element.data.command !== data.command;
+ });
+ }
+
+ this._stateRequests.push({
+ data: data,
+ callback: callback
+ });
+
+ this.nextRequest();
+ },
+
+ getWifiTetheringParameters: function getWifiTetheringParameters(enable) {
+ if (this.useTetheringAPI) {
+ return this.getWifiTetheringConfiguration(enable);
+ } else {
+ return this.getWifiTetheringParametersBySetting(enable);
+ }
+ },
+
+ getWifiTetheringConfiguration: function getWifiTetheringConfiguration(enable) {
+ let config = {};
+ let params = this.tetheringConfig;
+
+ let check = function(field, _default) {
+ config[field] = field in params ? params[field] : _default;
+ };
+
+ check("ssid", DEFAULT_WIFI_SSID);
+ check("security", DEFAULT_WIFI_SECURITY_TYPE);
+ check("key", DEFAULT_WIFI_SECURITY_PASSWORD);
+ check("ip", DEFAULT_WIFI_IP);
+ check("prefix", DEFAULT_WIFI_PREFIX);
+ check("wifiStartIp", DEFAULT_WIFI_DHCPSERVER_STARTIP);
+ check("wifiEndIp", DEFAULT_WIFI_DHCPSERVER_ENDIP);
+ check("usbStartIp", DEFAULT_USB_DHCPSERVER_STARTIP);
+ check("usbEndIp", DEFAULT_USB_DHCPSERVER_ENDIP);
+ check("dns1", DEFAULT_DNS1);
+ check("dns2", DEFAULT_DNS2);
+
+ config.enable = enable;
+ config.mode = enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION;
+ config.link = enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN;
+
+ // Check the format to prevent netd from crash.
+ if (enable && (!config.ssid || config.ssid == "")) {
+ debug("Invalid SSID value.");
+ return null;
+ }
+
+ if (enable && (config.security != WIFI_SECURITY_TYPE_NONE && !config.key)) {
+ debug("Invalid security password.");
+ return null;
+ }
+
+ // Using the default values here until application supports these settings.
+ if (config.ip == "" || config.prefix == "" ||
+ config.wifiStartIp == "" || config.wifiEndIp == "" ||
+ config.usbStartIp == "" || config.usbEndIp == "") {
+ debug("Invalid subnet information.");
+ return null;
+ }
+
+ return config;
+ },
+
+ getWifiTetheringParametersBySetting: function getWifiTetheringParametersBySetting(enable) {
+ let ssid;
+ let securityType;
+ let securityId;
+ let interfaceIp;
+ let prefix;
+ let wifiDhcpStartIp;
+ let wifiDhcpEndIp;
+ let usbDhcpStartIp;
+ let usbDhcpEndIp;
+ let dns1;
+ let dns2;
+
+ ssid = this.tetheringSettings[SETTINGS_WIFI_SSID];
+ securityType = this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE];
+ securityId = this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD];
+ interfaceIp = this.tetheringSettings[SETTINGS_WIFI_IP];
+ prefix = this.tetheringSettings[SETTINGS_WIFI_PREFIX];
+ wifiDhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
+ wifiDhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
+ usbDhcpStartIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP];
+ usbDhcpEndIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP];
+ dns1 = this.tetheringSettings[SETTINGS_WIFI_DNS1];
+ dns2 = this.tetheringSettings[SETTINGS_WIFI_DNS2];
+
+ // Check the format to prevent netd from crash.
+ if (!ssid || ssid == "") {
+ debug("Invalid SSID value.");
+ return null;
+ }
+ // Truncate ssid if its length of encoded to utf8 is longer than 32.
+ while (unescape(encodeURIComponent(ssid)).length > 32)
+ {
+ ssid = ssid.substring(0, ssid.length-1);
+ }
+
+ if (securityType != WIFI_SECURITY_TYPE_NONE &&
+ securityType != WIFI_SECURITY_TYPE_WPA_PSK &&
+ securityType != WIFI_SECURITY_TYPE_WPA2_PSK) {
+
+ debug("Invalid security type.");
+ return null;
+ }
+ if (securityType != WIFI_SECURITY_TYPE_NONE && !securityId) {
+ debug("Invalid security password.");
+ return null;
+ }
+ // Using the default values here until application supports these settings.
+ if (interfaceIp == "" || prefix == "" ||
+ wifiDhcpStartIp == "" || wifiDhcpEndIp == "" ||
+ usbDhcpStartIp == "" || usbDhcpEndIp == "") {
+ debug("Invalid subnet information.");
+ return null;
+ }
+
+ return {
+ ssid: ssid,
+ security: securityType,
+ key: securityId,
+ ip: interfaceIp,
+ prefix: prefix,
+ wifiStartIp: wifiDhcpStartIp,
+ wifiEndIp: wifiDhcpEndIp,
+ usbStartIp: usbDhcpStartIp,
+ usbEndIp: usbDhcpEndIp,
+ dns1: dns1,
+ dns2: dns2,
+ enable: enable,
+ mode: enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION,
+ link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
+ };
+ },
+
+ setWifiApEnabled: function(enabled, callback) {
+ let configuration = this.getWifiTetheringParameters(enabled);
+
+ if (!configuration) {
+ this.requestDone();
+ debug("Invalid Wifi Tethering configuration.");
+ return;
+ }
+
+ WifiManager.setWifiApEnabled(enabled, configuration, callback);
+ },
+
+ associate: function(msg) {
+ const MAX_PRIORITY = 9999;
+ const message = "WifiManager:associate:Return";
+ let network = msg.data;
+
+ let privnet = network;
+ let dontConnect = privnet.dontConnect;
+ delete privnet.dontConnect;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ let self = this;
+ function networkReady() {
+ // saveConfig now before we disable most of the other networks.
+ function selectAndConnect() {
+ WifiManager.enableNetwork(privnet.netId, true, function (ok) {
+ if (ok)
+ self._needToEnableNetworks = true;
+ if (WifiManager.state === "DISCONNECTED" ||
+ WifiManager.state === "SCANNING") {
+ WifiManager.reconnect(function (ok) {
+ self._sendMessage(message, ok, ok, msg);
+ });
+ } else {
+ self._sendMessage(message, ok, ok, msg);
+ }
+ });
+ }
+
+ var selectAndConnectOrReturn = dontConnect ?
+ function() {
+ self._sendMessage(message, true, "Wifi has been recorded", msg);
+ } : selectAndConnect;
+ if (self._highestPriority >= MAX_PRIORITY) {
+ self._reprioritizeNetworks(selectAndConnectOrReturn);
+ } else {
+ WifiManager.saveConfig(selectAndConnectOrReturn);
+ }
+ }
+
+ function connectToNetwork() {
+ WifiManager.updateNetwork(privnet, (function(ok) {
+ if (!ok) {
+ self._sendMessage(message, false, "Network is misconfigured", msg);
+ return;
+ }
+
+ networkReady();
+ }));
+ }
+
+ let ssid = privnet.ssid;
+ let networkKey = getNetworkKey(privnet);
+ let configured;
+
+ if (networkKey in this._addingNetworks) {
+ this._sendMessage(message, false, "Racing associates");
+ return;
+ }
+
+ if (networkKey in this.configuredNetworks)
+ configured = this.configuredNetworks[networkKey];
+
+ netFromDOM(privnet, configured);
+
+ privnet.priority = ++this._highestPriority;
+ if (configured) {
+ privnet.netId = configured.netId;
+ // Sync priority back to configured so if priority reaches MAX_PRIORITY,
+ // it can be sorted correctly in _reprioritizeNetworks() because the
+ // function sort network based on priority in configure list.
+ configured.priority = privnet.priority;
+
+ // When investigating Bug 1123680, we observed that gaia may unexpectedly
+ // request to associate with incorrect password before successfully
+ // forgetting the network. It would cause the network unable to connect
+ // subsequently. Aside from preventing the racing forget/associate, we
+ // also try to disable network prior to updating the network.
+ WifiManager.getNetworkId(dequote(configured.ssid), function(netId) {
+ if (netId) {
+ WifiManager.disableNetwork(netId, function() {
+ connectToNetwork();
+ });
+ }
+ else {
+ connectToNetwork();
+ }
+ });
+ } else {
+ // networkReady, above, calls saveConfig. We want to remember the new
+ // network as being enabled, which isn't the default, so we explicitly
+ // set it to being "enabled" before we add it and save the
+ // configuration.
+ privnet.disabled = 0;
+ this._addingNetworks[networkKey] = privnet;
+ WifiManager.addNetwork(privnet, (function(ok) {
+ delete this._addingNetworks[networkKey];
+
+ if (!ok) {
+ this._sendMessage(message, false, "Network is misconfigured", msg);
+ return;
+ }
+
+ this.configuredNetworks[networkKey] = privnet;
+ networkReady();
+ }).bind(this));
+ }
+ },
+
+ forget: function(msg) {
+ const message = "WifiManager:forget:Return";
+ let network = msg.data;
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ this._reloadConfiguredNetworks((function(ok) {
+ // Give it a chance to remove the network even if reload is failed.
+ if (!ok) {
+ debug("Warning !!! Failed to reload the configured networks");
+ }
+
+ let ssid = network.ssid;
+ let networkKey = getNetworkKey(network);
+ if (!(networkKey in this.configuredNetworks)) {
+ this._sendMessage(message, false, "Trying to forget an unknown network", msg);
+ return;
+ }
+
+ let self = this;
+ let configured = this.configuredNetworks[networkKey];
+ this._reconnectOnDisconnect = (this.currentNetwork &&
+ (this.currentNetwork.ssid === ssid));
+ WifiManager.removeNetwork(configured.netId, function(ok) {
+ if (self._needToEnableNetworks) {
+ self._enableAllNetworks();
+ self._needToEnableNetworks = false;
+ }
+
+ if (!ok) {
+ self._sendMessage(message, false, "Unable to remove the network", msg);
+ self._reconnectOnDisconnect = false;
+ return;
+ }
+
+ WifiManager.saveConfig(function() {
+ self._reloadConfiguredNetworks(function() {
+ self._sendMessage(message, true, true, msg);
+ });
+ });
+ });
+ }).bind(this));
+ },
+
+ wps: function(msg) {
+ const message = "WifiManager:wps:Return";
+ let self = this;
+ let detail = msg.data;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ if (detail.method === "pbc") {
+ WifiManager.wpsPbc(function(ok) {
+ if (ok)
+ self._sendMessage(message, true, true, msg);
+ else
+ self._sendMessage(message, false, "WPS PBC failed", msg);
+ });
+ } else if (detail.method === "pin") {
+ WifiManager.wpsPin(detail, function(pin) {
+ if (pin)
+ self._sendMessage(message, true, pin, msg);
+ else
+ self._sendMessage(message, false, "WPS PIN failed", msg);
+ });
+ } else if (detail.method === "cancel") {
+ WifiManager.wpsCancel(function(ok) {
+ if (ok)
+ self._sendMessage(message, true, true, msg);
+ else
+ self._sendMessage(message, false, "WPS Cancel failed", msg);
+ });
+ } else {
+ self._sendMessage(message, false, "Invalid WPS method=" + detail.method,
+ msg);
+ }
+ },
+
+ setPowerSavingMode: function(msg) {
+ const message = "WifiManager:setPowerSavingMode:Return";
+ let self = this;
+ let enabled = msg.data;
+ let mode = enabled ? "AUTO" : "ACTIVE";
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ // Some wifi drivers may not implement this command. Set power mode
+ // even if suspend optimization command failed.
+ WifiManager.setSuspendOptimizations(enabled, function(ok) {
+ WifiManager.setPowerMode(mode, function(ok) {
+ if (ok) {
+ self._sendMessage(message, true, true, msg);
+ } else {
+ self._sendMessage(message, false, "Set power saving mode failed", msg);
+ }
+ });
+ });
+ },
+
+ setHttpProxy: function(msg) {
+ const message = "WifiManager:setHttpProxy:Return";
+ let self = this;
+ let network = msg.data.network;
+ let info = msg.data.info;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ WifiManager.configureHttpProxy(network, info, function(ok) {
+ if (ok) {
+ // If configured network is current connected network
+ // need update http proxy immediately.
+ let setNetworkKey = getNetworkKey(network);
+ let curNetworkKey = self.currentNetwork ? getNetworkKey(self.currentNetwork) : null;
+ if (setNetworkKey === curNetworkKey)
+ WifiManager.setHttpProxy(network);
+
+ self._sendMessage(message, true, true, msg);
+ } else {
+ self._sendMessage(message, false, "Set http proxy failed", msg);
+ }
+ });
+ },
+
+ setStaticIpMode: function(msg) {
+ const message = "WifiManager:setStaticIpMode:Return";
+ let self = this;
+ let network = msg.data.network;
+ let info = msg.data.info;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ // To compatiable with DHCP returned info structure, do translation here
+ info.ipaddr_str = info.ipaddr;
+ info.proxy_str = info.proxy;
+ info.gateway_str = info.gateway;
+ info.dns1_str = info.dns1;
+ info.dns2_str = info.dns2;
+
+ WifiManager.setStaticIpMode(network, info, function(ok) {
+ if (ok) {
+ self._sendMessage(message, true, true, msg);
+ } else {
+ self._sendMessage(message, false, "Set static ip mode failed", msg);
+ }
+ });
+ },
+
+ importCert: function importCert(msg) {
+ const message = "WifiManager:importCert:Return";
+ let self = this;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ WifiManager.importCert(msg.data, function(data) {
+ if (data.status === 0) {
+ let usageString = ["ServerCert", "UserCert"];
+ let usageArray = [];
+ for (let i = 0; i < usageString.length; i++) {
+ if (data.usageFlag & (0x01 << i)) {
+ usageArray.push(usageString[i]);
+ }
+ }
+
+ self._sendMessage(message, true, {
+ nickname: data.nickname,
+ usage: usageArray
+ }, msg);
+ } else {
+ self._sendMessage(message, false, "Import Cert failed", msg);
+ }
+ });
+ },
+
+ getImportedCerts: function getImportedCerts(msg) {
+ const message = "WifiManager:getImportedCerts:Return";
+ let self = this;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ let certDB = Cc["@mozilla.org/security/x509certdb;1"]
+ .getService(Ci.nsIX509CertDB);
+ if (!certDB) {
+ self._sendMessage(message, false, "Failed to query NSS DB service", msg);
+ }
+
+ let certList = certDB.getCerts();
+ if (!certList) {
+ self._sendMessage(message, false, "Failed to get certificate List", msg);
+ }
+
+ let certListEnum = certList.getEnumerator();
+ if (!certListEnum) {
+ self._sendMessage(message, false, "Failed to get certificate List enumerator", msg);
+ }
+ let importedCerts = {
+ ServerCert: [],
+ UserCert: [],
+ };
+ let UsageMapping = {
+ SERVERCERT: "ServerCert",
+ USERCERT: "UserCert",
+ };
+
+ while (certListEnum.hasMoreElements()) {
+ let certInfo = certListEnum.getNext().QueryInterface(Ci.nsIX509Cert);
+ let certNicknameInfo = /WIFI\_([A-Z]*)\_(.*)/.exec(certInfo.nickname);
+ if (!certNicknameInfo) {
+ continue;
+ }
+ importedCerts[UsageMapping[certNicknameInfo[1]]].push(certNicknameInfo[2]);
+ }
+
+ self._sendMessage(message, true, importedCerts, msg);
+ },
+
+ deleteCert: function deleteCert(msg) {
+ const message = "WifiManager:deleteCert:Return";
+ let self = this;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ WifiManager.deleteCert(msg.data, function(data) {
+ self._sendMessage(message, data.status === 0, "Delete Cert failed", msg);
+ });
+ },
+
+ // TODO : These two variables should be removed once GAIA uses tethering API.
+ useTetheringAPI : false,
+ tetheringConfig : {},
+
+ setWifiTethering: function setWifiTethering(msg) {
+ const message = "WifiManager:setWifiTethering:Return";
+ let self = this;
+ let enabled = msg.data.enabled;
+
+ this.useTetheringAPI = true;
+ this.tetheringConfig = msg.data.config;
+
+ if (WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is enabled", msg);
+ return;
+ }
+
+ this.setWifiApEnabled(enabled, function() {
+ if ((enabled && WifiManager.tetheringState == "COMPLETED") ||
+ (!enabled && WifiManager.tetheringState == "UNINITIALIZED")) {
+ self._sendMessage(message, true, msg.data, msg);
+ } else {
+ msg.data.reason = enabled ?
+ "Enable WIFI tethering faild" : "Disable WIFI tethering faild";
+ self._sendMessage(message, false, msg.data, msg);
+ }
+ });
+ },
+
+ // This is a bit ugly, but works. In particular, this depends on the fact
+ // that RadioManager never actually tries to get the worker from us.
+ get worker() { throw "Not implemented"; },
+
+ shutdown: function() {
+ debug("shutting down ...");
+ this.queueRequest({command: "setWifiEnabled", value: false}, function(data) {
+ this._setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
+ }.bind(this));
+ },
+
+ // TODO: Remove command queue in Bug 1050147.
+ requestProcessing: false, // Hold while dequeue and execution a request.
+ // Released upon the request is fully executed,
+ // i.e, mostly after callback is done.
+ requestDone: function requestDone() {
+ this.requestProcessing = false;
+ this.nextRequest();
+ },
+
+ nextRequest: function nextRequest() {
+ // No request to process
+ if (this._stateRequests.length === 0) {
+ return;
+ }
+
+ // Handling request, wait for it.
+ if (this.requestProcessing) {
+ return;
+ }
+
+ // Hold processing lock
+ this.requestProcessing = true;
+
+ // Find next valid request
+ let request = this._stateRequests.shift();
+
+ request.callback(request.data);
+ },
+
+ notifyTetheringOn: function notifyTetheringOn() {
+ // It's really sad that we don't have an API to notify the wifi
+ // hotspot status. Toggle settings to let gaia know that wifi hotspot
+ // is enabled.
+ let self = this;
+ this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = true;
+ this._oldWifiTetheringEnabledState = true;
+ gSettingsService.createLock().set(
+ SETTINGS_WIFI_TETHERING_ENABLED,
+ true,
+ {
+ handle: function(aName, aResult) {
+ self.requestDone();
+ },
+ handleError: function(aErrorMessage) {
+ self.requestDone();
+ }
+ });
+ },
+
+ notifyTetheringOff: function notifyTetheringOff() {
+ // It's really sad that we don't have an API to notify the wifi
+ // hotspot status. Toggle settings to let gaia know that wifi hotspot
+ // is disabled.
+ let self = this;
+ this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
+ this._oldWifiTetheringEnabledState = false;
+ gSettingsService.createLock().set(
+ SETTINGS_WIFI_TETHERING_ENABLED,
+ false,
+ {
+ handle: function(aName, aResult) {
+ self.requestDone();
+ },
+ handleError: function(aErrorMessage) {
+ self.requestDone();
+ }
+ });
+ },
+
+ handleWifiEnabled: function(enabled) {
+ if (this.ignoreWifiEnabledFromSettings) {
+ return;
+ }
+
+ // Make sure Wifi hotspot is idle before switching to Wifi mode.
+ if (enabled) {
+ this.queueRequest({command: "setWifiApEnabled", value: false}, function(data) {
+ if (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
+ WifiManager.isWifiTetheringEnabled(WifiManager.tetheringState)) {
+ this.disconnectedByWifi = true;
+ this.setWifiApEnabled(false, this.notifyTetheringOff.bind(this));
+ } else {
+ this.requestDone();
+ }
+ }.bind(this));
+ }
+
+ this.queueRequest({command: "setWifiEnabled", value: enabled}, function(data) {
+ this._setWifiEnabled(enabled, this._setWifiEnabledCallback.bind(this));
+ }.bind(this));
+
+ if (!enabled) {
+ this.queueRequest({command: "setWifiApEnabled", value: true}, function(data) {
+ if (this.disconnectedByWifi) {
+ this.setWifiApEnabled(true, this.notifyTetheringOn.bind(this));
+ } else {
+ this.requestDone();
+ }
+ this.disconnectedByWifi = false;
+ }.bind(this));
+ }
+ },
+
+ handleWifiTetheringEnabled: function(enabled) {
+ // Make sure Wifi is idle before switching to Wifi hotspot mode.
+ if (enabled) {
+ this.queueRequest({command: "setWifiEnabled", value: false}, function(data) {
+ if (WifiManager.isWifiEnabled(WifiManager.state)) {
+ this.disconnectedByWifiTethering = true;
+ this._setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
+ } else {
+ this.requestDone();
+ }
+ }.bind(this));
+ }
+
+ this.queueRequest({command: "setWifiApEnabled", value: enabled}, function(data) {
+ this.setWifiApEnabled(enabled, this.requestDone.bind(this));
+ }.bind(this));
+
+ if (!enabled) {
+ this.queueRequest({command: "setWifiEnabled", value: true}, function(data) {
+ if (this.disconnectedByWifiTethering) {
+ this._setWifiEnabled(true, this._setWifiEnabledCallback.bind(this));
+ } else {
+ this.requestDone();
+ }
+ this.disconnectedByWifiTethering = false;
+ }.bind(this));
+ }
+ },
+
+ // nsIObserver implementation
+ observe: function observe(subject, topic, data) {
+ switch (topic) {
+ case kMozSettingsChangedObserverTopic:
+ // To avoid WifiWorker setting the wifi again, don't need to deal with
+ // the "mozsettings-changed" event fired from internal setting.
+ if ("wrappedJSObject" in subject) {
+ subject = subject.wrappedJSObject;
+ }
+ if (subject.isInternalChange) {
+ return;
+ }
+
+ this.handle(subject.key, subject.value);
+ break;
+
+ case "xpcom-shutdown":
+ // Ensure the supplicant is detached from B2G to avoid XPCOM shutdown
+ // blocks forever.
+ WifiManager.ensureSupplicantDetached(() => {
+ let wifiService = Cc["@mozilla.org/wifi/service;1"].getService(Ci.nsIWifiProxyService);
+ wifiService.shutdown();
+ let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"].getService(Ci.nsIWifiCertService);
+ wifiCertService.shutdown();
+ });
+ break;
+ }
+ },
+
+ handle: function handle(aName, aResult) {
+ switch(aName) {
+ // TODO: Remove function call in Bug 1050147.
+ case SETTINGS_WIFI_ENABLED:
+ this.handleWifiEnabled(aResult)
+ break;
+ case SETTINGS_WIFI_DEBUG_ENABLED:
+ if (aResult === null)
+ aResult = false;
+ DEBUG = aResult;
+ updateDebug();
+ break;
+ case SETTINGS_WIFI_TETHERING_ENABLED:
+ this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
+ // Fall through!
+ case SETTINGS_WIFI_SSID:
+ case SETTINGS_WIFI_SECURITY_TYPE:
+ case SETTINGS_WIFI_SECURITY_PASSWORD:
+ case SETTINGS_WIFI_IP:
+ case SETTINGS_WIFI_PREFIX:
+ case SETTINGS_WIFI_DHCPSERVER_STARTIP:
+ case SETTINGS_WIFI_DHCPSERVER_ENDIP:
+ case SETTINGS_WIFI_DNS1:
+ case SETTINGS_WIFI_DNS2:
+ case SETTINGS_USB_DHCPSERVER_STARTIP:
+ case SETTINGS_USB_DHCPSERVER_ENDIP:
+ // TODO: code related to wifi-tethering setting should be removed after GAIA
+ // use tethering API
+ if (this.useTetheringAPI) {
+ break;
+ }
+
+ if (aResult !== null) {
+ this.tetheringSettings[aName] = aResult;
+ }
+ debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
+ let index = this._wifiTetheringSettingsToRead.indexOf(aName);
+
+ if (index != -1) {
+ this._wifiTetheringSettingsToRead.splice(index, 1);
+ }
+
+ if (this._wifiTetheringSettingsToRead.length) {
+ debug("We haven't read completely the wifi Tethering data from settings db.");
+ break;
+ }
+
+ if (this._oldWifiTetheringEnabledState === this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
+ debug("No changes for SETTINGS_WIFI_TETHERING_ENABLED flag. Nothing to do.");
+ break;
+ }
+
+ if (this._oldWifiTetheringEnabledState === null &&
+ !this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
+ debug("Do nothing when initial settings for SETTINGS_WIFI_TETHERING_ENABLED flag is false.");
+ break;
+ }
+
+ this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
+ this.handleWifiTetheringEnabled(aResult)
+ break;
+ };
+ },
+
+ handleError: function handleError(aErrorMessage) {
+ debug("There was an error while reading Tethering settings.");
+ this.tetheringSettings = {};
+ this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WifiWorker]);
+
+var debug;
+function updateDebug() {
+ if (DEBUG) {
+ debug = function (s) {
+ dump("-*- WifiWorker component: " + s + "\n");
+ };
+ } else {
+ debug = function (s) {};
+ }
+ WifiManager.syncDebug();
+}
+updateDebug();
diff --git a/dom/wifi/WifiWorker.manifest b/dom/wifi/WifiWorker.manifest
new file mode 100644
index 000000000..e33d6401a
--- /dev/null
+++ b/dom/wifi/WifiWorker.manifest
@@ -0,0 +1 @@
+component {a14e8977-d259-433a-a88d-58dd44657e5b} WifiWorker.js
diff --git a/dom/wifi/moz.build b/dom/wifi/moz.build
new file mode 100644
index 000000000..d769c649c
--- /dev/null
+++ b/dom/wifi/moz.build
@@ -0,0 +1,41 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIWifi.idl',
+ 'nsIWifiCertService.idl',
+ 'nsIWifiService.idl',
+]
+
+XPIDL_MODULE = 'dom_wifi'
+
+EXTRA_COMPONENTS += [
+ 'DOMWifiManager.js',
+ 'DOMWifiManager.manifest',
+ 'DOMWifiP2pManager.js',
+ 'DOMWifiP2pManager.manifest',
+ 'WifiWorker.js',
+ 'WifiWorker.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'StateMachine.jsm',
+ 'WifiCommand.jsm',
+ 'WifiNetUtil.jsm',
+ 'WifiP2pManager.jsm',
+ 'WifiP2pWorkerObserver.jsm',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ UNIFIED_SOURCES = [
+ 'WifiCertService.cpp',
+ 'WifiHotspotUtils.cpp',
+ 'WifiProxyService.cpp',
+ 'WifiUtils.cpp',
+ ]
+ DEFINES['CERT_AddTempCertToPerm'] = '__CERT_AddTempCertToPerm'
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/wifi/nsIWifi.idl b/dom/wifi/nsIWifi.idl
new file mode 100644
index 000000000..82ab9a75b
--- /dev/null
+++ b/dom/wifi/nsIWifi.idl
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIDOMDOMRequest.idl"
+#include "nsIDOMEvent.idl"
+
+interface nsIVariant;
+
+[scriptable, uuid(cf1ac02b-1f39-446e-815b-651ac78d2233)]
+interface nsIWifiScanResult : nsISupports {
+ readonly attribute DOMString ssid;
+ readonly attribute DOMString bssid;
+
+ const int32_t WPA_PSK = 0x01;
+ const int32_t WPA_EAP = 0x02;
+ const int32_t WEP = 0x04;
+ readonly attribute uint32_t capabilities;
+
+ /**
+ * Strength of the signal to network.
+ */
+ readonly attribute uint32_t signalStrength;
+
+ readonly attribute uint32_t relSignalStrength;
+ readonly attribute boolean connected;
+};
+
+[scriptable, uuid(a6931ebf-8493-4014-90e2-99f406999982)]
+interface nsIWifiScanResultsReady : nsISupports {
+
+ /**
+ * Callback with list of networks.
+ */
+ void onready(in uint32_t count, [array, size_is(count)] in nsIWifiScanResult results);
+
+ /**
+ * Callback if scanning for networks failed after 3 retry attempts.
+ */
+ void onfailure();
+};
+
+[scriptable, uuid(08dfefed-5c5d-4468-8c5d-2c65c24692d9)]
+interface nsIWifi : nsISupports
+{
+ /**
+ * Shutdown the wifi system.
+ */
+ void shutdown();
+
+ /**
+ * Returns the list of currently available networks as well as the list of
+ * currently configured networks.
+ *
+ * On success a callback is notified with the list of networks.
+ * On failure after 3 scan retry attempts a callback is notified of failure.
+ */
+ void getWifiScanResults(in nsIWifiScanResultsReady callback);
+};
diff --git a/dom/wifi/nsIWifiCertService.idl b/dom/wifi/nsIWifiCertService.idl
new file mode 100644
index 000000000..fce2dd719
--- /dev/null
+++ b/dom/wifi/nsIWifiCertService.idl
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMBlob;
+interface nsIWifiEventListener;
+
+[scriptable, uuid(5d0edcd3-c2f1-4946-aae5-06adcbdf0992)]
+interface nsIWifiCertService : nsISupports
+{
+ const unsigned short WIFI_CERT_USAGE_FLAG_SERVER = 0x01;
+ const unsigned short WIFI_CERT_USAGE_FLAG_USER = 0x02;
+
+ void start(in nsIWifiEventListener listener);
+ void shutdown();
+
+ /**
+ * Import a certificate file.
+ *
+ * @param id
+ * Request ID.
+ * @param certBlob
+ * A Blob object containing raw data of certificate to be imported.
+ * @param certPassword
+ * Password of certificate.
+ * @param certNickname
+ * User assigned nickname for imported certificate.
+ */
+ void importCert(in long id,
+ in nsIDOMBlob certBlob,
+ in DOMString certPassword,
+ in DOMString certNickname);
+
+ /**
+ * Delete an imported certificate file
+ *
+ * @param id
+ * Request ID.
+ * @param certNickname
+ * Certificate nickname to delete.
+ */
+ void deleteCert(in long id,
+ in DOMString certNickname);
+
+ /**
+ * Check if certificate has private key.
+ *
+ * @param certNickname
+ * Certificate nickname to check for private key.
+ */
+ boolean hasPrivateKey(in DOMString certNickname);
+};
diff --git a/dom/wifi/nsIWifiService.idl b/dom/wifi/nsIWifiService.idl
new file mode 100644
index 000000000..0f320ddda
--- /dev/null
+++ b/dom/wifi/nsIWifiService.idl
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(4d4389e0-1547-11e3-8ffd-0800200c9a66)]
+interface nsIWifiEventListener : nsISupports {
+ void onWaitEvent(in AString event, in ACString aInterface);
+ void onCommand(in jsval result, in ACString aInterface);
+};
+
+[scriptable, uuid(5e2bd8c0-1547-11e3-8ffd-0800200c9a66)]
+interface nsIWifiProxyService : nsISupports {
+ void start(in nsIWifiEventListener listener,
+ [array, size_is(aNumOfInterface)] in string aInterfaces,
+ in unsigned long aNumOfInterface);
+ void shutdown();
+ [implicit_jscontext]
+ void sendCommand(in jsval parameters, in ACString aInterface);
+ void waitForEvent(in ACString aInterface);
+};
diff --git a/dom/wifi/test/marionette/head.js b/dom/wifi/test/marionette/head.js
new file mode 100644
index 000000000..f4a212b11
--- /dev/null
+++ b/dom/wifi/test/marionette/head.js
@@ -0,0 +1,1486 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Emulate Promise.jsm semantics.
+Promise.defer = function() { return new Deferred(); }
+function Deferred() {
+ this.promise = new Promise(function(resolve, reject) {
+ this.resolve = resolve;
+ this.reject = reject;
+ }.bind(this));
+ Object.freeze(this);
+}
+
+const STOCK_HOSTAPD_NAME = 'goldfish-hostapd';
+const HOSTAPD_CONFIG_PATH = '/data/misc/wifi/remote-hostapd/';
+
+const SETTINGS_RIL_DATA_ENABLED = 'ril.data.enabled';
+const SETTINGS_TETHERING_WIFI_ENABLED = 'tethering.wifi.enabled';
+const SETTINGS_TETHERING_WIFI_IP = 'tethering.wifi.ip';
+const SETTINGS_TETHERING_WIFI_SECURITY = 'tethering.wifi.security.type';
+
+const HOSTAPD_COMMON_CONFIG = {
+ driver: 'test',
+ ctrl_interface: '/data/misc/wifi/remote-hostapd',
+ test_socket: 'DIR:/data/misc/wifi/sockets',
+ hw_mode: 'b',
+ channel: '2',
+};
+
+const HOSTAPD_CONFIG_LIST = [
+ { ssid: 'ap0' },
+
+ { ssid: 'ap1',
+ wpa: 1,
+ wpa_pairwise: 'TKIP CCMP',
+ wpa_passphrase: '12345678'
+ },
+
+ { ssid: 'ap2',
+ wpa: 2,
+ rsn_pairwise: 'CCMP',
+ wpa_passphrase: '12345678',
+ },
+];
+
+var gTestSuite = (function() {
+ let suite = {};
+
+ // Private member variables of the returned object |suite|.
+ let wifiManager;
+ let wifiOrigEnabled;
+ let pendingEmulatorShellCount = 0;
+ let sdkVersion;
+
+ /**
+ * A wrapper function of "is".
+ *
+ * Calls the marionette function "is" as well as throws an exception
+ * if the givens values are not equal.
+ *
+ * @param value1
+ * Any type of value to compare.
+ *
+ * @param value2
+ * Any type of value to compare.
+ *
+ * @param message
+ * Debug message for this check.
+ *
+ */
+ function isOrThrow(value1, value2, message) {
+ is(value1, value2, message);
+ if (value1 !== value2) {
+ throw message;
+ }
+ }
+
+ /**
+ * Send emulator shell command with safe guard.
+ *
+ * We should only call |finish()| after all emulator command transactions
+ * end, so here comes with the pending counter. Resolve when the emulator
+ * gives positive response, and reject otherwise.
+ *
+ * Fulfill params:
+ * result -- an array of emulator response lines.
+ * Reject params:
+ * result -- an array of emulator response lines.
+ *
+ * @param aCommand
+ * A string command to be passed to emulator through its telnet console.
+ *
+ * @return A deferred promise.
+ */
+ function runEmulatorShellSafe(aCommand) {
+ let deferred = Promise.defer();
+
+ ++pendingEmulatorShellCount;
+ runEmulatorShell(aCommand, function(aResult) {
+ --pendingEmulatorShellCount;
+
+ ok(true, "Emulator shell response: " + JSON.stringify(aResult));
+ if (Array.isArray(aResult)) {
+ deferred.resolve(aResult);
+ } else {
+ deferred.reject(aResult);
+ }
+ });
+
+ return deferred.promise;
+ }
+
+ /**
+ * Wait for one named MozWifiManager event.
+ *
+ * Resolve if that named event occurs. Never reject.
+ *
+ * Fulfill params: the DOMEvent passed.
+ *
+ * @param aEventName
+ * A string event name.
+ *
+ * @return A deferred promise.
+ */
+ function waitForWifiManagerEventOnce(aEventName) {
+ let deferred = Promise.defer();
+
+ wifiManager.addEventListener(aEventName, function onevent(aEvent) {
+ wifiManager.removeEventListener(aEventName, onevent);
+
+ ok(true, "WifiManager event '" + aEventName + "' got.");
+ deferred.resolve(aEvent);
+ });
+
+ return deferred.promise;
+ }
+
+ /**
+ * Wait for one named MozMobileConnection event.
+ *
+ * Resolve if that named event occurs. Never reject.
+ *
+ * Fulfill params: the DOMEvent passed.
+ *
+ * @param aEventName
+ * A string event name.
+ *
+ * @return A deferred promise.
+ */
+ function waitForMobileConnectionEventOnce(aEventName, aServiceId) {
+ aServiceId = aServiceId || 0;
+
+ let deferred = Promise.defer();
+ let mobileconnection = navigator.mozMobileConnections[aServiceId];
+
+ mobileconnection.addEventListener(aEventName, function onevent(aEvent) {
+ mobileconnection.removeEventListener(aEventName, onevent);
+
+ ok(true, "Mobile connection event '" + aEventName + "' got.");
+ deferred.resolve(aEvent);
+ });
+
+ return deferred.promise;
+ }
+
+ /**
+ * Get the detail of currently running processes containing the given name.
+ *
+ * Use shell command 'ps' to get the desired process's detail. Never reject.
+ *
+ * Fulfill params:
+ * result -- an array of { pname, pid }
+ *
+ * @param aProcessName
+ * The process to get the detail.
+ *
+ * @return A deferred promise.
+ */
+ function getProcessDetail(aProcessName) {
+ return runEmulatorShellSafe(['ps'])
+ .then(processes => {
+ // Sample 'ps' output:
+ //
+ // USER PID PPID VSIZE RSS WCHAN PC NAME
+ // root 1 0 284 204 c009e6c4 0000deb4 S /init
+ // root 2 0 0 0 c0052c64 00000000 S kthreadd
+ // root 3 2 0 0 c0044978 00000000 S ksoftirqd/0
+ //
+ let detail = [];
+
+ processes.shift(); // Skip the first line.
+ for (let i = 0; i < processes.length; i++) {
+ let tokens = processes[i].split(/\s+/);
+ let pname = tokens[tokens.length - 1];
+ let pid = tokens[1];
+ if (-1 !== pname.indexOf(aProcessName)) {
+ detail.push({ pname: pname, pid: pid });
+ }
+ }
+
+ return detail;
+ });
+ }
+
+ /**
+ * Add required permissions for wifi testing. Never reject.
+ *
+ * The permissions required for wifi testing are 'wifi-manage' and 'settings-write'.
+ * Never reject.
+ *
+ * Fulfill params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function addRequiredPermissions() {
+ let deferred = Promise.defer();
+
+ let permissions = [{ 'type': 'wifi-manage', 'allow': 1, 'context': window.document },
+ { 'type': 'settings-write', 'allow': 1, 'context': window.document },
+ { 'type': 'settings-read', 'allow': 1, 'context': window.document },
+ { 'type': 'settings-api-write', 'allow': 1, 'context': window.document },
+ { 'type': 'settings-api-read', 'allow': 1, 'context': window.document },
+ { 'type': 'mobileconnection', 'allow': 1, 'context': window.document }];
+
+ SpecialPowers.pushPermissions(permissions, function() {
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ }
+
+ /**
+ * Wrap DOMRequest onsuccess/onerror events to Promise resolve/reject.
+ *
+ * Fulfill params: A DOMEvent.
+ * Reject params: A DOMEvent.
+ *
+ * @param aRequest
+ * A DOMRequest instance.
+ *
+ * @return A deferred promise.
+ */
+ function wrapDomRequestAsPromise(aRequest) {
+ let deferred = Promise.defer();
+
+ ok(aRequest instanceof DOMRequest,
+ "aRequest is instanceof " + aRequest.constructor);
+
+ aRequest.addEventListener("success", function(aEvent) {
+ deferred.resolve(aEvent);
+ });
+ aRequest.addEventListener("error", function(aEvent) {
+ deferred.reject(aEvent);
+ });
+
+ return deferred.promise;
+ }
+
+ /**
+ * Ensure wifi is enabled/disabled.
+ *
+ * Issue a wifi enable/disable request if wifi is not in the desired state;
+ * return a resolved promise otherwise. Note that you cannot rely on this
+ * function to test the correctness of enabling/disabling wifi.
+ * (use requestWifiEnabled instead)
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @return a resolved promise or deferred promise.
+ */
+ function ensureWifiEnabled(aEnabled, useAPI) {
+ if (wifiManager.enabled === aEnabled) {
+ log('Already ' + (aEnabled ? 'enabled' : 'disabled'));
+ return Promise.resolve();
+ }
+ return requestWifiEnabled(aEnabled, useAPI);
+ }
+
+ /**
+ * Issue a request to enable/disable wifi.
+ *
+ * This function will attempt to enable/disable wifi, by calling API or by
+ * writing settings 'wifi.enabled' regardless of the wifi state, based on the
+ * value of |userAPI| parameter.
+ * Default is using settings.
+ *
+ * Note there's a limitation of co-existance of both method, per bug 930355,
+ * that once enable/disable wifi by API, the settings method won't work until
+ * reboot. So the test of wifi enable API should be executed last.
+ * TODO: Remove settings method after enable/disable wifi by settings is
+ * removed after bug 1050147.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function requestWifiEnabled(aEnabled, useAPI) {
+ return Promise.all([
+ waitForWifiManagerEventOnce(aEnabled ? 'enabled' : 'disabled'),
+ useAPI ?
+ wrapDomRequestAsPromise(wifiManager.setWifiEnabled(aEnabled)) :
+ setSettings({ 'wifi.enabled': aEnabled }),
+ ]);
+ }
+
+ /**
+ * Wait for RIL data being connected.
+ *
+ * This function will check |MozMobileConnection.data.connected| on
+ * every 'datachange' event. Resolve when |MozMobileConnection.data.connected|
+ * becomes the expected state. Never reject.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aConnected
+ * Boolean that indicates the desired data state.
+ *
+ * @param aServiceId [optional]
+ * A numeric DSDS service id. Default: 0.
+ *
+ * @return A deferred promise.
+ */
+ function waitForRilDataConnected(aConnected, aServiceId) {
+ aServiceId = aServiceId || 0;
+ return waitForMobileConnectionEventOnce('datachange', aServiceId)
+ .then(function () {
+ let mobileconnection = navigator.mozMobileConnections[aServiceId];
+ if (mobileconnection.data.connected !== aConnected) {
+ return waitForRilDataConnected(aConnected, aServiceId);
+ }
+ });
+ }
+
+ /**
+ * Request to enable/disable wifi tethering.
+ *
+ * Enable/disable wifi tethering by changing the settings value 'tethering.wifi.enabled'.
+ * Resolve when the routing is verified to set up successfully in 20 seconds. The polling
+ * period is 1 second.
+ *
+ * Fulfill params: (none)
+ * Reject params: The error message.
+ *
+ * @param aEnabled
+ * Boolean that indicates to enable or disable wifi tethering.
+ *
+ * @return A deferred promise.
+ */
+ function requestTetheringEnabled(aEnabled) {
+ let RETRY_INTERVAL_MS = 1000;
+ let retryCnt = 20;
+
+ return setSettings1(SETTINGS_TETHERING_WIFI_ENABLED, aEnabled)
+ .then(function waitForRoutingVerified() {
+ return verifyTetheringRouting(aEnabled)
+ .then(null, function onreject(aReason) {
+
+ log('verifyTetheringRouting rejected due to ' + aReason +
+ ' (' + retryCnt + ')');
+
+ if (!retryCnt--) {
+ throw aReason;
+ }
+
+ return waitForTimeout(RETRY_INTERVAL_MS).then(waitForRoutingVerified);
+ });
+ });
+ }
+
+ /**
+ * Forget the given network.
+ *
+ * Resolve when we successfully forget the given network; reject when any error
+ * occurs.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aNetwork
+ * An object of MozWifiNetwork.
+ *
+ * @return A deferred promise.
+ */
+ function forgetNetwork(aNetwork) {
+ let request = wifiManager.forget(aNetwork);
+ return wrapDomRequestAsPromise(request)
+ .then(event => event.target.result);
+ }
+
+ /**
+ * Forget all known networks.
+ *
+ * Resolve when we successfully forget all the known network;
+ * reject when any error occurs.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function forgetAllKnownNetworks() {
+
+ function createForgetNetworkChain(aNetworks) {
+ let chain = Promise.resolve();
+
+ aNetworks.forEach(function (aNetwork) {
+ chain = chain.then(() => forgetNetwork(aNetwork));
+ });
+
+ return chain;
+ }
+
+ return getKnownNetworks()
+ .then(networks => createForgetNetworkChain(networks));
+ }
+
+ /**
+ * Get all known networks.
+ *
+ * Resolve when we get all the known networks; reject when any error
+ * occurs.
+ *
+ * Fulfill params: An array of MozWifiNetwork
+ * Reject params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function getKnownNetworks() {
+ let request = wifiManager.getKnownNetworks();
+ return wrapDomRequestAsPromise(request)
+ .then(event => event.target.result);
+ }
+
+ /**
+ * Set the given network to static ip mode.
+ *
+ * Resolve when we set static ip mode successfully; reject when any error
+ * occurs.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function setStaticIpMode(aNetwork, aConfig) {
+ let request = wifiManager.setStaticIpMode(aNetwork, aConfig);
+ return wrapDomRequestAsPromise(request)
+ .then(event => event.target.result);
+ }
+
+ /**
+ * Issue a request to scan all wifi available networks.
+ *
+ * Resolve when we get the scan result; reject when any error
+ * occurs.
+ *
+ * Fulfill params: An array of MozWifiNetwork
+ * Reject params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function requestWifiScan() {
+ let request = wifiManager.getNetworks();
+ return wrapDomRequestAsPromise(request)
+ .then(event => event.target.result);
+ }
+
+ /**
+ * Import a certificate with nickname and password.
+ *
+ * Resolve when we import certificate successfully; reject when any error
+ * occurs.
+ *
+ * Fulfill params: An object of certificate information.
+ * Reject params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function importCert(certBlob, password, nickname) {
+ let request = wifiManager.importCert(certBlob, password, nickname);
+ return wrapDomRequestAsPromise(request)
+ .then(event => event.target.result);
+ }
+
+ /**
+ * Delete certificate of nickname.
+ *
+ * Resolve when we delete certificate successfully; reject when any error
+ * occurs.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function deleteCert(nickname) {
+ let request = wifiManager.deleteCert(nickname);
+ return wrapDomRequestAsPromise(request)
+ .then(event => event.target.result);
+ }
+
+ /**
+ * Get list of imported certificates.
+ *
+ * Resolve when we get certificate list successfully; reject when any error
+ * occurs.
+ *
+ * Fulfill params: Nickname of imported certificate arranged by usage.
+ * Reject params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function getImportedCerts() {
+ let request = wifiManager.getImportedCerts();
+ return wrapDomRequestAsPromise(request)
+ .then(event => event.target.result);
+ }
+
+ /**
+ * Request wifi scan and verify the scan result as well.
+ *
+ * Issue a wifi scan request and check if the result is expected.
+ * Since the old APs may be cached and the newly added APs may be
+ * still not scan-able, a couple of attempts are acceptable.
+ * Resolve if we eventually get the expected scan result; reject otherwise.
+ *
+ * Fulfill params: The scan result, which is an array of MozWifiNetwork
+ * Reject params: (none)
+ *
+ * @param aRetryCnt
+ * The maxmimum number of attempts until we get the expected scan result.
+ * @param aExpectedNetworks
+ * An array of object, each of which contains at least the |ssid| property.
+ *
+ * @return A deferred promise.
+ */
+ function testWifiScanWithRetry(aRetryCnt, aExpectedNetworks) {
+
+ // Check if every single ssid of each |aScanResult| exists in |aExpectedNetworks|
+ // as well as the length of |aScanResult| equals to |aExpectedNetworks|.
+ function isScanResultExpected(aScanResult) {
+ if (aScanResult.length !== aExpectedNetworks.length) {
+ return false;
+ }
+
+ for (let i = 0; i < aScanResult.length; i++) {
+ if (-1 === getFirstIndexBySsid(aScanResult[i].ssid, aExpectedNetworks)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return requestWifiScan()
+ .then(function (networks) {
+ if (isScanResultExpected(networks, aExpectedNetworks)) {
+ return networks;
+ }
+ if (aRetryCnt > 0) {
+ return testWifiScanWithRetry(aRetryCnt - 1, aExpectedNetworks);
+ }
+ throw 'Unexpected scan result!';
+ });
+ }
+
+ /**
+ * Test wifi association.
+ *
+ * Associate with the given network object which is obtained by
+ * MozWifiManager.getNetworks() (i.e. MozWifiNetwork).
+ * Resolve when the 'connected' status change event is received.
+ * Note that we might see other events like 'connecting'
+ * before 'connected'. So we need to call |waitForWifiManagerEventOnce|
+ * again whenever non 'connected' event is seen. Never reject.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aNetwork
+ * An object of MozWifiNetwork.
+ *
+ * @return A deferred promise.
+ */
+ function testAssociate(aNetwork) {
+ setPasswordIfNeeded(aNetwork);
+
+ let promises = [];
+
+ // Register the event listerner to wait for 'connected' event first
+ // to avoid racing issue.
+ promises.push(waitForConnected(aNetwork));
+
+ // Then we do the association.
+ let request = wifiManager.associate(aNetwork);
+ promises.push(wrapDomRequestAsPromise(request));
+
+ return Promise.all(promises);
+ }
+
+ function waitForConnected(aExpectedNetwork) {
+ return waitForWifiManagerEventOnce('statuschange')
+ .then(function onstatuschange(event) {
+ log("event.status: " + event.status);
+ log("event.network.ssid: " + (event.network ? event.network.ssid : ''));
+
+ if ("connected" === event.status &&
+ event.network.ssid === aExpectedNetwork.ssid) {
+ return; // Got expected 'connected' event from aNetwork.ssid.
+ }
+
+ log('Not expected "connected" statuschange event. Wait again!');
+ return waitForConnected(aExpectedNetwork);
+ });
+ }
+
+ /**
+ * Set the password for associating the given network if needed.
+ *
+ * Set the password by looking up HOSTAPD_CONFIG_LIST. This function
+ * will also set |keyManagement| properly.
+ *
+ * @param aNetwork
+ * The MozWifiNetwork object.
+ */
+ function setPasswordIfNeeded(aNetwork) {
+ let i = getFirstIndexBySsid(aNetwork.ssid, HOSTAPD_CONFIG_LIST);
+ if (-1 === i) {
+ log('unknown ssid: ' + aNetwork.ssid);
+ return; // Unknown network. Assume insecure.
+ }
+
+ if (!aNetwork.security.length) {
+ return; // No need to set password.
+ }
+
+ let security = aNetwork.security[0];
+ if (/PSK$/.test(security)) {
+ aNetwork.psk = HOSTAPD_CONFIG_LIST[i].wpa_passphrase;
+ aNetwork.keyManagement = 'WPA-PSK';
+ } else if (/WEP$/.test(security)) {
+ aNetwork.wep = HOSTAPD_CONFIG_LIST[i].wpa_passphrase;
+ aNetwork.keyManagement = 'WEP';
+ }
+ }
+
+ /**
+ * Set mozSettings values.
+ *
+ * Resolve if that mozSettings value is set successfully, reject otherwise.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aSettings
+ * An object of format |{key1: value1, key2: value2, ...}|.
+ * @param aAllowError [optional]
+ * A boolean value. If set to true, an error response won't be treated
+ * as test failure. Default: false.
+ *
+ * @return A deferred promise.
+ */
+ function setSettings(aSettings) {
+ let lock = window.navigator.mozSettings.createLock();
+ let request = lock.set(aSettings);
+ let deferred = Promise.defer();
+ lock.onsettingstransactionsuccess = function () {
+ ok(true, "setSettings(" + JSON.stringify(aSettings) + ")");
+ deferred.resolve();
+ };
+ lock.onsettingstransactionfailure = function (aEvent) {
+ ok(false, "setSettings(" + JSON.stringify(aSettings) + ")");
+ deferred.reject();
+ throw aEvent.target.error;
+ };
+ return deferred.promise;
+ }
+
+ /**
+ * Set mozSettings value with only one key.
+ *
+ * Resolve if that mozSettings value is set successfully, reject otherwise.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aKey
+ * A string key.
+ * @param aValue
+ * An object value.
+ * @param aAllowError [optional]
+ * A boolean value. If set to true, an error response won't be treated
+ * as test failure. Default: false.
+ *
+ * @return A deferred promise.
+ */
+ function setSettings1(aKey, aValue, aAllowError) {
+ let settings = {};
+ settings[aKey] = aValue;
+ return setSettings(settings, aAllowError);
+ }
+
+ /**
+ * Get mozSettings value specified by @aKey.
+ *
+ * Resolve if that mozSettings value is retrieved successfully, reject
+ * otherwise.
+ *
+ * Fulfill params:
+ * The corresponding mozSettings value of the key.
+ * Reject params: (none)
+ *
+ * @param aKey
+ * A string.
+ * @param aAllowError [optional]
+ * A boolean value. If set to true, an error response won't be treated
+ * as test failure. Default: false.
+ *
+ * @return A deferred promise.
+ */
+ function getSettings(aKey, aAllowError) {
+ let request =
+ navigator.mozSettings.createLock().get(aKey);
+ return wrapDomRequestAsPromise(request)
+ .then(function resolve(aEvent) {
+ ok(true, "getSettings(" + aKey + ") - success");
+ return aEvent.target.result[aKey];
+ }, function reject(aEvent) {
+ ok(aAllowError, "getSettings(" + aKey + ") - error");
+ });
+ }
+
+
+ /**
+ * Start hostapd processes with given configuration list.
+ *
+ * For starting one hostapd, we need to generate a specific config file
+ * then launch a hostapd process with the confg file path passed. The
+ * config file is generated by two sources: one is the common
+ * part (HOSTAPD_COMMON_CONFIG) and the other is from the given |aConfigList|.
+ * Resolve when all the hostpads are requested to start. It is not guaranteed
+ * that all the hostapds will be up and running successfully. Never reject.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aConfigList
+ * An array of config objects, each property in which will be
+ * output to the confg file with the format: [key]=[value] in one line.
+ * See http://hostap.epitest.fi/cgit/hostap/plain/hostapd/hostapd.conf
+ * for more information.
+ *
+ * @return A deferred promise.
+ */
+ function startHostapds(aConfigList) {
+
+ function createConfigFromCommon(aIndex) {
+ // Create an copy of HOSTAPD_COMMON_CONFIG.
+ let config = JSON.parse(JSON.stringify(HOSTAPD_COMMON_CONFIG));
+
+ // Add user config.
+ for (let key in aConfigList[aIndex]) {
+ config[key] = aConfigList[aIndex][key];
+ }
+
+ // 'interface' is a required field but no need of being configurable
+ // for a test case. So we initialize this field on our own.
+ config.interface = 'AP-' + aIndex;
+
+ return config;
+ }
+
+ function startOneHostapd(aIndex) {
+ let configFileName = HOSTAPD_CONFIG_PATH + 'ap' + aIndex + '.conf';
+ return writeHostapdConfFile(configFileName, createConfigFromCommon(aIndex))
+ .then(() => runEmulatorShellSafe(['hostapd', '-B', configFileName]))
+ .then(function (reply) {
+ // It may fail at the first time due to the previous ungracefully terminated one.
+ if (reply.length === 0) {
+ // The hostapd starts successfully
+ return;
+ }
+
+ if (reply[0].indexOf('bind(PF_UNIX): Address already in use') !== -1) {
+ return startOneHostapd(aIndex);
+ }
+ });
+ }
+
+ return Promise.all(aConfigList.map(function(aConfig, aIndex) {
+ return startOneHostapd(aIndex);
+ }));
+ }
+
+ /**
+ * Kill all the running hostapd processes.
+ *
+ * Use shell command 'kill -9' to kill all hostapds. Never reject.
+ *
+ * Fulfill params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function killAllHostapd() {
+ return getProcessDetail('hostapd')
+ .then(function (runningHostapds) {
+ let promises = runningHostapds.map(runningHostapd => {
+ return runEmulatorShellSafe(['kill', '-9', runningHostapd.pid]);
+ });
+ return Promise.all(promises);
+ });
+ }
+
+ /**
+ * Write out the config file to the given path.
+ *
+ * For each key/value pair in |aConfig|,
+ *
+ * [key]=[value]
+ *
+ * will be output to one new line. Never reject.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aFilePath
+ * The file path that we desire the config file to be located.
+ *
+ * @param aConfig
+ * The config object.
+ *
+ * @return A deferred promise.
+ */
+ function writeHostapdConfFile(aFilePath, aConfig) {
+ let content = '';
+ for (let key in aConfig) {
+ if (aConfig.hasOwnProperty(key)) {
+ content += (key + '=' + aConfig[key] + '\n');
+ }
+ }
+ return writeFile(aFilePath, content);
+ }
+
+ /**
+ * Write file to the given path filled with given content.
+ *
+ * For now it is implemented by shell command 'echo'. Also, if the
+ * content contains whitespace, we need to quote the content to
+ * avoid error. Never reject.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aFilePath
+ * The file path that we desire the file to be located.
+ *
+ * @param aContent
+ * The content as string which should be written to the file.
+ *
+ * @return A deferred promise.
+ */
+ function writeFile(aFilePath, aContent) {
+ const CONTENT_MAX_LENGTH = 900;
+ var commands = [];
+ for (var i = 0; i < aContent.length; i += CONTENT_MAX_LENGTH) {
+ var content = aContent.substr(i, CONTENT_MAX_LENGTH);
+ if (-1 === content.indexOf(' ')) {
+ content = '"' + content + '"';
+ }
+ commands.push(['echo', '-n', content, i === 0 ? '>' : '>>', aFilePath]);
+ }
+
+ let chain = Promise.resolve();
+ commands.forEach(function (command) {
+ chain = chain.then(() => runEmulatorShellSafe(command));
+ });
+ return chain;
+ }
+
+ /**
+ * Check if a init service is running or not.
+ *
+ * Check the android property 'init.svc.[aServiceName]' to determine if
+ * a init service is running. Reject if the propery is neither 'running'
+ * nor 'stopped'.
+ *
+ * Fulfill params:
+ * result -- |true| if the init service is running; |false| otherwise.
+ * Reject params: (none)
+ *
+ * @param aServiceName
+ * The init service name.
+ *
+ * @return A deferred promise.
+ */
+ function isInitServiceRunning(aServiceName) {
+ return runEmulatorShellSafe(['getprop', 'init.svc.' + aServiceName])
+ .then(function (result) {
+ if ('running' !== result[0] && 'stopped' !== result[0]) {
+ throw 'Init service running state should be "running" or "stopped".';
+ }
+ return 'running' === result[0];
+ });
+ }
+
+ /**
+ * Wait for timeout.
+ *
+ * Resolve when the given duration elapsed. Never reject.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aTimeoutMs
+ * The duration after which the timeout event should occurs.
+ *
+ * @return A deferred promise.
+ */
+ function waitForTimeout(aTimeoutMs) {
+ let deferred = Promise.defer();
+
+ setTimeout(function() {
+ deferred.resolve();
+ }, aTimeoutMs);
+
+ return deferred.promise;
+ }
+
+ /**
+ * Start or stop an init service.
+ *
+ * Use shell command 'start'/'stop' to start/stop an init service.
+ * The running state will also be checked after we start/stop the service.
+ * Resolve if the service is successfully started/stopped; Reject otherwise.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aServiceName
+ * The name of the service we want to start/stop.
+ *
+ * @param aStart
+ * |true| for starting the init service. |false| for stopping.
+ *
+ * @return A deferred promise.
+ */
+ function startStopInitService(aServiceName, aStart) {
+ let retryCnt = 5;
+
+ return runEmulatorShellSafe([aStart ? 'start' : 'stop', aServiceName])
+ .then(() => isInitServiceRunning(aServiceName))
+ .then(function onIsServiceRunningResolved(aIsRunning) {
+ if (aStart === aIsRunning) {
+ return;
+ }
+
+ if (retryCnt-- > 0) {
+ log('Failed to ' + (aStart ? 'start ' : 'stop ') + aServiceName +
+ '. Retry: ' + retryCnt);
+
+ return waitForTimeout(500)
+ .then(() => isInitServiceRunning(aServiceName))
+ .then(onIsServiceRunningResolved);
+ }
+
+ throw 'Failed to ' + (aStart ? 'start' : 'stop') + ' ' + aServiceName;
+ });
+ }
+
+ /**
+ * Start the stock hostapd.
+ *
+ * Since the stock hostapd is an init service, use |startStopInitService| to
+ * start it. Note that we might fail to start the stock hostapd at the first time
+ * for unknown reason so give it the second chance to start again.
+ * Resolve when we are eventually successful to start the stock hostapd; Reject
+ * otherwise.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function startStockHostapd() {
+ return startStopInitService(STOCK_HOSTAPD_NAME, true)
+ .then(null, function onreject() {
+ log('Failed to restart goldfish-hostapd at the first time. Try again!');
+ return startStopInitService((STOCK_HOSTAPD_NAME), true);
+ });
+ }
+
+ /**
+ * Stop the stock hostapd.
+ *
+ * Since the stock hostapd is an init service, use |startStopInitService| to
+ * stop it.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function stopStockHostapd() {
+ return startStopInitService(STOCK_HOSTAPD_NAME, false);
+ }
+
+ /**
+ * Get the index of the first matching entry by |ssid|.
+ *
+ * Find the index of the first entry of |aArray| which property |ssid|
+ * is same as |aSsid|.
+ *
+ * @param aSsid
+ * The ssid that we want to match.
+ * @param aArray
+ * An array of objects, each of which should have the property |ssid|.
+ *
+ * @return The 0-based index of first matching entry if found; -1 otherwise.
+ */
+ function getFirstIndexBySsid(aSsid, aArray) {
+ for (let i = 0; i < aArray.length; i++) {
+ if (aArray[i].ssid === aSsid) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Count the number of running process and verify if the count is expected.
+ *
+ * Return a promise that resolves when the process has expected number
+ * of running instances and rejects otherwise.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aOrigWifiEnabled
+ * Boolean which indicates wifi was originally enabled.
+ *
+ * @return A deferred promise.
+ */
+ function verifyNumOfProcesses(aProcessName, aExpectedNum) {
+ return getProcessDetail(aProcessName)
+ .then(function (detail) {
+ if (detail.length === aExpectedNum) {
+ return;
+ }
+ throw 'Unexpected number of running processes:' + aProcessName +
+ ', expected: ' + aExpectedNum + ', actual: ' + detail.length;
+ });
+ }
+
+ /**
+ * Execute 'netcfg' shell and parse the result.
+ *
+ * Resolve when the executing is successful and reject otherwise.
+ *
+ * Fulfill params: Command result object, each key of which is the interface
+ * name and value is { ip(string), prefix(string) }.
+ * Reject params: String that indicates the reason of rejection.
+ *
+ * @return A deferred promise.
+ */
+ function exeAndParseNetcfg() {
+ return runEmulatorShellSafe(['netcfg'])
+ .then(function (aLines) {
+ // Sample output:
+ //
+ // lo UP 127.0.0.1/8 0x00000049 00:00:00:00:00:00
+ // eth0 UP 10.0.2.15/24 0x00001043 52:54:00:12:34:56
+ // rmnet1 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:58
+ // rmnet2 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:59
+ // rmnet3 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:5a
+ // wlan0 UP 192.168.1.1/24 0x00001043 52:54:00:12:34:5b
+ // sit0 DOWN 0.0.0.0/0 0x00000080 00:00:00:00:00:00
+ // rmnet0 UP 10.0.2.100/24 0x00001043 52:54:00:12:34:57
+ //
+ let netcfgResult = {};
+ aLines.forEach(function (aLine) {
+ let tokens = aLine.split(/\s+/);
+ if (tokens.length < 5) {
+ return;
+ }
+ let ifname = tokens[0];
+ let [ip, prefix] = tokens[2].split('/');
+ netcfgResult[ifname] = { ip: ip, prefix: prefix };
+ });
+ log("netcfg result:" + JSON.stringify(netcfgResult));
+
+ return netcfgResult;
+ });
+ }
+
+ /**
+ * Execute 'ip route' and parse the result.
+ *
+ * Resolve when the executing is successful and reject otherwise.
+ *
+ * Fulfill params: Command result object, each key of which is the interface
+ * name and value is { src(string), gateway(string),
+ * default(boolean) }.
+ * Reject params: String that indicates the reason of rejection.
+ *
+ * @return A deferred promise.
+ */
+ function exeAndParseIpRoute() {
+ return runEmulatorShellSafe(['ip', 'route'])
+ .then(function (aLines) {
+ // Sample output:
+ //
+ // 10.0.2.4 via 10.0.2.2 dev rmnet0
+ // 10.0.2.3 via 10.0.2.2 dev rmnet0
+ // 192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.1
+ // 10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15
+ // 10.0.2.0/24 dev rmnet0 proto kernel scope link src 10.0.2.100
+ // default via 10.0.2.2 dev rmnet0
+ // default via 10.0.2.2 dev eth0 metric 2
+ //
+
+ let ipRouteResult = {};
+
+ // Parse source ip for each interface.
+ aLines.forEach(function (aLine) {
+ let tokens = aLine.trim().split(/\s+/);
+ let srcIndex = tokens.indexOf('src');
+ if (srcIndex < 0 || srcIndex + 1 >= tokens.length) {
+ return;
+ }
+ let ifname = tokens[2];
+ let src = tokens[srcIndex + 1];
+ ipRouteResult[ifname] = { src: src, default: false, gateway: null };
+ });
+
+ // Parse default interfaces.
+ aLines.forEach(function (aLine) {
+ let tokens = aLine.split(/\s+/);
+ if (tokens.length < 2) {
+ return;
+ }
+ if ('default' === tokens[0]) {
+ let ifnameIndex = tokens.indexOf('dev');
+ if (ifnameIndex < 0 || ifnameIndex + 1 >= tokens.length) {
+ return;
+ }
+ let ifname = tokens[ifnameIndex + 1];
+ if (!ipRouteResult[ifname]) {
+ return;
+ }
+ ipRouteResult[ifname].default = true;
+ let gwIndex = tokens.indexOf('via');
+ if (gwIndex < 0 || gwIndex + 1 >= tokens.length) {
+ return;
+ }
+ ipRouteResult[ifname].gateway = tokens[gwIndex + 1];
+ return;
+ }
+ });
+ log("ip route result:" + JSON.stringify(ipRouteResult));
+
+ return ipRouteResult;
+ });
+ }
+
+ /**
+ * Verify everything about routing when the wifi tethering is either on or off.
+ *
+ * We use two unix commands to verify the routing: 'netcfg' and 'ip route'.
+ * For now the following two things will be checked:
+ * 1) The default route interface should be 'rmnet0'.
+ * 2) wlan0 is up and its ip is set to a private subnet.
+ *
+ * We also verify iptables output as netd's NatController will execute
+ * $ iptables -t nat -A POSTROUTING -o rmnet0 -j MASQUERADE
+ *
+ * Resolve when the verification is successful and reject otherwise.
+ *
+ * Fulfill params: (none)
+ * Reject params: String that indicates the reason of rejection.
+ *
+ * @return A deferred promise.
+ */
+ function verifyTetheringRouting(aEnabled) {
+ let netcfgResult;
+ let ipRouteResult;
+
+ // Find MASQUERADE in POSTROUTING section. 'MASQUERADE' should be found
+ // when tethering is enabled. 'MASQUERADE' shouldn't be found when tethering
+ // is disabled.
+ function verifyIptables() {
+ let MASQUERADE_checkSection = 'POSTROUTING';
+ if (sdkVersion > 15) {
+ // Check 'natctrl_nat_POSTROUTING' section after ICS.
+ MASQUERADE_checkSection = 'natctrl_nat_POSTROUTING';
+ }
+
+ return runEmulatorShellSafe(['iptables', '-t', 'nat', '-L', MASQUERADE_checkSection])
+ .then(function(aLines) {
+ // $ iptables -t nat -L POSTROUTING
+ //
+ // Sample output (tethering on):
+ //
+ // Chain POSTROUTING (policy ACCEPT)
+ // target prot opt source destination
+ // MASQUERADE all -- anywhere anywhere
+ //
+ let found = (function find_MASQUERADE() {
+ // Skip first two lines.
+ for (let i = 2; i < aLines.length; i++) {
+ if (-1 !== aLines[i].indexOf('MASQUERADE')) {
+ return true;
+ }
+ }
+ return false;
+ })();
+
+ if ((aEnabled && !found) || (!aEnabled && found)) {
+ throw 'MASQUERADE' + (found ? '' : ' not') + ' found while tethering is ' +
+ (aEnabled ? 'enabled' : 'disabled');
+ }
+ });
+ }
+
+ function verifyDefaultRouteAndIp(aExpectedWifiTetheringIp) {
+ if (aEnabled) {
+ isOrThrow(ipRouteResult['rmnet0'].src, netcfgResult['rmnet0'].ip, 'rmnet0.ip');
+ isOrThrow(ipRouteResult['rmnet0'].default, true, 'rmnet0.default');
+
+ isOrThrow(ipRouteResult['wlan0'].src, netcfgResult['wlan0'].ip, 'wlan0.ip');
+ isOrThrow(ipRouteResult['wlan0'].src, aExpectedWifiTetheringIp, 'expected ip');
+ isOrThrow(ipRouteResult['wlan0'].default, false, 'wlan0.default');
+ }
+ }
+
+ return verifyIptables()
+ .then(exeAndParseNetcfg)
+ .then((aResult) => { netcfgResult = aResult; })
+ .then(exeAndParseIpRoute)
+ .then((aResult) => { ipRouteResult = aResult; })
+ .then(() => getSettings(SETTINGS_TETHERING_WIFI_IP))
+ .then(ip => verifyDefaultRouteAndIp(ip));
+ }
+
+ /**
+ * Clean up all the allocated resources and running services for the test.
+ *
+ * After the test no matter success or failure, we should
+ * 1) Restore to the wifi original state (enabled or disabled)
+ * 2) Wait until all pending emulator shell commands are done.
+ *
+ * |finsih| will be called in the end.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @return A deferred promise.
+ */
+ function cleanUp() {
+ waitFor(function() {
+ return ensureWifiEnabled(true)
+ .then(forgetAllKnownNetworks)
+ .then(() => ensureWifiEnabled(wifiOrigEnabled))
+ .then(finish);
+ }, function() {
+ return pendingEmulatorShellCount === 0;
+ });
+ }
+
+ /**
+ * Init the test environment.
+ *
+ * Mainly add the required permissions and initialize the wifiManager
+ * and the orignal state of wifi. Reject if failing to create
+ * window.navigator.mozWifiManager; resolve if all is well.
+ *
+ * |finsih| will be called in the end.
+ *
+ * Fulfill params: (none)
+ * Reject params: The reject reason.
+ *
+ * @return A deferred promise.
+ */
+ function initTestEnvironment() {
+ return addRequiredPermissions()
+ .then(function() {
+ wifiManager = window.navigator.mozWifiManager;
+ if (!wifiManager) {
+ throw 'window.navigator.mozWifiManager is NULL';
+ }
+ wifiOrigEnabled = wifiManager.enabled;
+ })
+ .then(() => runEmulatorShellSafe(['getprop', 'ro.build.version.sdk']))
+ .then(aLines => {
+ sdkVersion = parseInt(aLines[0]);
+ });
+ }
+
+ //---------------------------------------------------
+ // Public test suite functions
+ //---------------------------------------------------
+ suite.getWifiManager = (() => wifiManager);
+ suite.ensureWifiEnabled = ensureWifiEnabled;
+ suite.requestWifiEnabled = requestWifiEnabled;
+ suite.startHostapds = startHostapds;
+ suite.getProcessDetail = getProcessDetail;
+ suite.killAllHostapd = killAllHostapd;
+ suite.wrapDomRequestAsPromise = wrapDomRequestAsPromise;
+ suite.waitForWifiManagerEventOnce = waitForWifiManagerEventOnce;
+ suite.verifyNumOfProcesses = verifyNumOfProcesses;
+ suite.testWifiScanWithRetry = testWifiScanWithRetry;
+ suite.getFirstIndexBySsid = getFirstIndexBySsid;
+ suite.testAssociate = testAssociate;
+ suite.getKnownNetworks = getKnownNetworks;
+ suite.setStaticIpMode = setStaticIpMode;
+ suite.requestWifiScan = requestWifiScan;
+ suite.waitForConnected = waitForConnected;
+ suite.forgetNetwork = forgetNetwork;
+ suite.waitForTimeout = waitForTimeout;
+ suite.waitForRilDataConnected = waitForRilDataConnected;
+ suite.requestTetheringEnabled = requestTetheringEnabled;
+ suite.importCert = importCert;
+ suite.getImportedCerts = getImportedCerts;
+ suite.deleteCert = deleteCert;
+ suite.writeFile = writeFile;
+ suite.exeAndParseNetcfg = exeAndParseNetcfg;
+ suite.exeAndParseIpRoute = exeAndParseIpRoute;
+
+ /**
+ * Common test routine.
+ *
+ * Start a test with the given test case chain. The test environment will be
+ * settled down before the test. After the test, all the affected things will
+ * be restored.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aTestCaseChain
+ * The test case entry point, which can be a function or a promise.
+ *
+ * @return A deferred promise.
+ */
+ suite.doTest = function(aTestCaseChain) {
+ return initTestEnvironment()
+ .then(aTestCaseChain)
+ .then(function onresolve() {
+ cleanUp();
+ }, function onreject(aReason) {
+ ok(false, 'Promise rejects during test' + (aReason ? '(' + aReason + ')' : ''));
+ cleanUp();
+ });
+ };
+
+ /**
+ * Common test routine without the presence of stock hostapd.
+ *
+ * Same as doTest except stopping the stock hostapd before test
+ * and restarting it after test.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aTestCaseChain
+ * The test case entry point, which can be a function or a promise.
+ *
+ * @return A deferred promise.
+ */
+ suite.doTestWithoutStockAp = function(aTestCaseChain) {
+ return suite.doTest(function() {
+ return stopStockHostapd()
+ .then(aTestCaseChain)
+ .then(startStockHostapd);
+ });
+ };
+
+ /**
+ * The common test routine for wifi tethering.
+ *
+ * Similar as doTest except that it will set 'ril.data.enabled' to true
+ * before testing and restore it afterward. It will also verify 'ril.data.enabled'
+ * and 'tethering.wifi.enabled' to be false in the beginning. Note that this routine
+ * will NOT change the state of 'tethering.wifi.enabled' so the user should enable
+ * than disable on his/her own. This routine will only check if tethering is turned
+ * off after testing.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aTestCaseChain
+ * The test case entry point, which can be a function or a promise.
+ *
+ * @return A deferred promise.
+ */
+ suite.doTestTethering = function(aTestCaseChain) {
+
+ function verifyInitialState() {
+ return getSettings(SETTINGS_RIL_DATA_ENABLED)
+ .then(enabled => isOrThrow(enabled, false, SETTINGS_RIL_DATA_ENABLED))
+ .then(() => getSettings(SETTINGS_TETHERING_WIFI_ENABLED))
+ .then(enabled => isOrThrow(enabled, false, SETTINGS_TETHERING_WIFI_ENABLED));
+ }
+
+ function initTetheringTestEnvironment() {
+ // Enable ril data.
+ return Promise.all([waitForRilDataConnected(true),
+ setSettings1(SETTINGS_RIL_DATA_ENABLED, true)])
+ .then(setSettings1(SETTINGS_TETHERING_WIFI_SECURITY, 'open'));
+ }
+
+ function restoreToInitialState() {
+ return setSettings1(SETTINGS_RIL_DATA_ENABLED, false)
+ .then(() => getSettings(SETTINGS_TETHERING_WIFI_ENABLED))
+ .then(enabled => is(enabled, false, 'Tethering should be turned off.'));
+ }
+
+ return suite.doTest(function() {
+ return verifyInitialState()
+ .then(initTetheringTestEnvironment)
+ // Since stock hostapd is not reliable after ICS, we just
+ // turn off potential stock hostapd during testing to avoid
+ // interference.
+ .then(stopStockHostapd)
+ .then(aTestCaseChain)
+ .then(startStockHostapd)
+ .then(restoreToInitialState, function onreject(aReason) {
+ return restoreToInitialState()
+ .then(() => { throw aReason; }); // Re-throw the orignal reject reason.
+ });
+ });
+ };
+
+ /**
+ * Run test with imported certificate.
+ *
+ * Certificate will be imported and confirmed before running test, and be
+ * deleted after running test.
+ *
+ * Fulfill params: (none)
+ *
+ * @param certBlob
+ * Certificate content as Blob.
+ * @param password
+ * Password for importing certificate, only used for importing PKCS#12.
+ * @param nickanem
+ * Nickname for imported certificate.
+ * @param usage
+ * Expected usage of imported certificate.
+ * @param aTestCaseChain
+ * The test case entry point, which can be a function or a promise.
+ *
+ * @return A deferred promise.
+ */
+ suite.doTestWithCertificate = function(certBlob, password, nickname, usage, aTestCaseChain) {
+ return suite.doTestWithoutStockAp(function() {
+ return ensureWifiEnabled(true)
+ // Import test certificate.
+ .then(() => importCert(certBlob, password, nickname))
+ .then(function(info) {
+ // Check import result.
+ is(info.nickname, nickname, "Imported nickname");
+ for (let i = 0; i < usage.length; i++) {
+ isnot(info.usage.indexOf(usage[i]), -1, "Usage " + usage[i]);
+ }
+ })
+ // Get imported certificate list.
+ .then(getImportedCerts)
+ // Check if certificate exists in imported certificate list.
+ .then(function(list) {
+ for (let i = 0; i < usage.length; i++) {
+ isnot(list[usage[i]].indexOf(nickname), -1,
+ "Certificate \"" + nickname + "\" of usage " + usage[i] + " is imported");
+ }
+ })
+ // Run test case.
+ .then(aTestCaseChain)
+ // Delete imported certificates.
+ .then(() => deleteCert(nickname))
+ // Check if certificate doesn't exist in imported certificate list.
+ .then(getImportedCerts)
+ .then(function(list) {
+ for (let i = 0; i < usage.length; i++) {
+ is(list[usage[i]].indexOf(nickname), -1, "Certificate is deleted");
+ }
+ })
+ });
+ };
+
+ return suite;
+})();
diff --git a/dom/wifi/test/marionette/manifest.ini b/dom/wifi/test/marionette/manifest.ini
new file mode 100644
index 000000000..5d719e4b6
--- /dev/null
+++ b/dom/wifi/test/marionette/manifest.ini
@@ -0,0 +1,22 @@
+[DEFAULT]
+run-if = buildapp == 'b2g'
+
+[test_wifi_enable.js]
+[test_wifi_scan.js]
+[test_wifi_associate.js]
+[test_wifi_associate_wo_connect.js]
+[test_wifi_auto_connect.js]
+[test_wifi_static_ip.js]
+[test_wifi_tethering_wifi_disabled.js]
+[test_wifi_tethering_wifi_inactive.js]
+[test_wifi_tethering_wifi_active.js]
+[test_wifi_manage_server_certificate.js]
+[test_wifi_manage_user_certificate.js]
+[test_wifi_manage_pkcs12_certificate.js]
+[test_wifi_associate_WPA_EAP_PEAP.js]
+skip-if = android_version < '16' # EAP test not working before KK.
+[test_wifi_associate_WPA_EAP_TTLS.js]
+skip-if = android_version < '16'
+[test_wifi_associate_WPA_EAP_TLS.js]
+skip-if = android_version < '16'
+[test_wifi_enable_api.js]
diff --git a/dom/wifi/test/marionette/test_wifi_associate.js b/dom/wifi/test/marionette/test_wifi_associate.js
new file mode 100644
index 000000000..0c00c583b
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_associate.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const SCAN_RETRY_CNT = 5;
+
+/**
+ * Convert the given MozWifiNetwork object array to testAssociate chain.
+ *
+ * @param aNetworks
+ * An array of MozWifiNetwork which we want to convert.
+ *
+ * @return A promise chain which "then"s testAssociate accordingly.
+ */
+function convertToTestAssociateChain(aNetworks) {
+ let chain = Promise.resolve();
+
+ aNetworks.forEach(function (aNetwork) {
+ chain = chain.then(() => gTestSuite.testAssociate(aNetwork));
+ });
+
+ return chain;
+}
+
+gTestSuite.doTestWithoutStockAp(function() {
+ return gTestSuite.ensureWifiEnabled(true)
+ .then(() => gTestSuite.startHostapds(HOSTAPD_CONFIG_LIST))
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd', HOSTAPD_CONFIG_LIST.length))
+ .then(() => gTestSuite.testWifiScanWithRetry(SCAN_RETRY_CNT, HOSTAPD_CONFIG_LIST))
+ .then(networks => convertToTestAssociateChain(networks))
+ .then(gTestSuite.killAllHostapd)
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd', 0));
+});
diff --git a/dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_PEAP.js b/dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_PEAP.js
new file mode 100644
index 000000000..ce53da2d7
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_PEAP.js
@@ -0,0 +1,623 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const SCAN_RETRY_CNT = 5;
+
+const EAP_USERNAME = 'username';
+const EAP_PASSWORD = 'password';
+
+const SERVER_EAP_USER_CONF = {
+ path: HOSTAPD_CONFIG_PATH + 'hostapd.eap_user',
+ content: '* PEAP,TTLS,TLS\n' +
+ '"' + EAP_USERNAME + '" MSCHAPV2,TTLS-MSCHAPV2 "' + EAP_PASSWORD + '" [2]\n'
+};
+const CA_CERT = {
+ path: HOSTAPD_CONFIG_PATH + 'ca.pem',
+ content: '-----BEGIN CERTIFICATE-----\n' +
+ 'MIIDsTCCApmgAwIBAgIJAKxTf+8X8qngMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV\n' +
+ 'BAYTAlRXMRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQKDAhjaHVja2xlZTER\n' +
+ 'MA8GA1UEAwwIY2h1Y2tsZWUxJDAiBgkqhkiG9w0BCQEWFWNodWNrbGkwNzA2QGdt\n' +
+ 'YWlsLmNvbTAgFw0xNDEyMjQxMTI4NTBaGA8yMjg4MTAwNzExMjg1MFowbjELMAkG\n' +
+ 'A1UEBhMCVFcxEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAoMCGNodWNrbGVl\n' +
+ 'MREwDwYDVQQDDAhjaHVja2xlZTEkMCIGCSqGSIb3DQEJARYVY2h1Y2tsaTA3MDZA\n' +
+ 'Z21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo3c2yFxY\n' +
+ 'o6gGg0I83jy00ME+MAfzCd+4ShL45ZLqysQP93jRBfPzU9ZuZ29ysVwgWIdqkZao\n' +
+ 'XTuV/NAW2GMGd8W1jQJ3J81fjb9wvhlny3rrACwvUn1N1S1BnM+BAAiDLGxEmvAQ\n' +
+ 'onp2aaa6HsHsYS8ONX+d2Qh4LEA4vupeSGAqJychCZv/l+aq/ErFZhFYB3CPUQEt\n' +
+ 'cClO24ucsIYP95lA0zhscnmAj06qplFD4Bv6IVrdDqujy1zNwCQwsJq/8OQdaTN/\n' +
+ 'h3y9pWvNKMBMM2niOUAjtuNpqsSK/lTS1WAT3PdtVECX9fYBi0Bg+HM92xs/6gt6\n' +
+ 'kh9jPV8keXHvSwIDAQABo1AwTjAdBgNVHQ4EFgQU7hBqhuG04xeCzrQ3ngx18ZJ3\n' +
+ 'lUswHwYDVR0jBBgwFoAU7hBqhuG04xeCzrQ3ngx18ZJ3lUswDAYDVR0TBAUwAwEB\n' +
+ '/zANBgkqhkiG9w0BAQsFAAOCAQEAFYX2iy680GAnBTttk0gyX6gk+8pYr3D22k/G\n' +
+ '6rvcjefzS7ELQPRKr6mfmwXq3mMf/4jiS2zI5zmXsestPYzHYxf2viQ6t7vr9XiJ\n' +
+ '3WfFjNw4ERlRisAvg0aqqTNNQq5v2VME4sdFZagy217f73C7azwCHl0bqOLH05rl\n' +
+ '8RubOxiHEj7ZybJqnRciK/bht4D+rZkwf4bBBmoloqH7xT0+rFQclpYXDGGjNUQB\n' +
+ 'LcHLF10xcr7g3ZVVu82fe6+d85gIGOIMR9+TKhdw6gO3CNcnDAj6gxksghgtcxmh\n' +
+ 'OzOggCn7nlIwImtsg2sZkpWB4lEi9hdv4lkNuyFjOL3bnuc+NA==\n' +
+ '-----END CERTIFICATE-----\n'
+};
+
+const SERVER_CERT = {
+ path: HOSTAPD_CONFIG_PATH + 'server.pem',
+ content: '-----BEGIN CERTIFICATE-----\n' +
+ 'MIID1DCCArygAwIBAgIBADANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJUVzET\n' +
+ 'MBEGA1UECAwKU29tZS1TdGF0ZTERMA8GA1UECgwIY2h1Y2tsZWUxETAPBgNVBAMM\n' +
+ 'CGNodWNrbGVlMSQwIgYJKoZIhvcNAQkBFhVjaHVja2xpMDcwNkBnbWFpbC5jb20w\n' +
+ 'IBcNMTQxMjI0MTEyOTQ5WhgPMjI4ODEwMDcxMTI5NDlaMG4xCzAJBgNVBAYTAlRX\n' +
+ 'MRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQKDAhjaHVja2xlZTERMA8GA1UE\n' +
+ 'AwwIY2h1Y2tsZWUxJDAiBgkqhkiG9w0BCQEWFWNodWNrbGkwNzA2QGdtYWlsLmNv\n' +
+ 'bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMdhQmKilTJbWZRxTiSV\n' +
+ 'rqIU+LYW1RKghx5o+0JpNRJVLuz5kBMaNskbbfUSNuHbEq0QA9BDKAZWIc4LSotk\n' +
+ 'lCo8TbcO9CJvJPQGGjGdHcohWX5vy6BE/OVE46CUteMFyZF6F8R2fNUww08iR/u1\n' +
+ 'YZebL5pWO1j43sPpAzEy6Tij2ACPt6EZcFaZG3SF2mVJWkCQnBqrojP65WUvZQqp\n' +
+ 'seUhW2YAS8Nu0Yrohgxz6VYk+cNDuDZVGs6qWRStZzJfYrfc76DtkHof5B14M+xp\n' +
+ 'XJaBLxN+whvnYkDTfinaCxnW1O7eXUltr87fLc5zmeBkgwaiaQuIdcfZm7vDUiz8\n' +
+ 'vnUCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBH\n' +
+ 'ZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFKK4f9/YavTHOfEiAB83Deac\n' +
+ '6gT5MB8GA1UdIwQYMBaAFO4QaobhtOMXgs60N54MdfGSd5VLMA0GCSqGSIb3DQEB\n' +
+ 'CwUAA4IBAQBWnO9o9KSJIqjoz5Nwll63ULOdcvgGdOeJIw1fcKQ817Rsp+TVcjcH\n' +
+ 'IrIADsT/QZGXRO/l6p1750e2iFtJEo1hsRaxtA1wWn2I9HO3+av2spQhr3jpYGPf\n' +
+ 'zpsMTp4RNYV7Q8+q1kZIz9PY4V1T0p6lveK8+fUj2hSLnxSj0QiGSJJtnEC3w4Rv\n' +
+ 'C9T6oUwIeToULmi+8FXQFdEqwKRU98DPq3eLzN28ZxUgoPE1C8+42D2UW8uyp/Gm\n' +
+ 'tGOa/k7nzkCdVqZI7lX7f0AjEvQgjtAMQ/k7Mhxx7TzW2HO+1YPMoKji6Z4WkNwt\n' +
+ 'JEj9ZUBSNt8B26UksJMBDkcvSegF3a7o\n' +
+ '-----END CERTIFICATE-----\n'
+};
+
+const SERVER_KEY = {
+ path: HOSTAPD_CONFIG_PATH + 'server.key',
+ content: '-----BEGIN RSA PRIVATE KEY-----\n' +
+ 'MIIEpAIBAAKCAQEAx2FCYqKVMltZlHFOJJWuohT4thbVEqCHHmj7Qmk1ElUu7PmQ\n' +
+ 'Exo2yRtt9RI24dsSrRAD0EMoBlYhzgtKi2SUKjxNtw70Im8k9AYaMZ0dyiFZfm/L\n' +
+ 'oET85UTjoJS14wXJkXoXxHZ81TDDTyJH+7Vhl5svmlY7WPjew+kDMTLpOKPYAI+3\n' +
+ 'oRlwVpkbdIXaZUlaQJCcGquiM/rlZS9lCqmx5SFbZgBLw27RiuiGDHPpViT5w0O4\n' +
+ 'NlUazqpZFK1nMl9it9zvoO2Qeh/kHXgz7GlcloEvE37CG+diQNN+KdoLGdbU7t5d\n' +
+ 'SW2vzt8tznOZ4GSDBqJpC4h1x9mbu8NSLPy+dQIDAQABAoIBAASG4Mr8hgaurEoC\n' +
+ 'iJOsElr7vunjetMBcg/uskW/vcS8ymP3Bp5oafYG+WgnEbfvEW18f5mq7K24JuxW\n' +
+ 'tUqU7ghHdjxByqk9fMlNmiqmNpbwSufkAeuRpWxPNBvhRH/zEbCL5R5A0nTEtqqF\n' +
+ 'TL0aUSzwCRSoAJD0lZo9ICVt0n3GsDyM9rqQg/uZmh1qsRdwPsRuYORND9g48rKq\n' +
+ '6WN9leskSxhhsYE2D9ocOFd9bNt8Zxejh9ppVSnG/KsIdt18iBzcabatgAQ046fb\n' +
+ 'Z3vprcZJLg93Sg2gSuVqlSTs3M2W8VQnm22/EBMb1y0M48MSRCgnbPLG/CcCLLfF\n' +
+ 'LwxCOgECgYEA/eYt67xyJ6JeAdxdwOZuT1WWGbFpLiG9+2OgiHumyRQ5969XMTWo\n' +
+ 'fIhMKchDdjoy9RR236\/\/EFCs7UEyB7+a7ODRzNiK2zCD8Smjp+21fUPSthEeQesk\n' +
+ 'eiMYICIu5Ay35x9sxIX+XOUVvRhPOGcD29GVeRnKh1inTHOz2dje8LkCgYEAyQeY\n' +
+ 'STi9jjCEcHkM1E/UeDiLfHHepLXi8wS41JNRHl5Jacp7XB5djAjKu/jf367/VpFy\n' +
+ 'GDDMetE7n8eWkrnAvovxOwZ000YDMtL1sUYSjL+XtBS5s6VY1p4qaSAY9nUUGrJh\n' +
+ 'JvtvsuI7SKTtL+60vjBOH7zDnvOdBgAp0utLhZ0CgYEAuLzzqrPKB8afShFSchn4\n' +
+ 'J2dpuLYahsNsXW7HDqeR2nsKFosRETAusLXnXPtnAq4kB6jlOarwFqnsuRCX24Vx\n' +
+ 'r2uBm9/vYL7zMdUPTA+s30ErHuhjsKjsOKYyVqcooSwT32pBFNk+E89nutfmRG7I\n' +
+ 'IvhjHuNCNqqtx/Xj5d1jkZkCgYBQicppC2Jl5OoqZVTOem0U/RJk+PnJ41TZJ7sk\n' +
+ '7yBAmmWvDH\/\/l+rCf4M5a6vFYcbKV9rt9h711X2dtciNX/3oWQh8LUoAmrwNUJc+\n' +
+ 'PmSQHvIYI3WCk2vUD+nN1B4sHxu+1lg11eYaNKiroeeknG2tBI1ICcgVlmQCU25u\n' +
+ 'IfZPwQKBgQCdO6QHhPLtcHUDNFA6FQ1jKL1iEd7G0JLVRz4Xkpkn1Vrr5MD6JFDa\n' +
+ '5ccabADyl0lpFqDIVJQIzLku2hOD2i9aBNCY0pL391HeOS7CkZX+TdOY1tquoBq5\n' +
+ 'MnmixZjDCVd2VcrVyTA6ntOBoharKFW0rH1PqU+qu7dZF7CBPbAdEw==\n' +
+ '-----END RSA PRIVATE KEY-----\n'
+};
+
+const WPA_EAP_AP_LIST = [
+ {
+ ssid: 'WPA-EAP-PEAP',
+ ieee8021x: 1,
+ eapol_version: 1,
+ eap_server: 1,
+ eapol_key_index_workaround: 0,
+ eap_user_file: SERVER_EAP_USER_CONF.path,
+ ca_cert: CA_CERT.path,
+ server_cert: SERVER_CERT.path,
+ private_key: SERVER_KEY.path,
+ wpa: 3,
+ wpa_key_mgmt: 'WPA-EAP'
+ }
+];
+
+const CLIENT_PKCS12_CERT = {
+ nickname: 'client',
+ password: 'password',
+ usage: ['UserCert', 'ServerCert'],
+ content: [0x30, 0x82, 0x0E, 0x01, 0x02, 0x01, 0x03, 0x30,
+ 0x82, 0x0D, 0xC7, 0x06, 0x09, 0x2A, 0x86, 0x48,
+ 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01, 0xA0, 0x82,
+ 0x0D, 0xB8, 0x04, 0x82, 0x0D, 0xB4, 0x30, 0x82,
+ 0x0D, 0xB0, 0x30, 0x82, 0x08, 0x67, 0x06, 0x09,
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07,
+ 0x06, 0xA0, 0x82, 0x08, 0x58, 0x30, 0x82, 0x08,
+ 0x54, 0x02, 0x01, 0x00, 0x30, 0x82, 0x08, 0x4D,
+ 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D,
+ 0x01, 0x07, 0x01, 0x30, 0x1C, 0x06, 0x0A, 0x2A,
+ 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01,
+ 0x06, 0x30, 0x0E, 0x04, 0x08, 0x67, 0x7A, 0xF3,
+ 0x61, 0xBE, 0xE0, 0x51, 0xC1, 0x02, 0x02, 0x08,
+ 0x00, 0x80, 0x82, 0x08, 0x20, 0xFC, 0x6A, 0x79,
+ 0xA1, 0x6C, 0xAF, 0xBE, 0xEE, 0x62, 0x45, 0x33,
+ 0xB8, 0x48, 0xE1, 0x68, 0xA1, 0x15, 0x11, 0x4B,
+ 0x95, 0xCB, 0x77, 0xC0, 0x5D, 0xA2, 0xCB, 0xDB,
+ 0xD1, 0x83, 0x74, 0x60, 0xD7, 0xEC, 0x42, 0xA6,
+ 0x3A, 0x23, 0xF7, 0x85, 0xEB, 0xC1, 0xFE, 0x6A,
+ 0x57, 0x8E, 0xC1, 0x44, 0xF3, 0x1F, 0xFE, 0xB8,
+ 0x2D, 0x8C, 0x4D, 0xC9, 0x5B, 0xAE, 0x21, 0x2E,
+ 0x4C, 0x1A, 0xEB, 0x84, 0x09, 0xF3, 0x40, 0x92,
+ 0x39, 0x7F, 0x56, 0x02, 0x46, 0x61, 0x16, 0xDE,
+ 0x5C, 0x48, 0xB6, 0x0C, 0x1D, 0xD3, 0x5F, 0x10,
+ 0x9A, 0x39, 0xB8, 0x66, 0x31, 0xFC, 0x39, 0x71,
+ 0x87, 0x23, 0x46, 0x9D, 0xE8, 0x3C, 0x2B, 0xA1,
+ 0x39, 0x8A, 0xD3, 0xFF, 0xD9, 0x43, 0xB6, 0x61,
+ 0xC6, 0x67, 0x70, 0x40, 0xBD, 0xFE, 0xD3, 0xC1,
+ 0x68, 0xF5, 0xF7, 0xC8, 0x89, 0xD8, 0x17, 0xC5,
+ 0xE8, 0x3D, 0x29, 0xD5, 0x91, 0xDF, 0x1F, 0x56,
+ 0x74, 0x5A, 0xC4, 0xA8, 0x14, 0xBA, 0xD4, 0xFA,
+ 0x13, 0x49, 0x2A, 0x9F, 0x63, 0xF1, 0xB2, 0x45,
+ 0xF1, 0xF0, 0x2A, 0xDD, 0x75, 0x66, 0x8A, 0xF7,
+ 0xAB, 0x73, 0x86, 0x26, 0x9D, 0x1F, 0x07, 0xAD,
+ 0xD3, 0xFE, 0xE0, 0xA3, 0xED, 0xA0, 0x96, 0x3E,
+ 0x1E, 0x89, 0x86, 0x02, 0x4C, 0x28, 0xFD, 0x57,
+ 0xA1, 0x67, 0x55, 0xF0, 0x82, 0x3B, 0x7F, 0xCC,
+ 0x2A, 0x32, 0x01, 0x93, 0x1D, 0x8B, 0x66, 0x8A,
+ 0x20, 0x52, 0x84, 0xDD, 0x2C, 0xFD, 0xEE, 0x72,
+ 0xF3, 0x8C, 0x58, 0xB9, 0x99, 0xE5, 0xC1, 0x22,
+ 0x63, 0x59, 0x00, 0xE2, 0x76, 0xC5, 0x3A, 0x17,
+ 0x7F, 0x93, 0xE9, 0x67, 0x61, 0xAA, 0x10, 0xC3,
+ 0xD9, 0xC8, 0x24, 0x46, 0x5B, 0xBE, 0x8C, 0x1F,
+ 0x2D, 0x66, 0x48, 0xD2, 0x02, 0x11, 0xFB, 0x74,
+ 0x14, 0x76, 0x76, 0x5A, 0x98, 0x54, 0x35, 0xA7,
+ 0x85, 0x66, 0x20, 0x26, 0x8B, 0x13, 0x6F, 0x68,
+ 0xE3, 0xC9, 0x58, 0x7D, 0x1C, 0x3E, 0x01, 0x8D,
+ 0xF8, 0xD6, 0x7F, 0xCF, 0xA2, 0x07, 0xB7, 0x95,
+ 0xFD, 0xF0, 0x02, 0x34, 0x32, 0x30, 0xE8, 0xD4,
+ 0x57, 0x5E, 0x53, 0xFB, 0x54, 0xE2, 0x03, 0x32,
+ 0xCC, 0x52, 0x2E, 0xD2, 0x35, 0xD9, 0x58, 0x85,
+ 0x2D, 0xEC, 0x2D, 0x71, 0xD1, 0x8A, 0x29, 0xD0,
+ 0xB0, 0x24, 0xBD, 0x24, 0xDC, 0x1A, 0x28, 0x3F,
+ 0xA0, 0x12, 0x81, 0x15, 0x24, 0xC9, 0xB5, 0x4A,
+ 0x23, 0xB6, 0xA3, 0x45, 0x50, 0x2D, 0x73, 0x99,
+ 0x6B, 0x1C, 0xFB, 0xA4, 0x53, 0xD7, 0x5C, 0xF4,
+ 0x6C, 0xB0, 0xE5, 0x74, 0xB3, 0x76, 0xF8, 0xB1,
+ 0x0D, 0x59, 0x70, 0x9F, 0xCA, 0xDE, 0xF2, 0xAA,
+ 0x4C, 0x7D, 0x11, 0x54, 0xC4, 0x19, 0x0F, 0x36,
+ 0x4A, 0x62, 0xFF, 0x8B, 0x10, 0xCB, 0x93, 0x50,
+ 0xDA, 0x79, 0x5E, 0x4E, 0x09, 0x1F, 0x22, 0xC8,
+ 0x19, 0x85, 0xE9, 0xEE, 0xB7, 0x71, 0x65, 0xB9,
+ 0x10, 0xD2, 0x0A, 0x73, 0x5B, 0xA6, 0xDA, 0x37,
+ 0x46, 0x02, 0x00, 0x98, 0x9E, 0x20, 0x6C, 0x7D,
+ 0xC7, 0x69, 0xBB, 0xC2, 0x00, 0x40, 0x9C, 0x57,
+ 0x00, 0xC2, 0x36, 0x76, 0xE8, 0x2A, 0x8D, 0xAD,
+ 0x62, 0x57, 0xC8, 0xD0, 0x9D, 0x66, 0x27, 0x5A,
+ 0xD8, 0x0D, 0x35, 0x60, 0x28, 0x38, 0x62, 0x94,
+ 0x78, 0x36, 0x25, 0x58, 0xFD, 0xF8, 0x66, 0x1F,
+ 0x68, 0x04, 0x0F, 0xD8, 0x00, 0xDF, 0xA0, 0x6C,
+ 0x25, 0x42, 0x9A, 0x4C, 0xEB, 0x80, 0x13, 0x51,
+ 0x7D, 0x2D, 0xA8, 0x89, 0xD6, 0x1B, 0x67, 0x72,
+ 0x01, 0xF3, 0x2D, 0x16, 0x77, 0xFE, 0x22, 0xBC,
+ 0x8A, 0x45, 0x09, 0x1F, 0x9C, 0x2F, 0x2A, 0xA9,
+ 0x61, 0x5B, 0x4A, 0xE6, 0x64, 0x2C, 0x62, 0x1A,
+ 0x3A, 0x96, 0xE6, 0x0A, 0xAE, 0x05, 0x1A, 0xC8,
+ 0xCB, 0xD6, 0x8F, 0x3A, 0x4B, 0xE0, 0x7F, 0x82,
+ 0xB4, 0x98, 0xF1, 0x9D, 0xD7, 0x14, 0x76, 0x5E,
+ 0x77, 0x85, 0x87, 0xEC, 0x13, 0xDA, 0xFD, 0xAF,
+ 0xCB, 0xA3, 0x1C, 0x99, 0xC1, 0xFE, 0x17, 0x0C,
+ 0x40, 0x4D, 0x3C, 0x8F, 0x70, 0x86, 0x63, 0x64,
+ 0xB7, 0x75, 0xA8, 0x71, 0x36, 0xDC, 0x54, 0x10,
+ 0x57, 0x0C, 0xA8, 0xF2, 0xA1, 0xBB, 0xED, 0x03,
+ 0x41, 0x57, 0x34, 0x2C, 0x8F, 0x7C, 0xA0, 0x09,
+ 0xF3, 0x9E, 0x41, 0xB7, 0xA8, 0xD4, 0x66, 0x0D,
+ 0x0D, 0xC0, 0x6A, 0xFC, 0x6A, 0xA2, 0xAC, 0xE2,
+ 0x60, 0x00, 0xE3, 0xF7, 0x75, 0x43, 0x23, 0xEB,
+ 0xC8, 0x61, 0xFA, 0xB3, 0xB8, 0x28, 0xCE, 0xCA,
+ 0xF4, 0x47, 0x7F, 0x30, 0x6D, 0x61, 0x89, 0x47,
+ 0xA1, 0x4A, 0xFE, 0xD1, 0x21, 0x0B, 0x6D, 0xF4,
+ 0x3F, 0x00, 0x86, 0x30, 0x8E, 0x33, 0x21, 0x6F,
+ 0xDA, 0x15, 0xFD, 0x5F, 0xEC, 0x8E, 0xF1, 0x12,
+ 0x3F, 0xC9, 0x83, 0x0C, 0xCA, 0x22, 0x01, 0xF1,
+ 0x70, 0x5F, 0x1F, 0x66, 0xB5, 0xF8, 0x3E, 0x7A,
+ 0x6F, 0xDE, 0xDB, 0xA7, 0x8D, 0x18, 0x9E, 0xBE,
+ 0xDB, 0xAD, 0x3D, 0x66, 0x30, 0xC1, 0x6C, 0x0C,
+ 0x87, 0xB4, 0x65, 0x75, 0xE0, 0x9D, 0xEA, 0x16,
+ 0x0D, 0x07, 0x37, 0x33, 0xC5, 0xEC, 0x97, 0x93,
+ 0x37, 0xEB, 0x8E, 0x65, 0x9C, 0x40, 0x63, 0x6C,
+ 0x43, 0x60, 0xB0, 0x40, 0x4D, 0x85, 0xEF, 0xC2,
+ 0x47, 0x5F, 0xE7, 0x6B, 0xCB, 0x40, 0xE8, 0xEA,
+ 0xD8, 0xAB, 0xB1, 0x9A, 0x72, 0xDC, 0x4C, 0x14,
+ 0xFA, 0x43, 0x61, 0x5F, 0xA6, 0x5C, 0x3A, 0x05,
+ 0x17, 0x2E, 0x74, 0xF3, 0x5E, 0x45, 0xD9, 0x47,
+ 0xAA, 0x59, 0xB6, 0x8F, 0x42, 0x66, 0x42, 0x29,
+ 0x90, 0x95, 0x48, 0x46, 0x91, 0x88, 0x3C, 0x8C,
+ 0xDE, 0xCC, 0xED, 0xB3, 0xAA, 0x62, 0xEA, 0xBC,
+ 0xB4, 0x0C, 0x48, 0x4C, 0x53, 0x23, 0x5E, 0x24,
+ 0x85, 0xBF, 0x92, 0xDA, 0x14, 0xDB, 0x1A, 0x3D,
+ 0xEF, 0x30, 0xD9, 0x49, 0x64, 0x4D, 0xE5, 0x01,
+ 0xFC, 0xA4, 0x4B, 0xD1, 0x9F, 0xDE, 0x96, 0x7F,
+ 0x50, 0xBC, 0x4D, 0x38, 0x44, 0xE9, 0x23, 0x5F,
+ 0x37, 0x57, 0x1A, 0xA6, 0x52, 0x5A, 0x0F, 0x4F,
+ 0x87, 0x33, 0x4A, 0x7B, 0x66, 0xEE, 0x3D, 0x66,
+ 0x0A, 0x63, 0x39, 0x1F, 0x23, 0x38, 0x35, 0x73,
+ 0x60, 0x5E, 0x47, 0x20, 0x4F, 0xC0, 0xC8, 0x3C,
+ 0x09, 0xF9, 0x29, 0x4F, 0x5E, 0x55, 0x69, 0xC4,
+ 0x6B, 0xE8, 0xF8, 0x91, 0xC0, 0x22, 0x65, 0x15,
+ 0x1E, 0xFB, 0xB9, 0x61, 0xCE, 0x45, 0xBE, 0x2B,
+ 0xEE, 0xB9, 0x04, 0x2B, 0xFD, 0xAE, 0x61, 0x1C,
+ 0x3D, 0x3D, 0x7C, 0xBF, 0xC1, 0xF7, 0x3C, 0x4E,
+ 0x9E, 0x0E, 0x54, 0xC8, 0xAD, 0xA9, 0xDF, 0x43,
+ 0x49, 0xB9, 0x41, 0x05, 0xE5, 0xF1, 0x49, 0xAA,
+ 0x77, 0x6C, 0x34, 0x5B, 0x93, 0x24, 0x24, 0x23,
+ 0x74, 0x68, 0x11, 0xCE, 0x15, 0x80, 0xA1, 0xA4,
+ 0x1F, 0x8D, 0x81, 0xCD, 0xB2, 0x98, 0xCA, 0x14,
+ 0x0B, 0x0C, 0x61, 0x50, 0x69, 0x72, 0xAE, 0xFA,
+ 0x8B, 0xC0, 0x3F, 0x0D, 0xE7, 0xF2, 0x0F, 0xEB,
+ 0xC1, 0x11, 0xB9, 0x10, 0x03, 0x6A, 0xF5, 0x97,
+ 0x3C, 0x53, 0x2F, 0x67, 0x86, 0x09, 0x6A, 0xE3,
+ 0x28, 0xC0, 0x78, 0xC8, 0xB4, 0x39, 0x8E, 0xD1,
+ 0xCE, 0x25, 0xE8, 0x66, 0xF7, 0x09, 0x40, 0x7D,
+ 0x81, 0xFB, 0xAF, 0xFA, 0x59, 0xC4, 0x9B, 0x2B,
+ 0x83, 0x45, 0x5B, 0xA8, 0x66, 0x9E, 0x38, 0xC8,
+ 0xFD, 0xAC, 0xF2, 0x2D, 0x21, 0xDE, 0x50, 0x4C,
+ 0x03, 0xCB, 0x88, 0x42, 0xDD, 0x84, 0x09, 0x99,
+ 0x8E, 0x8B, 0x40, 0x97, 0x1B, 0x14, 0x85, 0x37,
+ 0x11, 0x01, 0xE0, 0x74, 0x6B, 0x33, 0x52, 0x8C,
+ 0x68, 0x3A, 0x89, 0xB2, 0xAF, 0x35, 0xE6, 0x65,
+ 0xC3, 0x58, 0x70, 0xD2, 0xE7, 0x1F, 0x1F, 0xF6,
+ 0xE5, 0x0A, 0xB1, 0xFE, 0xD0, 0xC9, 0x51, 0x50,
+ 0xE7, 0xFD, 0x58, 0xF5, 0xC4, 0x58, 0x65, 0x94,
+ 0xD1, 0x57, 0x55, 0x5E, 0xD2, 0x27, 0x98, 0xAF,
+ 0xE7, 0x55, 0x0B, 0x87, 0x50, 0x9B, 0xEF, 0xE8,
+ 0x2B, 0xFC, 0xE7, 0x3B, 0x4E, 0xD7, 0xB7, 0x4D,
+ 0xF4, 0xBC, 0xF4, 0x88, 0x63, 0xE4, 0x8A, 0x20,
+ 0x4B, 0x22, 0xB0, 0xA0, 0x53, 0x7F, 0xA8, 0xC9,
+ 0x0C, 0xF8, 0xD7, 0xBD, 0x46, 0x39, 0xA7, 0x7D,
+ 0xDD, 0x10, 0x91, 0x50, 0x54, 0x06, 0x47, 0xF0,
+ 0x3C, 0xAA, 0x43, 0x40, 0xF8, 0x54, 0xDD, 0x8A,
+ 0xEA, 0x8A, 0x0B, 0xA5, 0x7F, 0xCD, 0x5E, 0xAA,
+ 0x02, 0x2E, 0x1F, 0xC6, 0x50, 0x15, 0xF8, 0x0A,
+ 0x0C, 0x1B, 0x3C, 0x55, 0x3A, 0xC3, 0x6F, 0x88,
+ 0xD7, 0xBF, 0xB1, 0x02, 0xCC, 0xE0, 0x08, 0x29,
+ 0x97, 0xD2, 0xAA, 0x23, 0xC4, 0x6D, 0xE3, 0xE3,
+ 0x76, 0x39, 0x92, 0xC3, 0x2E, 0x7A, 0xE2, 0x98,
+ 0xD1, 0xFC, 0xAE, 0xCC, 0x95, 0xD8, 0xB4, 0xDC,
+ 0x92, 0xEA, 0x6A, 0x5F, 0xF2, 0x92, 0x17, 0x0B,
+ 0x8D, 0xC3, 0xFA, 0x9C, 0x62, 0xCE, 0x44, 0x8D,
+ 0xC3, 0x1E, 0xC3, 0xB2, 0xD5, 0x00, 0xCD, 0xB4,
+ 0x9E, 0x2D, 0x7B, 0xF2, 0x98, 0xA3, 0x00, 0x8B,
+ 0x81, 0x30, 0x77, 0x5B, 0x02, 0x99, 0xB1, 0xCD,
+ 0xC3, 0x1D, 0x74, 0x74, 0xEF, 0x41, 0xCB, 0x69,
+ 0x63, 0x8E, 0xA6, 0xD3, 0x2D, 0x3E, 0x1F, 0x1D,
+ 0x12, 0x9E, 0xD9, 0x18, 0x67, 0x06, 0xAF, 0x37,
+ 0x29, 0xAD, 0x65, 0xD8, 0xEB, 0x71, 0xC4, 0x7D,
+ 0x94, 0x3D, 0xEA, 0xCC, 0xDF, 0x72, 0x41, 0x51,
+ 0x3C, 0xA1, 0x66, 0x98, 0x32, 0x32, 0x40, 0x54,
+ 0xB0, 0x2F, 0xEB, 0xCE, 0xDF, 0x4A, 0x64, 0xFB,
+ 0x9A, 0x90, 0xDC, 0xF6, 0x6F, 0xA9, 0xD4, 0xCA,
+ 0xCB, 0x91, 0xC4, 0xFE, 0xEE, 0x9C, 0x01, 0x50,
+ 0x2E, 0xAC, 0xCC, 0x5F, 0x89, 0xD0, 0x91, 0xA3,
+ 0xD9, 0xF9, 0x4B, 0x8D, 0xDE, 0x6C, 0x60, 0x21,
+ 0x19, 0xB1, 0xD3, 0x4D, 0x75, 0x56, 0x6F, 0xB8,
+ 0x25, 0xA4, 0x92, 0x4F, 0x12, 0xF5, 0x8F, 0xC1,
+ 0x17, 0x4B, 0xB3, 0x34, 0x21, 0x22, 0xAC, 0x52,
+ 0xD2, 0x64, 0xC9, 0x9A, 0x7D, 0xFC, 0xC0, 0x0A,
+ 0x89, 0x34, 0xFF, 0x08, 0xD3, 0x04, 0xDC, 0xFE,
+ 0x7C, 0xB3, 0xB8, 0xFD, 0x85, 0xDD, 0x79, 0x51,
+ 0xA7, 0x89, 0xE8, 0xF1, 0x23, 0xB1, 0xDF, 0xD7,
+ 0x1F, 0x7B, 0xB1, 0x5D, 0x42, 0xF9, 0x61, 0xF8,
+ 0xDC, 0x81, 0x04, 0xF1, 0xCC, 0xFA, 0xD7, 0xED,
+ 0xBF, 0x47, 0xAC, 0xBD, 0xE5, 0xFA, 0xAC, 0xB3,
+ 0x1C, 0xD9, 0xA1, 0xB3, 0x60, 0xEE, 0x9C, 0x8A,
+ 0x36, 0x57, 0xB4, 0x2F, 0xA1, 0xA2, 0xF3, 0xE2,
+ 0x09, 0x9A, 0x6E, 0x43, 0x9B, 0xE5, 0x93, 0xB8,
+ 0x3D, 0x9E, 0x9F, 0xC1, 0xC6, 0x0D, 0x02, 0xEB,
+ 0x4D, 0x38, 0xE9, 0xB4, 0x9F, 0xEA, 0x33, 0x8C,
+ 0x07, 0xD8, 0xB4, 0x71, 0xAD, 0xE5, 0x43, 0xB2,
+ 0xCC, 0x55, 0x93, 0x6A, 0xDB, 0x1E, 0x80, 0xDB,
+ 0xC2, 0xEA, 0x42, 0x8E, 0xFC, 0x86, 0x44, 0xC9,
+ 0x8A, 0xC4, 0xF2, 0x46, 0xA7, 0x39, 0x50, 0x0D,
+ 0x1A, 0xAA, 0x07, 0x04, 0xBE, 0xD4, 0xCE, 0x62,
+ 0x4D, 0x0F, 0x91, 0x7D, 0x29, 0x88, 0x9C, 0x4C,
+ 0xAF, 0xF7, 0xD8, 0x40, 0x93, 0x88, 0xC7, 0x20,
+ 0xD2, 0x17, 0x2A, 0xC4, 0x92, 0x72, 0xD0, 0xC0,
+ 0x4E, 0x56, 0x47, 0xB1, 0x27, 0x02, 0xE6, 0x61,
+ 0x82, 0x5E, 0xC8, 0x2E, 0x90, 0xD2, 0x31, 0x22,
+ 0xE2, 0xA9, 0x4A, 0x91, 0x45, 0x69, 0xB1, 0xA5,
+ 0x0F, 0x66, 0x2C, 0x30, 0xAD, 0x7F, 0x1B, 0x0E,
+ 0x22, 0x17, 0x60, 0x2E, 0x3D, 0x7F, 0x7F, 0x8C,
+ 0x33, 0x51, 0xA0, 0x25, 0xDE, 0xFD, 0x75, 0xBC,
+ 0xEF, 0xE6, 0xE7, 0x20, 0x04, 0x5A, 0xEC, 0x50,
+ 0x21, 0x48, 0x56, 0x98, 0xE2, 0x33, 0x6D, 0x22,
+ 0x5C, 0xC3, 0xFB, 0xFC, 0x6F, 0xB3, 0xA7, 0x8E,
+ 0x6F, 0x67, 0x70, 0x9D, 0xDA, 0x02, 0x01, 0x59,
+ 0x7B, 0x3D, 0x2B, 0x38, 0xCC, 0x0F, 0x44, 0x3D,
+ 0xFB, 0x9A, 0xB3, 0x23, 0x15, 0x50, 0x6E, 0xBF,
+ 0x8B, 0xA1, 0x94, 0x33, 0xE5, 0x7B, 0x88, 0x4E,
+ 0xCB, 0x6D, 0x9F, 0xBF, 0xBC, 0x7A, 0xA8, 0x1E,
+ 0x68, 0x25, 0xED, 0x8E, 0x53, 0x21, 0x72, 0xC5,
+ 0x70, 0xB3, 0xE4, 0xA6, 0xA1, 0x5A, 0x2D, 0xC8,
+ 0x43, 0x9D, 0x60, 0x77, 0x78, 0xE0, 0xC4, 0xAF,
+ 0xC8, 0x29, 0xBA, 0xD0, 0x4D, 0x39, 0x83, 0x51,
+ 0xA7, 0x10, 0x7F, 0x0C, 0x34, 0x0E, 0x6C, 0x75,
+ 0x26, 0xD7, 0xD6, 0xC7, 0x32, 0x53, 0xAF, 0x4E,
+ 0xBE, 0xF2, 0xC2, 0x0F, 0x99, 0x23, 0xB9, 0xE1,
+ 0xC8, 0xB4, 0xBC, 0x5A, 0xC6, 0xCB, 0xEB, 0x4D,
+ 0x28, 0x56, 0x72, 0xFE, 0x1B, 0x2C, 0x5D, 0xE3,
+ 0xBC, 0xC7, 0xA3, 0xC0, 0x7D, 0x27, 0xF0, 0xD0,
+ 0x4F, 0x3F, 0x1F, 0xF7, 0x87, 0x15, 0xF2, 0xEA,
+ 0xD4, 0x03, 0x6D, 0x2F, 0xD4, 0x8E, 0x50, 0x4B,
+ 0x05, 0xBF, 0xF7, 0x8C, 0x67, 0x5A, 0xDC, 0x4D,
+ 0xCD, 0xCF, 0x9D, 0x02, 0xB6, 0xE7, 0xAE, 0x49,
+ 0xD1, 0x7C, 0x00, 0xE7, 0x3B, 0xEA, 0xFB, 0x0D,
+ 0x2A, 0x7B, 0x41, 0x33, 0x66, 0xD0, 0x29, 0x9F,
+ 0xB3, 0x8A, 0x71, 0xB0, 0xE2, 0x76, 0xA9, 0xDB,
+ 0xFD, 0x64, 0x04, 0x69, 0xDF, 0x89, 0x1F, 0x56,
+ 0x86, 0x92, 0xD9, 0xD9, 0xB9, 0xF3, 0x4F, 0xAC,
+ 0xAE, 0x61, 0x48, 0x20, 0xCE, 0x3C, 0x2B, 0x44,
+ 0xAB, 0x42, 0xFA, 0xAB, 0x2E, 0x94, 0x82, 0xC8,
+ 0xD9, 0x97, 0xCF, 0x27, 0xDF, 0xAC, 0xAC, 0xE7,
+ 0xCA, 0xB2, 0x84, 0xAB, 0xF2, 0x5D, 0xDF, 0x56,
+ 0x0C, 0x8C, 0x07, 0x3C, 0x3D, 0xA8, 0xDD, 0xBE,
+ 0xFF, 0x4E, 0x28, 0x0D, 0xB2, 0x2D, 0xE6, 0x9D,
+ 0x44, 0x21, 0xCB, 0xE7, 0x33, 0x63, 0x22, 0x8F,
+ 0x4C, 0xFF, 0xB6, 0x1D, 0x9A, 0x71, 0x3F, 0xB1,
+ 0x29, 0xAE, 0x3A, 0x35, 0xEE, 0x9C, 0x97, 0x68,
+ 0xA7, 0x52, 0x66, 0x01, 0xD8, 0x9A, 0x5D, 0xF4,
+ 0xB3, 0x2F, 0x5C, 0xD4, 0x0E, 0xF9, 0xCF, 0x07,
+ 0xF6, 0x8C, 0xBA, 0xA6, 0x8D, 0x6B, 0xC6, 0x01,
+ 0xC2, 0x69, 0xAE, 0x60, 0x08, 0x1A, 0x0E, 0x3F,
+ 0xAE, 0x60, 0x29, 0xF3, 0x48, 0x0D, 0xE0, 0xD0,
+ 0xAE, 0x52, 0x44, 0xE9, 0x7F, 0x1F, 0x92, 0x5F,
+ 0x71, 0xAD, 0xEC, 0x6B, 0x47, 0x66, 0x92, 0x22,
+ 0x27, 0xAE, 0x6E, 0x25, 0xCD, 0xF3, 0x5F, 0x55,
+ 0x59, 0xBD, 0x73, 0xCE, 0x2B, 0x7E, 0x99, 0x44,
+ 0x56, 0x70, 0xA3, 0xE7, 0x7A, 0x59, 0x75, 0xD8,
+ 0x48, 0x0C, 0x39, 0x2B, 0xD7, 0x53, 0xC6, 0xAD,
+ 0x4A, 0x6F, 0xB4, 0x14, 0x96, 0xDF, 0xF2, 0x4A,
+ 0x0C, 0xA2, 0xD5, 0x29, 0x98, 0x7C, 0x42, 0x87,
+ 0xD9, 0x1F, 0x97, 0x61, 0xD9, 0xBF, 0x99, 0x4F,
+ 0x2C, 0x4C, 0x75, 0xAC, 0xB8, 0x06, 0x75, 0xD6,
+ 0x87, 0x76, 0x7E, 0xE3, 0x23, 0x4B, 0xEA, 0x1A,
+ 0x1A, 0xF4, 0xB7, 0x09, 0xAF, 0x53, 0xEB, 0xA6,
+ 0x39, 0x10, 0xFE, 0xD4, 0xEB, 0x1B, 0xAE, 0x38,
+ 0x31, 0x33, 0xBA, 0x68, 0xEE, 0xC7, 0x65, 0x76,
+ 0xFB, 0x49, 0x77, 0xD4, 0x19, 0xC4, 0xE6, 0xA7,
+ 0x05, 0xFE, 0x2A, 0xDA, 0x39, 0x99, 0x1A, 0x92,
+ 0xD2, 0xF0, 0x61, 0x97, 0xF6, 0x06, 0x6C, 0x88,
+ 0x7B, 0x6F, 0x60, 0xE6, 0x70, 0x08, 0xF0, 0xB4,
+ 0x6B, 0x39, 0x6F, 0x05, 0x41, 0x81, 0xF9, 0xBE,
+ 0x7A, 0x51, 0xC4, 0x75, 0xB0, 0x6A, 0x89, 0xA0,
+ 0xA6, 0x9A, 0x5B, 0xEE, 0x7D, 0x78, 0x17, 0x5F,
+ 0x9F, 0x3B, 0x7D, 0xDD, 0x8A, 0x9E, 0xAA, 0x1A,
+ 0xDA, 0x49, 0x08, 0xE9, 0xFD, 0x91, 0xA6, 0xFA,
+ 0xCE, 0xCF, 0x67, 0xDF, 0x0F, 0xC9, 0xD6, 0x38,
+ 0xD9, 0xD5, 0xD1, 0xC0, 0x76, 0x59, 0x42, 0x53,
+ 0xBF, 0x48, 0xE9, 0x11, 0x74, 0xC7, 0x11, 0xD8,
+ 0xE7, 0x8E, 0xD3, 0xC8, 0x25, 0xA1, 0x26, 0x50,
+ 0xBB, 0xB4, 0x35, 0xAF, 0xAF, 0x06, 0x23, 0x69,
+ 0x3E, 0x30, 0xFD, 0x7B, 0x34, 0x83, 0x07, 0xD0,
+ 0xF0, 0x0F, 0x6C, 0x9A, 0x13, 0x5D, 0xC2, 0x7B,
+ 0xDF, 0x6F, 0xDD, 0x8E, 0xF4, 0x30, 0x82, 0x05,
+ 0x41, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7,
+ 0x0D, 0x01, 0x07, 0x01, 0xA0, 0x82, 0x05, 0x32,
+ 0x04, 0x82, 0x05, 0x2E, 0x30, 0x82, 0x05, 0x2A,
+ 0x30, 0x82, 0x05, 0x26, 0x06, 0x0B, 0x2A, 0x86,
+ 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01,
+ 0x02, 0xA0, 0x82, 0x04, 0xEE, 0x30, 0x82, 0x04,
+ 0xEA, 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48,
+ 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30,
+ 0x0E, 0x04, 0x08, 0x74, 0xC0, 0x84, 0x8F, 0xC7,
+ 0x74, 0x5E, 0x21, 0x02, 0x02, 0x08, 0x00, 0x04,
+ 0x82, 0x04, 0xC8, 0x1E, 0xF4, 0xE9, 0x07, 0x27,
+ 0x9E, 0x5A, 0xC9, 0x39, 0x1D, 0x37, 0x2C, 0x06,
+ 0x4B, 0x57, 0xEA, 0xC5, 0x42, 0x9A, 0x60, 0xD5,
+ 0x42, 0xB2, 0x34, 0x2D, 0xD3, 0x88, 0x7C, 0x78,
+ 0x87, 0xB6, 0xE9, 0x42, 0x44, 0x1F, 0x67, 0x32,
+ 0x92, 0x54, 0x22, 0xDA, 0xB2, 0x43, 0xE7, 0x40,
+ 0xBE, 0x1F, 0xAF, 0x3A, 0xCD, 0x2A, 0x9F, 0xD7,
+ 0x44, 0x5B, 0x37, 0x69, 0x85, 0xDF, 0xEB, 0x2A,
+ 0xB9, 0xE2, 0x92, 0x3B, 0xEA, 0xD5, 0x42, 0x53,
+ 0x95, 0x4A, 0xB0, 0x1B, 0xA5, 0xEF, 0xA6, 0x0D,
+ 0x29, 0xF4, 0x33, 0xFE, 0xD7, 0x49, 0x04, 0x1E,
+ 0x8C, 0xAD, 0x63, 0x1E, 0x79, 0x63, 0x74, 0x0C,
+ 0xE5, 0x5E, 0xA2, 0x2C, 0xBE, 0xB8, 0x90, 0xCE,
+ 0x06, 0x25, 0xBF, 0xD1, 0x5A, 0x50, 0xCF, 0x3B,
+ 0x52, 0xE2, 0xA7, 0xFF, 0x19, 0x02, 0xCF, 0xD0,
+ 0x9B, 0xD9, 0xF7, 0x28, 0x07, 0x38, 0x1F, 0xF2,
+ 0xAF, 0x44, 0x91, 0x3F, 0x0F, 0xB6, 0x6E, 0x8C,
+ 0xC0, 0x32, 0x92, 0xC0, 0xCD, 0x25, 0x98, 0x67,
+ 0xF1, 0x47, 0x52, 0x50, 0xF0, 0xA3, 0x7B, 0xE6,
+ 0x74, 0xDC, 0x72, 0x28, 0xC8, 0xAB, 0xB3, 0x31,
+ 0x7D, 0xA3, 0xF7, 0xC7, 0xD1, 0xE6, 0x99, 0xB4,
+ 0xB6, 0x5A, 0x3A, 0x4D, 0x83, 0x4F, 0xB8, 0xB5,
+ 0x86, 0xF8, 0x37, 0x7F, 0xA0, 0x16, 0x2F, 0x3C,
+ 0x62, 0x7A, 0xD4, 0x3A, 0xEB, 0xC2, 0xE8, 0x03,
+ 0x49, 0x17, 0x9E, 0xFB, 0xD7, 0xAF, 0x91, 0x32,
+ 0xFD, 0xEA, 0x4F, 0x64, 0xC6, 0x6E, 0x02, 0xEA,
+ 0xC4, 0xC8, 0x1F, 0x16, 0xC5, 0x4C, 0xFB, 0xC5,
+ 0x42, 0xF5, 0x85, 0x05, 0x92, 0x59, 0x4B, 0x31,
+ 0xE5, 0xE9, 0x69, 0xE7, 0x02, 0x98, 0x33, 0xBA,
+ 0x4C, 0x17, 0x09, 0xEF, 0x89, 0x20, 0xFA, 0x83,
+ 0x9F, 0xAE, 0x0E, 0x1B, 0x7D, 0x98, 0xB9, 0xF2,
+ 0x3C, 0x0F, 0xB7, 0x1C, 0x72, 0xDF, 0x17, 0x84,
+ 0x7F, 0x0A, 0xFD, 0x12, 0x3C, 0x6F, 0x68, 0x5D,
+ 0x45, 0xEB, 0xB8, 0xD6, 0x24, 0x65, 0x42, 0x75,
+ 0x5C, 0xC2, 0xF3, 0x3A, 0x6A, 0x4E, 0x51, 0x34,
+ 0x1B, 0xB6, 0x81, 0xB2, 0x8A, 0xEF, 0x28, 0xA4,
+ 0xC5, 0x88, 0x9A, 0x97, 0xE0, 0xEF, 0x31, 0x12,
+ 0x01, 0x7E, 0x1B, 0x43, 0x0F, 0x27, 0x80, 0x87,
+ 0x98, 0xC5, 0xD5, 0x83, 0xCB, 0x4B, 0xB7, 0x01,
+ 0x79, 0x60, 0xA1, 0x1A, 0x03, 0x05, 0xC6, 0x36,
+ 0x04, 0x31, 0x3C, 0x06, 0xDB, 0x08, 0xA8, 0xDA,
+ 0x8E, 0x32, 0x19, 0x91, 0xF1, 0x0D, 0x61, 0x6F,
+ 0xE4, 0xB2, 0x79, 0x8A, 0xDE, 0xF4, 0xF7, 0xFB,
+ 0x2C, 0x23, 0x5B, 0xD9, 0x64, 0x2F, 0xB7, 0xB3,
+ 0x8B, 0xCA, 0xB8, 0x8C, 0x1D, 0x3B, 0x49, 0x05,
+ 0x38, 0xA1, 0xE5, 0x8C, 0x1A, 0xDC, 0xA5, 0x61,
+ 0xFE, 0xF4, 0x2B, 0xDC, 0x77, 0x28, 0xF6, 0x19,
+ 0xE7, 0xB7, 0x8F, 0x4D, 0x27, 0x2D, 0xED, 0x8A,
+ 0x3F, 0x3D, 0xDC, 0x9F, 0xD1, 0x30, 0xFF, 0xD6,
+ 0xC3, 0xBE, 0x41, 0x25, 0xE3, 0xA5, 0x9B, 0x73,
+ 0xDF, 0x6A, 0xD9, 0xF9, 0x70, 0x84, 0x02, 0x4C,
+ 0x35, 0xD4, 0x3E, 0x05, 0x76, 0x3A, 0xDC, 0x6D,
+ 0x5A, 0x81, 0xB3, 0x94, 0xF7, 0x22, 0xF7, 0xDC,
+ 0xC1, 0x43, 0x31, 0x57, 0x5B, 0x42, 0x9A, 0x0B,
+ 0xF4, 0x95, 0x30, 0xA9, 0xBB, 0xD8, 0x06, 0xFB,
+ 0x1D, 0x6F, 0x9B, 0xC3, 0xBB, 0xF3, 0xBF, 0xFB,
+ 0xB4, 0x9F, 0x35, 0x64, 0x0A, 0x69, 0xB7, 0xD1,
+ 0x3E, 0xCA, 0x78, 0x07, 0x04, 0x03, 0x79, 0xD4,
+ 0xF3, 0xA8, 0xEC, 0x18, 0xDB, 0x03, 0x5E, 0x47,
+ 0xD7, 0xD0, 0x56, 0x2C, 0x74, 0x94, 0x86, 0x04,
+ 0x46, 0xB8, 0xD4, 0x35, 0x0A, 0x7B, 0xE6, 0x78,
+ 0xC4, 0x43, 0x3C, 0x56, 0xCC, 0x37, 0x8B, 0xFD,
+ 0xE8, 0xF4, 0x57, 0xEA, 0xAE, 0xCF, 0x36, 0x97,
+ 0x12, 0xAC, 0x39, 0xCF, 0x7C, 0xEF, 0x22, 0x67,
+ 0x01, 0xEC, 0xD8, 0x09, 0x49, 0x4E, 0xE3, 0x74,
+ 0xDD, 0x39, 0xE1, 0x39, 0xD7, 0x0C, 0x5F, 0x1B,
+ 0xCE, 0x69, 0xBC, 0x72, 0x44, 0x87, 0x64, 0x1C,
+ 0x08, 0x05, 0x93, 0x69, 0x6D, 0x7F, 0x90, 0x0A,
+ 0x2C, 0xCB, 0x8A, 0xBB, 0x7F, 0xE3, 0xE0, 0x80,
+ 0x31, 0xD0, 0x0A, 0x3A, 0x95, 0xFF, 0xF7, 0xB4,
+ 0x36, 0x38, 0x93, 0xE0, 0x0C, 0x11, 0x37, 0x12,
+ 0x06, 0xF6, 0xAD, 0xE9, 0xB1, 0x7A, 0x00, 0xF5,
+ 0xD2, 0x32, 0x6B, 0xD0, 0x27, 0xA5, 0x1B, 0x3D,
+ 0xE8, 0xDB, 0xCC, 0xA9, 0x1F, 0x1F, 0xB1, 0x99,
+ 0x3D, 0x7C, 0xB7, 0xCA, 0xDA, 0x27, 0x2C, 0x64,
+ 0x1C, 0x49, 0xB6, 0x87, 0x44, 0x06, 0x94, 0x9D,
+ 0xBC, 0x6B, 0x20, 0xA2, 0x68, 0x15, 0x1F, 0xE2,
+ 0xF2, 0xAD, 0x6D, 0x23, 0x2E, 0x2B, 0x74, 0xE2,
+ 0x5D, 0xE4, 0xB0, 0xC7, 0x84, 0xCB, 0x64, 0xBF,
+ 0xE0, 0xA8, 0x18, 0x83, 0xB4, 0xC9, 0xD9, 0x73,
+ 0xA8, 0xE6, 0xA9, 0x36, 0xD5, 0x63, 0x1E, 0x2C,
+ 0x2A, 0x55, 0x09, 0x77, 0x5E, 0xB3, 0x4B, 0xEA,
+ 0xB5, 0xD0, 0x14, 0x5F, 0xEB, 0x50, 0x7B, 0xAA,
+ 0xEF, 0x94, 0xBA, 0x2B, 0xD7, 0x8A, 0x07, 0xF1,
+ 0xF9, 0x5E, 0x12, 0x12, 0x21, 0x52, 0xE5, 0x0A,
+ 0x3E, 0xC0, 0xBC, 0x5D, 0x4C, 0xE2, 0x12, 0x7C,
+ 0x39, 0xF9, 0x16, 0x9D, 0xBD, 0x96, 0x83, 0x3B,
+ 0x7F, 0x3D, 0x6A, 0xEC, 0xF1, 0x25, 0xD2, 0xB0,
+ 0xB0, 0xEB, 0x20, 0x06, 0x07, 0xD6, 0xD9, 0x4C,
+ 0x07, 0x9A, 0x82, 0xC1, 0xFC, 0xF7, 0x66, 0x15,
+ 0xBD, 0x62, 0x65, 0xD8, 0x6C, 0xF6, 0x33, 0x7B,
+ 0x5A, 0x28, 0xEC, 0x90, 0xA1, 0x26, 0x9F, 0xC3,
+ 0x28, 0x4A, 0x64, 0x50, 0x5F, 0xCA, 0xE2, 0x6D,
+ 0xB8, 0x0F, 0xE2, 0x94, 0xB5, 0x8E, 0x1F, 0x8A,
+ 0x8F, 0x6B, 0xA6, 0x86, 0x1F, 0xEE, 0xDC, 0x24,
+ 0xB4, 0xB8, 0x25, 0xEC, 0x28, 0x2D, 0xF9, 0xCB,
+ 0x7D, 0x38, 0xFF, 0xC7, 0x74, 0x2E, 0xD3, 0x10,
+ 0xEC, 0x03, 0x31, 0xEE, 0x83, 0xE7, 0xA4, 0xF7,
+ 0xBA, 0x28, 0x21, 0xE0, 0x7F, 0xB4, 0xB7, 0xE1,
+ 0x7A, 0xF9, 0x2B, 0xB0, 0x2C, 0x3B, 0x80, 0x5F,
+ 0xE0, 0x5D, 0xB2, 0x7E, 0x59, 0xFF, 0x59, 0x07,
+ 0x58, 0x42, 0x57, 0xEE, 0x44, 0xF1, 0xB1, 0xAD,
+ 0xBA, 0xDE, 0xCB, 0x1D, 0x8A, 0x36, 0x67, 0xE8,
+ 0x45, 0xFF, 0x07, 0x8D, 0xEE, 0xA4, 0x51, 0x9C,
+ 0x4C, 0x83, 0x5D, 0x2E, 0x2F, 0xE1, 0x5B, 0x75,
+ 0xE8, 0x29, 0xCD, 0x0B, 0x07, 0x62, 0xE0, 0xC3,
+ 0x0D, 0x1D, 0xEA, 0xCF, 0xF0, 0x8A, 0x65, 0x27,
+ 0x70, 0x42, 0x9F, 0x26, 0x00, 0x15, 0x70, 0xC5,
+ 0x4A, 0xF6, 0x25, 0xD0, 0x40, 0x72, 0xE9, 0xC1,
+ 0x73, 0xFD, 0x48, 0x94, 0xA3, 0x8D, 0x66, 0x63,
+ 0x96, 0x4F, 0xF7, 0xEE, 0xFB, 0x4C, 0xC7, 0xB8,
+ 0x6B, 0xE9, 0x90, 0xE1, 0x2A, 0x66, 0x80, 0x99,
+ 0x3B, 0xB0, 0x1A, 0x6C, 0xF9, 0x0E, 0x72, 0xDA,
+ 0x8E, 0x4F, 0x46, 0xC2, 0x6A, 0x4B, 0x7A, 0x16,
+ 0xE5, 0x26, 0x0B, 0x5C, 0xD4, 0x47, 0x34, 0xE5,
+ 0x37, 0xBE, 0x68, 0x6C, 0xDA, 0xD3, 0x9B, 0x6F,
+ 0xAE, 0x51, 0x9C, 0x99, 0x0A, 0x5B, 0xF8, 0x37,
+ 0xBC, 0xDE, 0xFC, 0x93, 0xC5, 0xE7, 0x0F, 0xEF,
+ 0x0B, 0xA6, 0x07, 0xC2, 0xA6, 0xE6, 0xDA, 0x2D,
+ 0x1B, 0x49, 0xC9, 0xDE, 0x6B, 0x27, 0xDC, 0x00,
+ 0xEF, 0x23, 0x87, 0x0E, 0xEB, 0xD1, 0x48, 0x7D,
+ 0xB4, 0xF2, 0x58, 0xC6, 0x3C, 0xE2, 0x89, 0xBA,
+ 0xB0, 0x05, 0xAC, 0x94, 0x41, 0x9A, 0xA8, 0xFF,
+ 0x3E, 0xBC, 0x3A, 0x52, 0x9C, 0xF9, 0x7F, 0x07,
+ 0x8B, 0xB0, 0x2C, 0x71, 0x83, 0x7B, 0xCF, 0x2E,
+ 0x7F, 0x7C, 0x96, 0x65, 0xD9, 0x08, 0x17, 0xEC,
+ 0xFA, 0xDE, 0x4E, 0x40, 0x12, 0x26, 0x70, 0x71,
+ 0x65, 0xA5, 0xDC, 0x98, 0x47, 0xA3, 0xFC, 0xE0,
+ 0x9A, 0x16, 0xED, 0x45, 0x56, 0x72, 0x50, 0x05,
+ 0x28, 0x2C, 0x99, 0xEC, 0x20, 0x2E, 0x40, 0xC0,
+ 0x26, 0x69, 0xCD, 0x49, 0x45, 0x17, 0xA4, 0xA3,
+ 0x42, 0x0D, 0x14, 0x65, 0x87, 0x33, 0x8C, 0x92,
+ 0xC5, 0xC4, 0x61, 0xFD, 0xE8, 0x68, 0x56, 0x20,
+ 0x57, 0xF5, 0x8E, 0x5F, 0xCF, 0x7E, 0x97, 0xF6,
+ 0x49, 0x97, 0x0A, 0xFE, 0xD3, 0x60, 0x1A, 0x5B,
+ 0x0C, 0x75, 0xDD, 0x8E, 0x31, 0x78, 0x29, 0xA6,
+ 0xB1, 0x4D, 0xAA, 0xDF, 0x8A, 0xD1, 0xE6, 0x91,
+ 0xE3, 0x32, 0x3F, 0xEC, 0x8A, 0x1F, 0x0E, 0x35,
+ 0x07, 0x6E, 0x4B, 0x83, 0x3B, 0xE5, 0x67, 0x34,
+ 0x1F, 0x0C, 0x81, 0xD8, 0xD5, 0x25, 0x68, 0xE5,
+ 0x28, 0x1B, 0x5C, 0x81, 0x3E, 0xE3, 0x5C, 0xB4,
+ 0xB6, 0xBD, 0x62, 0x6A, 0x70, 0x33, 0xC2, 0xC5,
+ 0x75, 0x27, 0xF4, 0x30, 0xE1, 0x1D, 0xC1, 0x4C,
+ 0xC5, 0x02, 0x12, 0x46, 0xAC, 0xEC, 0xF9, 0xE8,
+ 0xE7, 0x58, 0x24, 0x11, 0xB1, 0xF3, 0xB7, 0x8C,
+ 0x3C, 0xA4, 0x0A, 0x94, 0xA6, 0x7C, 0x68, 0x54,
+ 0x5B, 0xB9, 0x4D, 0x57, 0x9C, 0xE7, 0x28, 0x09,
+ 0x6B, 0x89, 0x26, 0x5D, 0xE7, 0x50, 0xA9, 0x95,
+ 0x90, 0x91, 0x8E, 0x00, 0x59, 0xF8, 0x3A, 0x70,
+ 0xAF, 0x48, 0x2E, 0xE8, 0xC4, 0x34, 0x8C, 0xF4,
+ 0x5F, 0x7F, 0xCB, 0x07, 0xAA, 0xF0, 0xD9, 0xFB,
+ 0x5C, 0x32, 0x90, 0x22, 0x1A, 0xD2, 0x1A, 0xCF,
+ 0x92, 0x06, 0x02, 0xCF, 0x10, 0x18, 0x7B, 0x93,
+ 0xCC, 0x07, 0x4A, 0x31, 0x25, 0x30, 0x23, 0x06,
+ 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01,
+ 0x09, 0x15, 0x31, 0x16, 0x04, 0x14, 0xD1, 0xDE,
+ 0x23, 0x16, 0x9F, 0x6E, 0xF4, 0x42, 0x21, 0x23,
+ 0xE1, 0x11, 0xAA, 0xC8, 0x7C, 0x60, 0x4A, 0x78,
+ 0x9D, 0x24, 0x30, 0x31, 0x30, 0x21, 0x30, 0x09,
+ 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05,
+ 0x00, 0x04, 0x14, 0xD6, 0x4A, 0xBB, 0x75, 0xB1,
+ 0xF9, 0x9E, 0xD3, 0x58, 0x6D, 0xD1, 0x74, 0x9F,
+ 0x00, 0x8A, 0xF2, 0xC8, 0xAA, 0x52, 0x4D, 0x04,
+ 0x08, 0x77, 0x46, 0xE7, 0xBA, 0x25, 0x4B, 0xDA,
+ 0x41, 0x02, 0x02, 0x08, 0x00]
+};
+
+const WPA_EAP_CLIENT_LIST = [
+ {
+ ssid: 'WPA-EAP-PEAP',
+ keyManagement: 'WPA-EAP',
+ eap: 'PEAP',
+ identity: EAP_USERNAME,
+ password: EAP_PASSWORD,
+ serverCertificate: CLIENT_PKCS12_CERT.nickname,
+ phase2: 'MSCHAPV2'
+ }
+];
+
+/**
+ * Convert the given MozWifiNetwork object array to testAssociate chain.
+ *
+ * @param aNetworks
+ * An array of MozWifiNetwork which we want to convert.
+ *
+ * @return A promise chain which "then"s testAssociate accordingly.
+ */
+function convertToTestAssociateChain(aNetworks) {
+ let chain = Promise.resolve();
+
+ aNetworks.forEach(function (aNetwork) {
+ network = new window.MozWifiNetwork(aNetwork);
+ chain = chain.then(() => gTestSuite.testAssociate(network));
+ });
+
+ return chain;
+}
+
+gTestSuite.doTestWithCertificate(
+ new Blob([new Uint8Array(CLIENT_PKCS12_CERT.content)]),
+ CLIENT_PKCS12_CERT.password,
+ CLIENT_PKCS12_CERT.nickname,
+ CLIENT_PKCS12_CERT.usage,
+ function() {
+ return gTestSuite.ensureWifiEnabled(true)
+ // Load required server files.
+ .then(() => gTestSuite.writeFile(SERVER_EAP_USER_CONF.path, SERVER_EAP_USER_CONF.content))
+ .then(() => gTestSuite.writeFile(CA_CERT.path, CA_CERT.content))
+ .then(() => gTestSuite.writeFile(SERVER_CERT.path, SERVER_CERT.content))
+ .then(() => gTestSuite.writeFile(SERVER_KEY.path, SERVER_KEY.content))
+ // Start AP.
+ .then(() => gTestSuite.startHostapds(WPA_EAP_AP_LIST))
+ // Scan test.
+ .then(() => gTestSuite.testWifiScanWithRetry(SCAN_RETRY_CNT, WPA_EAP_AP_LIST))
+ // Associate test.
+ .then(() => convertToTestAssociateChain(WPA_EAP_CLIENT_LIST))
+ // Tear down.
+ .then(gTestSuite.killAllHostapd)
+});
diff --git a/dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_TLS.js b/dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_TLS.js
new file mode 100644
index 000000000..35b942f8e
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_TLS.js
@@ -0,0 +1,622 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const SCAN_RETRY_CNT = 5;
+
+const EAP_USERNAME = 'username';
+const EAP_PASSWORD = 'password';
+
+const SERVER_EAP_USER_CONF = {
+ path: HOSTAPD_CONFIG_PATH + 'hostapd.eap_user',
+ content: '* PEAP,TTLS,TLS\n' +
+ '"' + EAP_USERNAME + '" MSCHAPV2,TTLS-MSCHAPV2 "' + EAP_PASSWORD + '" [2]\n'
+};
+const CA_CERT = {
+ path: HOSTAPD_CONFIG_PATH + 'ca.pem',
+ content: '-----BEGIN CERTIFICATE-----\n' +
+ 'MIIDsTCCApmgAwIBAgIJAKxTf+8X8qngMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV\n' +
+ 'BAYTAlRXMRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQKDAhjaHVja2xlZTER\n' +
+ 'MA8GA1UEAwwIY2h1Y2tsZWUxJDAiBgkqhkiG9w0BCQEWFWNodWNrbGkwNzA2QGdt\n' +
+ 'YWlsLmNvbTAgFw0xNDEyMjQxMTI4NTBaGA8yMjg4MTAwNzExMjg1MFowbjELMAkG\n' +
+ 'A1UEBhMCVFcxEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAoMCGNodWNrbGVl\n' +
+ 'MREwDwYDVQQDDAhjaHVja2xlZTEkMCIGCSqGSIb3DQEJARYVY2h1Y2tsaTA3MDZA\n' +
+ 'Z21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo3c2yFxY\n' +
+ 'o6gGg0I83jy00ME+MAfzCd+4ShL45ZLqysQP93jRBfPzU9ZuZ29ysVwgWIdqkZao\n' +
+ 'XTuV/NAW2GMGd8W1jQJ3J81fjb9wvhlny3rrACwvUn1N1S1BnM+BAAiDLGxEmvAQ\n' +
+ 'onp2aaa6HsHsYS8ONX+d2Qh4LEA4vupeSGAqJychCZv/l+aq/ErFZhFYB3CPUQEt\n' +
+ 'cClO24ucsIYP95lA0zhscnmAj06qplFD4Bv6IVrdDqujy1zNwCQwsJq/8OQdaTN/\n' +
+ 'h3y9pWvNKMBMM2niOUAjtuNpqsSK/lTS1WAT3PdtVECX9fYBi0Bg+HM92xs/6gt6\n' +
+ 'kh9jPV8keXHvSwIDAQABo1AwTjAdBgNVHQ4EFgQU7hBqhuG04xeCzrQ3ngx18ZJ3\n' +
+ 'lUswHwYDVR0jBBgwFoAU7hBqhuG04xeCzrQ3ngx18ZJ3lUswDAYDVR0TBAUwAwEB\n' +
+ '/zANBgkqhkiG9w0BAQsFAAOCAQEAFYX2iy680GAnBTttk0gyX6gk+8pYr3D22k/G\n' +
+ '6rvcjefzS7ELQPRKr6mfmwXq3mMf/4jiS2zI5zmXsestPYzHYxf2viQ6t7vr9XiJ\n' +
+ '3WfFjNw4ERlRisAvg0aqqTNNQq5v2VME4sdFZagy217f73C7azwCHl0bqOLH05rl\n' +
+ '8RubOxiHEj7ZybJqnRciK/bht4D+rZkwf4bBBmoloqH7xT0+rFQclpYXDGGjNUQB\n' +
+ 'LcHLF10xcr7g3ZVVu82fe6+d85gIGOIMR9+TKhdw6gO3CNcnDAj6gxksghgtcxmh\n' +
+ 'OzOggCn7nlIwImtsg2sZkpWB4lEi9hdv4lkNuyFjOL3bnuc+NA==\n' +
+ '-----END CERTIFICATE-----\n'
+};
+
+const SERVER_CERT = {
+ path: HOSTAPD_CONFIG_PATH + 'server.pem',
+ content: '-----BEGIN CERTIFICATE-----\n' +
+ 'MIID1DCCArygAwIBAgIBADANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJUVzET\n' +
+ 'MBEGA1UECAwKU29tZS1TdGF0ZTERMA8GA1UECgwIY2h1Y2tsZWUxETAPBgNVBAMM\n' +
+ 'CGNodWNrbGVlMSQwIgYJKoZIhvcNAQkBFhVjaHVja2xpMDcwNkBnbWFpbC5jb20w\n' +
+ 'IBcNMTQxMjI0MTEyOTQ5WhgPMjI4ODEwMDcxMTI5NDlaMG4xCzAJBgNVBAYTAlRX\n' +
+ 'MRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQKDAhjaHVja2xlZTERMA8GA1UE\n' +
+ 'AwwIY2h1Y2tsZWUxJDAiBgkqhkiG9w0BCQEWFWNodWNrbGkwNzA2QGdtYWlsLmNv\n' +
+ 'bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMdhQmKilTJbWZRxTiSV\n' +
+ 'rqIU+LYW1RKghx5o+0JpNRJVLuz5kBMaNskbbfUSNuHbEq0QA9BDKAZWIc4LSotk\n' +
+ 'lCo8TbcO9CJvJPQGGjGdHcohWX5vy6BE/OVE46CUteMFyZF6F8R2fNUww08iR/u1\n' +
+ 'YZebL5pWO1j43sPpAzEy6Tij2ACPt6EZcFaZG3SF2mVJWkCQnBqrojP65WUvZQqp\n' +
+ 'seUhW2YAS8Nu0Yrohgxz6VYk+cNDuDZVGs6qWRStZzJfYrfc76DtkHof5B14M+xp\n' +
+ 'XJaBLxN+whvnYkDTfinaCxnW1O7eXUltr87fLc5zmeBkgwaiaQuIdcfZm7vDUiz8\n' +
+ 'vnUCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBH\n' +
+ 'ZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFKK4f9/YavTHOfEiAB83Deac\n' +
+ '6gT5MB8GA1UdIwQYMBaAFO4QaobhtOMXgs60N54MdfGSd5VLMA0GCSqGSIb3DQEB\n' +
+ 'CwUAA4IBAQBWnO9o9KSJIqjoz5Nwll63ULOdcvgGdOeJIw1fcKQ817Rsp+TVcjcH\n' +
+ 'IrIADsT/QZGXRO/l6p1750e2iFtJEo1hsRaxtA1wWn2I9HO3+av2spQhr3jpYGPf\n' +
+ 'zpsMTp4RNYV7Q8+q1kZIz9PY4V1T0p6lveK8+fUj2hSLnxSj0QiGSJJtnEC3w4Rv\n' +
+ 'C9T6oUwIeToULmi+8FXQFdEqwKRU98DPq3eLzN28ZxUgoPE1C8+42D2UW8uyp/Gm\n' +
+ 'tGOa/k7nzkCdVqZI7lX7f0AjEvQgjtAMQ/k7Mhxx7TzW2HO+1YPMoKji6Z4WkNwt\n' +
+ 'JEj9ZUBSNt8B26UksJMBDkcvSegF3a7o\n' +
+ '-----END CERTIFICATE-----\n'
+};
+
+const SERVER_KEY = {
+ path: HOSTAPD_CONFIG_PATH + 'server.key',
+ content: '-----BEGIN RSA PRIVATE KEY-----\n' +
+ 'MIIEpAIBAAKCAQEAx2FCYqKVMltZlHFOJJWuohT4thbVEqCHHmj7Qmk1ElUu7PmQ\n' +
+ 'Exo2yRtt9RI24dsSrRAD0EMoBlYhzgtKi2SUKjxNtw70Im8k9AYaMZ0dyiFZfm/L\n' +
+ 'oET85UTjoJS14wXJkXoXxHZ81TDDTyJH+7Vhl5svmlY7WPjew+kDMTLpOKPYAI+3\n' +
+ 'oRlwVpkbdIXaZUlaQJCcGquiM/rlZS9lCqmx5SFbZgBLw27RiuiGDHPpViT5w0O4\n' +
+ 'NlUazqpZFK1nMl9it9zvoO2Qeh/kHXgz7GlcloEvE37CG+diQNN+KdoLGdbU7t5d\n' +
+ 'SW2vzt8tznOZ4GSDBqJpC4h1x9mbu8NSLPy+dQIDAQABAoIBAASG4Mr8hgaurEoC\n' +
+ 'iJOsElr7vunjetMBcg/uskW/vcS8ymP3Bp5oafYG+WgnEbfvEW18f5mq7K24JuxW\n' +
+ 'tUqU7ghHdjxByqk9fMlNmiqmNpbwSufkAeuRpWxPNBvhRH/zEbCL5R5A0nTEtqqF\n' +
+ 'TL0aUSzwCRSoAJD0lZo9ICVt0n3GsDyM9rqQg/uZmh1qsRdwPsRuYORND9g48rKq\n' +
+ '6WN9leskSxhhsYE2D9ocOFd9bNt8Zxejh9ppVSnG/KsIdt18iBzcabatgAQ046fb\n' +
+ 'Z3vprcZJLg93Sg2gSuVqlSTs3M2W8VQnm22/EBMb1y0M48MSRCgnbPLG/CcCLLfF\n' +
+ 'LwxCOgECgYEA/eYt67xyJ6JeAdxdwOZuT1WWGbFpLiG9+2OgiHumyRQ5969XMTWo\n' +
+ 'fIhMKchDdjoy9RR236\/\/EFCs7UEyB7+a7ODRzNiK2zCD8Smjp+21fUPSthEeQesk\n' +
+ 'eiMYICIu5Ay35x9sxIX+XOUVvRhPOGcD29GVeRnKh1inTHOz2dje8LkCgYEAyQeY\n' +
+ 'STi9jjCEcHkM1E/UeDiLfHHepLXi8wS41JNRHl5Jacp7XB5djAjKu/jf367/VpFy\n' +
+ 'GDDMetE7n8eWkrnAvovxOwZ000YDMtL1sUYSjL+XtBS5s6VY1p4qaSAY9nUUGrJh\n' +
+ 'JvtvsuI7SKTtL+60vjBOH7zDnvOdBgAp0utLhZ0CgYEAuLzzqrPKB8afShFSchn4\n' +
+ 'J2dpuLYahsNsXW7HDqeR2nsKFosRETAusLXnXPtnAq4kB6jlOarwFqnsuRCX24Vx\n' +
+ 'r2uBm9/vYL7zMdUPTA+s30ErHuhjsKjsOKYyVqcooSwT32pBFNk+E89nutfmRG7I\n' +
+ 'IvhjHuNCNqqtx/Xj5d1jkZkCgYBQicppC2Jl5OoqZVTOem0U/RJk+PnJ41TZJ7sk\n' +
+ '7yBAmmWvDH\/\/l+rCf4M5a6vFYcbKV9rt9h711X2dtciNX/3oWQh8LUoAmrwNUJc+\n' +
+ 'PmSQHvIYI3WCk2vUD+nN1B4sHxu+1lg11eYaNKiroeeknG2tBI1ICcgVlmQCU25u\n' +
+ 'IfZPwQKBgQCdO6QHhPLtcHUDNFA6FQ1jKL1iEd7G0JLVRz4Xkpkn1Vrr5MD6JFDa\n' +
+ '5ccabADyl0lpFqDIVJQIzLku2hOD2i9aBNCY0pL391HeOS7CkZX+TdOY1tquoBq5\n' +
+ 'MnmixZjDCVd2VcrVyTA6ntOBoharKFW0rH1PqU+qu7dZF7CBPbAdEw==\n' +
+ '-----END RSA PRIVATE KEY-----\n'
+};
+
+const WPA_EAP_AP_LIST = [
+ {
+ ssid: 'WPA-EAP-TLS',
+ ieee8021x: 1,
+ eapol_version: 1,
+ eap_server: 1,
+ eapol_key_index_workaround: 0,
+ eap_user_file: SERVER_EAP_USER_CONF.path,
+ ca_cert: CA_CERT.path,
+ server_cert: SERVER_CERT.path,
+ private_key: SERVER_KEY.path,
+ wpa: 2,
+ wpa_key_mgmt: 'WPA-EAP'
+ }
+];
+
+const CLIENT_PKCS12_CERT = {
+ nickname: 'client',
+ password: 'password',
+ usage: ['UserCert', 'ServerCert'],
+ content: [0x30, 0x82, 0x0E, 0x01, 0x02, 0x01, 0x03, 0x30,
+ 0x82, 0x0D, 0xC7, 0x06, 0x09, 0x2A, 0x86, 0x48,
+ 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01, 0xA0, 0x82,
+ 0x0D, 0xB8, 0x04, 0x82, 0x0D, 0xB4, 0x30, 0x82,
+ 0x0D, 0xB0, 0x30, 0x82, 0x08, 0x67, 0x06, 0x09,
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07,
+ 0x06, 0xA0, 0x82, 0x08, 0x58, 0x30, 0x82, 0x08,
+ 0x54, 0x02, 0x01, 0x00, 0x30, 0x82, 0x08, 0x4D,
+ 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D,
+ 0x01, 0x07, 0x01, 0x30, 0x1C, 0x06, 0x0A, 0x2A,
+ 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01,
+ 0x06, 0x30, 0x0E, 0x04, 0x08, 0x67, 0x7A, 0xF3,
+ 0x61, 0xBE, 0xE0, 0x51, 0xC1, 0x02, 0x02, 0x08,
+ 0x00, 0x80, 0x82, 0x08, 0x20, 0xFC, 0x6A, 0x79,
+ 0xA1, 0x6C, 0xAF, 0xBE, 0xEE, 0x62, 0x45, 0x33,
+ 0xB8, 0x48, 0xE1, 0x68, 0xA1, 0x15, 0x11, 0x4B,
+ 0x95, 0xCB, 0x77, 0xC0, 0x5D, 0xA2, 0xCB, 0xDB,
+ 0xD1, 0x83, 0x74, 0x60, 0xD7, 0xEC, 0x42, 0xA6,
+ 0x3A, 0x23, 0xF7, 0x85, 0xEB, 0xC1, 0xFE, 0x6A,
+ 0x57, 0x8E, 0xC1, 0x44, 0xF3, 0x1F, 0xFE, 0xB8,
+ 0x2D, 0x8C, 0x4D, 0xC9, 0x5B, 0xAE, 0x21, 0x2E,
+ 0x4C, 0x1A, 0xEB, 0x84, 0x09, 0xF3, 0x40, 0x92,
+ 0x39, 0x7F, 0x56, 0x02, 0x46, 0x61, 0x16, 0xDE,
+ 0x5C, 0x48, 0xB6, 0x0C, 0x1D, 0xD3, 0x5F, 0x10,
+ 0x9A, 0x39, 0xB8, 0x66, 0x31, 0xFC, 0x39, 0x71,
+ 0x87, 0x23, 0x46, 0x9D, 0xE8, 0x3C, 0x2B, 0xA1,
+ 0x39, 0x8A, 0xD3, 0xFF, 0xD9, 0x43, 0xB6, 0x61,
+ 0xC6, 0x67, 0x70, 0x40, 0xBD, 0xFE, 0xD3, 0xC1,
+ 0x68, 0xF5, 0xF7, 0xC8, 0x89, 0xD8, 0x17, 0xC5,
+ 0xE8, 0x3D, 0x29, 0xD5, 0x91, 0xDF, 0x1F, 0x56,
+ 0x74, 0x5A, 0xC4, 0xA8, 0x14, 0xBA, 0xD4, 0xFA,
+ 0x13, 0x49, 0x2A, 0x9F, 0x63, 0xF1, 0xB2, 0x45,
+ 0xF1, 0xF0, 0x2A, 0xDD, 0x75, 0x66, 0x8A, 0xF7,
+ 0xAB, 0x73, 0x86, 0x26, 0x9D, 0x1F, 0x07, 0xAD,
+ 0xD3, 0xFE, 0xE0, 0xA3, 0xED, 0xA0, 0x96, 0x3E,
+ 0x1E, 0x89, 0x86, 0x02, 0x4C, 0x28, 0xFD, 0x57,
+ 0xA1, 0x67, 0x55, 0xF0, 0x82, 0x3B, 0x7F, 0xCC,
+ 0x2A, 0x32, 0x01, 0x93, 0x1D, 0x8B, 0x66, 0x8A,
+ 0x20, 0x52, 0x84, 0xDD, 0x2C, 0xFD, 0xEE, 0x72,
+ 0xF3, 0x8C, 0x58, 0xB9, 0x99, 0xE5, 0xC1, 0x22,
+ 0x63, 0x59, 0x00, 0xE2, 0x76, 0xC5, 0x3A, 0x17,
+ 0x7F, 0x93, 0xE9, 0x67, 0x61, 0xAA, 0x10, 0xC3,
+ 0xD9, 0xC8, 0x24, 0x46, 0x5B, 0xBE, 0x8C, 0x1F,
+ 0x2D, 0x66, 0x48, 0xD2, 0x02, 0x11, 0xFB, 0x74,
+ 0x14, 0x76, 0x76, 0x5A, 0x98, 0x54, 0x35, 0xA7,
+ 0x85, 0x66, 0x20, 0x26, 0x8B, 0x13, 0x6F, 0x68,
+ 0xE3, 0xC9, 0x58, 0x7D, 0x1C, 0x3E, 0x01, 0x8D,
+ 0xF8, 0xD6, 0x7F, 0xCF, 0xA2, 0x07, 0xB7, 0x95,
+ 0xFD, 0xF0, 0x02, 0x34, 0x32, 0x30, 0xE8, 0xD4,
+ 0x57, 0x5E, 0x53, 0xFB, 0x54, 0xE2, 0x03, 0x32,
+ 0xCC, 0x52, 0x2E, 0xD2, 0x35, 0xD9, 0x58, 0x85,
+ 0x2D, 0xEC, 0x2D, 0x71, 0xD1, 0x8A, 0x29, 0xD0,
+ 0xB0, 0x24, 0xBD, 0x24, 0xDC, 0x1A, 0x28, 0x3F,
+ 0xA0, 0x12, 0x81, 0x15, 0x24, 0xC9, 0xB5, 0x4A,
+ 0x23, 0xB6, 0xA3, 0x45, 0x50, 0x2D, 0x73, 0x99,
+ 0x6B, 0x1C, 0xFB, 0xA4, 0x53, 0xD7, 0x5C, 0xF4,
+ 0x6C, 0xB0, 0xE5, 0x74, 0xB3, 0x76, 0xF8, 0xB1,
+ 0x0D, 0x59, 0x70, 0x9F, 0xCA, 0xDE, 0xF2, 0xAA,
+ 0x4C, 0x7D, 0x11, 0x54, 0xC4, 0x19, 0x0F, 0x36,
+ 0x4A, 0x62, 0xFF, 0x8B, 0x10, 0xCB, 0x93, 0x50,
+ 0xDA, 0x79, 0x5E, 0x4E, 0x09, 0x1F, 0x22, 0xC8,
+ 0x19, 0x85, 0xE9, 0xEE, 0xB7, 0x71, 0x65, 0xB9,
+ 0x10, 0xD2, 0x0A, 0x73, 0x5B, 0xA6, 0xDA, 0x37,
+ 0x46, 0x02, 0x00, 0x98, 0x9E, 0x20, 0x6C, 0x7D,
+ 0xC7, 0x69, 0xBB, 0xC2, 0x00, 0x40, 0x9C, 0x57,
+ 0x00, 0xC2, 0x36, 0x76, 0xE8, 0x2A, 0x8D, 0xAD,
+ 0x62, 0x57, 0xC8, 0xD0, 0x9D, 0x66, 0x27, 0x5A,
+ 0xD8, 0x0D, 0x35, 0x60, 0x28, 0x38, 0x62, 0x94,
+ 0x78, 0x36, 0x25, 0x58, 0xFD, 0xF8, 0x66, 0x1F,
+ 0x68, 0x04, 0x0F, 0xD8, 0x00, 0xDF, 0xA0, 0x6C,
+ 0x25, 0x42, 0x9A, 0x4C, 0xEB, 0x80, 0x13, 0x51,
+ 0x7D, 0x2D, 0xA8, 0x89, 0xD6, 0x1B, 0x67, 0x72,
+ 0x01, 0xF3, 0x2D, 0x16, 0x77, 0xFE, 0x22, 0xBC,
+ 0x8A, 0x45, 0x09, 0x1F, 0x9C, 0x2F, 0x2A, 0xA9,
+ 0x61, 0x5B, 0x4A, 0xE6, 0x64, 0x2C, 0x62, 0x1A,
+ 0x3A, 0x96, 0xE6, 0x0A, 0xAE, 0x05, 0x1A, 0xC8,
+ 0xCB, 0xD6, 0x8F, 0x3A, 0x4B, 0xE0, 0x7F, 0x82,
+ 0xB4, 0x98, 0xF1, 0x9D, 0xD7, 0x14, 0x76, 0x5E,
+ 0x77, 0x85, 0x87, 0xEC, 0x13, 0xDA, 0xFD, 0xAF,
+ 0xCB, 0xA3, 0x1C, 0x99, 0xC1, 0xFE, 0x17, 0x0C,
+ 0x40, 0x4D, 0x3C, 0x8F, 0x70, 0x86, 0x63, 0x64,
+ 0xB7, 0x75, 0xA8, 0x71, 0x36, 0xDC, 0x54, 0x10,
+ 0x57, 0x0C, 0xA8, 0xF2, 0xA1, 0xBB, 0xED, 0x03,
+ 0x41, 0x57, 0x34, 0x2C, 0x8F, 0x7C, 0xA0, 0x09,
+ 0xF3, 0x9E, 0x41, 0xB7, 0xA8, 0xD4, 0x66, 0x0D,
+ 0x0D, 0xC0, 0x6A, 0xFC, 0x6A, 0xA2, 0xAC, 0xE2,
+ 0x60, 0x00, 0xE3, 0xF7, 0x75, 0x43, 0x23, 0xEB,
+ 0xC8, 0x61, 0xFA, 0xB3, 0xB8, 0x28, 0xCE, 0xCA,
+ 0xF4, 0x47, 0x7F, 0x30, 0x6D, 0x61, 0x89, 0x47,
+ 0xA1, 0x4A, 0xFE, 0xD1, 0x21, 0x0B, 0x6D, 0xF4,
+ 0x3F, 0x00, 0x86, 0x30, 0x8E, 0x33, 0x21, 0x6F,
+ 0xDA, 0x15, 0xFD, 0x5F, 0xEC, 0x8E, 0xF1, 0x12,
+ 0x3F, 0xC9, 0x83, 0x0C, 0xCA, 0x22, 0x01, 0xF1,
+ 0x70, 0x5F, 0x1F, 0x66, 0xB5, 0xF8, 0x3E, 0x7A,
+ 0x6F, 0xDE, 0xDB, 0xA7, 0x8D, 0x18, 0x9E, 0xBE,
+ 0xDB, 0xAD, 0x3D, 0x66, 0x30, 0xC1, 0x6C, 0x0C,
+ 0x87, 0xB4, 0x65, 0x75, 0xE0, 0x9D, 0xEA, 0x16,
+ 0x0D, 0x07, 0x37, 0x33, 0xC5, 0xEC, 0x97, 0x93,
+ 0x37, 0xEB, 0x8E, 0x65, 0x9C, 0x40, 0x63, 0x6C,
+ 0x43, 0x60, 0xB0, 0x40, 0x4D, 0x85, 0xEF, 0xC2,
+ 0x47, 0x5F, 0xE7, 0x6B, 0xCB, 0x40, 0xE8, 0xEA,
+ 0xD8, 0xAB, 0xB1, 0x9A, 0x72, 0xDC, 0x4C, 0x14,
+ 0xFA, 0x43, 0x61, 0x5F, 0xA6, 0x5C, 0x3A, 0x05,
+ 0x17, 0x2E, 0x74, 0xF3, 0x5E, 0x45, 0xD9, 0x47,
+ 0xAA, 0x59, 0xB6, 0x8F, 0x42, 0x66, 0x42, 0x29,
+ 0x90, 0x95, 0x48, 0x46, 0x91, 0x88, 0x3C, 0x8C,
+ 0xDE, 0xCC, 0xED, 0xB3, 0xAA, 0x62, 0xEA, 0xBC,
+ 0xB4, 0x0C, 0x48, 0x4C, 0x53, 0x23, 0x5E, 0x24,
+ 0x85, 0xBF, 0x92, 0xDA, 0x14, 0xDB, 0x1A, 0x3D,
+ 0xEF, 0x30, 0xD9, 0x49, 0x64, 0x4D, 0xE5, 0x01,
+ 0xFC, 0xA4, 0x4B, 0xD1, 0x9F, 0xDE, 0x96, 0x7F,
+ 0x50, 0xBC, 0x4D, 0x38, 0x44, 0xE9, 0x23, 0x5F,
+ 0x37, 0x57, 0x1A, 0xA6, 0x52, 0x5A, 0x0F, 0x4F,
+ 0x87, 0x33, 0x4A, 0x7B, 0x66, 0xEE, 0x3D, 0x66,
+ 0x0A, 0x63, 0x39, 0x1F, 0x23, 0x38, 0x35, 0x73,
+ 0x60, 0x5E, 0x47, 0x20, 0x4F, 0xC0, 0xC8, 0x3C,
+ 0x09, 0xF9, 0x29, 0x4F, 0x5E, 0x55, 0x69, 0xC4,
+ 0x6B, 0xE8, 0xF8, 0x91, 0xC0, 0x22, 0x65, 0x15,
+ 0x1E, 0xFB, 0xB9, 0x61, 0xCE, 0x45, 0xBE, 0x2B,
+ 0xEE, 0xB9, 0x04, 0x2B, 0xFD, 0xAE, 0x61, 0x1C,
+ 0x3D, 0x3D, 0x7C, 0xBF, 0xC1, 0xF7, 0x3C, 0x4E,
+ 0x9E, 0x0E, 0x54, 0xC8, 0xAD, 0xA9, 0xDF, 0x43,
+ 0x49, 0xB9, 0x41, 0x05, 0xE5, 0xF1, 0x49, 0xAA,
+ 0x77, 0x6C, 0x34, 0x5B, 0x93, 0x24, 0x24, 0x23,
+ 0x74, 0x68, 0x11, 0xCE, 0x15, 0x80, 0xA1, 0xA4,
+ 0x1F, 0x8D, 0x81, 0xCD, 0xB2, 0x98, 0xCA, 0x14,
+ 0x0B, 0x0C, 0x61, 0x50, 0x69, 0x72, 0xAE, 0xFA,
+ 0x8B, 0xC0, 0x3F, 0x0D, 0xE7, 0xF2, 0x0F, 0xEB,
+ 0xC1, 0x11, 0xB9, 0x10, 0x03, 0x6A, 0xF5, 0x97,
+ 0x3C, 0x53, 0x2F, 0x67, 0x86, 0x09, 0x6A, 0xE3,
+ 0x28, 0xC0, 0x78, 0xC8, 0xB4, 0x39, 0x8E, 0xD1,
+ 0xCE, 0x25, 0xE8, 0x66, 0xF7, 0x09, 0x40, 0x7D,
+ 0x81, 0xFB, 0xAF, 0xFA, 0x59, 0xC4, 0x9B, 0x2B,
+ 0x83, 0x45, 0x5B, 0xA8, 0x66, 0x9E, 0x38, 0xC8,
+ 0xFD, 0xAC, 0xF2, 0x2D, 0x21, 0xDE, 0x50, 0x4C,
+ 0x03, 0xCB, 0x88, 0x42, 0xDD, 0x84, 0x09, 0x99,
+ 0x8E, 0x8B, 0x40, 0x97, 0x1B, 0x14, 0x85, 0x37,
+ 0x11, 0x01, 0xE0, 0x74, 0x6B, 0x33, 0x52, 0x8C,
+ 0x68, 0x3A, 0x89, 0xB2, 0xAF, 0x35, 0xE6, 0x65,
+ 0xC3, 0x58, 0x70, 0xD2, 0xE7, 0x1F, 0x1F, 0xF6,
+ 0xE5, 0x0A, 0xB1, 0xFE, 0xD0, 0xC9, 0x51, 0x50,
+ 0xE7, 0xFD, 0x58, 0xF5, 0xC4, 0x58, 0x65, 0x94,
+ 0xD1, 0x57, 0x55, 0x5E, 0xD2, 0x27, 0x98, 0xAF,
+ 0xE7, 0x55, 0x0B, 0x87, 0x50, 0x9B, 0xEF, 0xE8,
+ 0x2B, 0xFC, 0xE7, 0x3B, 0x4E, 0xD7, 0xB7, 0x4D,
+ 0xF4, 0xBC, 0xF4, 0x88, 0x63, 0xE4, 0x8A, 0x20,
+ 0x4B, 0x22, 0xB0, 0xA0, 0x53, 0x7F, 0xA8, 0xC9,
+ 0x0C, 0xF8, 0xD7, 0xBD, 0x46, 0x39, 0xA7, 0x7D,
+ 0xDD, 0x10, 0x91, 0x50, 0x54, 0x06, 0x47, 0xF0,
+ 0x3C, 0xAA, 0x43, 0x40, 0xF8, 0x54, 0xDD, 0x8A,
+ 0xEA, 0x8A, 0x0B, 0xA5, 0x7F, 0xCD, 0x5E, 0xAA,
+ 0x02, 0x2E, 0x1F, 0xC6, 0x50, 0x15, 0xF8, 0x0A,
+ 0x0C, 0x1B, 0x3C, 0x55, 0x3A, 0xC3, 0x6F, 0x88,
+ 0xD7, 0xBF, 0xB1, 0x02, 0xCC, 0xE0, 0x08, 0x29,
+ 0x97, 0xD2, 0xAA, 0x23, 0xC4, 0x6D, 0xE3, 0xE3,
+ 0x76, 0x39, 0x92, 0xC3, 0x2E, 0x7A, 0xE2, 0x98,
+ 0xD1, 0xFC, 0xAE, 0xCC, 0x95, 0xD8, 0xB4, 0xDC,
+ 0x92, 0xEA, 0x6A, 0x5F, 0xF2, 0x92, 0x17, 0x0B,
+ 0x8D, 0xC3, 0xFA, 0x9C, 0x62, 0xCE, 0x44, 0x8D,
+ 0xC3, 0x1E, 0xC3, 0xB2, 0xD5, 0x00, 0xCD, 0xB4,
+ 0x9E, 0x2D, 0x7B, 0xF2, 0x98, 0xA3, 0x00, 0x8B,
+ 0x81, 0x30, 0x77, 0x5B, 0x02, 0x99, 0xB1, 0xCD,
+ 0xC3, 0x1D, 0x74, 0x74, 0xEF, 0x41, 0xCB, 0x69,
+ 0x63, 0x8E, 0xA6, 0xD3, 0x2D, 0x3E, 0x1F, 0x1D,
+ 0x12, 0x9E, 0xD9, 0x18, 0x67, 0x06, 0xAF, 0x37,
+ 0x29, 0xAD, 0x65, 0xD8, 0xEB, 0x71, 0xC4, 0x7D,
+ 0x94, 0x3D, 0xEA, 0xCC, 0xDF, 0x72, 0x41, 0x51,
+ 0x3C, 0xA1, 0x66, 0x98, 0x32, 0x32, 0x40, 0x54,
+ 0xB0, 0x2F, 0xEB, 0xCE, 0xDF, 0x4A, 0x64, 0xFB,
+ 0x9A, 0x90, 0xDC, 0xF6, 0x6F, 0xA9, 0xD4, 0xCA,
+ 0xCB, 0x91, 0xC4, 0xFE, 0xEE, 0x9C, 0x01, 0x50,
+ 0x2E, 0xAC, 0xCC, 0x5F, 0x89, 0xD0, 0x91, 0xA3,
+ 0xD9, 0xF9, 0x4B, 0x8D, 0xDE, 0x6C, 0x60, 0x21,
+ 0x19, 0xB1, 0xD3, 0x4D, 0x75, 0x56, 0x6F, 0xB8,
+ 0x25, 0xA4, 0x92, 0x4F, 0x12, 0xF5, 0x8F, 0xC1,
+ 0x17, 0x4B, 0xB3, 0x34, 0x21, 0x22, 0xAC, 0x52,
+ 0xD2, 0x64, 0xC9, 0x9A, 0x7D, 0xFC, 0xC0, 0x0A,
+ 0x89, 0x34, 0xFF, 0x08, 0xD3, 0x04, 0xDC, 0xFE,
+ 0x7C, 0xB3, 0xB8, 0xFD, 0x85, 0xDD, 0x79, 0x51,
+ 0xA7, 0x89, 0xE8, 0xF1, 0x23, 0xB1, 0xDF, 0xD7,
+ 0x1F, 0x7B, 0xB1, 0x5D, 0x42, 0xF9, 0x61, 0xF8,
+ 0xDC, 0x81, 0x04, 0xF1, 0xCC, 0xFA, 0xD7, 0xED,
+ 0xBF, 0x47, 0xAC, 0xBD, 0xE5, 0xFA, 0xAC, 0xB3,
+ 0x1C, 0xD9, 0xA1, 0xB3, 0x60, 0xEE, 0x9C, 0x8A,
+ 0x36, 0x57, 0xB4, 0x2F, 0xA1, 0xA2, 0xF3, 0xE2,
+ 0x09, 0x9A, 0x6E, 0x43, 0x9B, 0xE5, 0x93, 0xB8,
+ 0x3D, 0x9E, 0x9F, 0xC1, 0xC6, 0x0D, 0x02, 0xEB,
+ 0x4D, 0x38, 0xE9, 0xB4, 0x9F, 0xEA, 0x33, 0x8C,
+ 0x07, 0xD8, 0xB4, 0x71, 0xAD, 0xE5, 0x43, 0xB2,
+ 0xCC, 0x55, 0x93, 0x6A, 0xDB, 0x1E, 0x80, 0xDB,
+ 0xC2, 0xEA, 0x42, 0x8E, 0xFC, 0x86, 0x44, 0xC9,
+ 0x8A, 0xC4, 0xF2, 0x46, 0xA7, 0x39, 0x50, 0x0D,
+ 0x1A, 0xAA, 0x07, 0x04, 0xBE, 0xD4, 0xCE, 0x62,
+ 0x4D, 0x0F, 0x91, 0x7D, 0x29, 0x88, 0x9C, 0x4C,
+ 0xAF, 0xF7, 0xD8, 0x40, 0x93, 0x88, 0xC7, 0x20,
+ 0xD2, 0x17, 0x2A, 0xC4, 0x92, 0x72, 0xD0, 0xC0,
+ 0x4E, 0x56, 0x47, 0xB1, 0x27, 0x02, 0xE6, 0x61,
+ 0x82, 0x5E, 0xC8, 0x2E, 0x90, 0xD2, 0x31, 0x22,
+ 0xE2, 0xA9, 0x4A, 0x91, 0x45, 0x69, 0xB1, 0xA5,
+ 0x0F, 0x66, 0x2C, 0x30, 0xAD, 0x7F, 0x1B, 0x0E,
+ 0x22, 0x17, 0x60, 0x2E, 0x3D, 0x7F, 0x7F, 0x8C,
+ 0x33, 0x51, 0xA0, 0x25, 0xDE, 0xFD, 0x75, 0xBC,
+ 0xEF, 0xE6, 0xE7, 0x20, 0x04, 0x5A, 0xEC, 0x50,
+ 0x21, 0x48, 0x56, 0x98, 0xE2, 0x33, 0x6D, 0x22,
+ 0x5C, 0xC3, 0xFB, 0xFC, 0x6F, 0xB3, 0xA7, 0x8E,
+ 0x6F, 0x67, 0x70, 0x9D, 0xDA, 0x02, 0x01, 0x59,
+ 0x7B, 0x3D, 0x2B, 0x38, 0xCC, 0x0F, 0x44, 0x3D,
+ 0xFB, 0x9A, 0xB3, 0x23, 0x15, 0x50, 0x6E, 0xBF,
+ 0x8B, 0xA1, 0x94, 0x33, 0xE5, 0x7B, 0x88, 0x4E,
+ 0xCB, 0x6D, 0x9F, 0xBF, 0xBC, 0x7A, 0xA8, 0x1E,
+ 0x68, 0x25, 0xED, 0x8E, 0x53, 0x21, 0x72, 0xC5,
+ 0x70, 0xB3, 0xE4, 0xA6, 0xA1, 0x5A, 0x2D, 0xC8,
+ 0x43, 0x9D, 0x60, 0x77, 0x78, 0xE0, 0xC4, 0xAF,
+ 0xC8, 0x29, 0xBA, 0xD0, 0x4D, 0x39, 0x83, 0x51,
+ 0xA7, 0x10, 0x7F, 0x0C, 0x34, 0x0E, 0x6C, 0x75,
+ 0x26, 0xD7, 0xD6, 0xC7, 0x32, 0x53, 0xAF, 0x4E,
+ 0xBE, 0xF2, 0xC2, 0x0F, 0x99, 0x23, 0xB9, 0xE1,
+ 0xC8, 0xB4, 0xBC, 0x5A, 0xC6, 0xCB, 0xEB, 0x4D,
+ 0x28, 0x56, 0x72, 0xFE, 0x1B, 0x2C, 0x5D, 0xE3,
+ 0xBC, 0xC7, 0xA3, 0xC0, 0x7D, 0x27, 0xF0, 0xD0,
+ 0x4F, 0x3F, 0x1F, 0xF7, 0x87, 0x15, 0xF2, 0xEA,
+ 0xD4, 0x03, 0x6D, 0x2F, 0xD4, 0x8E, 0x50, 0x4B,
+ 0x05, 0xBF, 0xF7, 0x8C, 0x67, 0x5A, 0xDC, 0x4D,
+ 0xCD, 0xCF, 0x9D, 0x02, 0xB6, 0xE7, 0xAE, 0x49,
+ 0xD1, 0x7C, 0x00, 0xE7, 0x3B, 0xEA, 0xFB, 0x0D,
+ 0x2A, 0x7B, 0x41, 0x33, 0x66, 0xD0, 0x29, 0x9F,
+ 0xB3, 0x8A, 0x71, 0xB0, 0xE2, 0x76, 0xA9, 0xDB,
+ 0xFD, 0x64, 0x04, 0x69, 0xDF, 0x89, 0x1F, 0x56,
+ 0x86, 0x92, 0xD9, 0xD9, 0xB9, 0xF3, 0x4F, 0xAC,
+ 0xAE, 0x61, 0x48, 0x20, 0xCE, 0x3C, 0x2B, 0x44,
+ 0xAB, 0x42, 0xFA, 0xAB, 0x2E, 0x94, 0x82, 0xC8,
+ 0xD9, 0x97, 0xCF, 0x27, 0xDF, 0xAC, 0xAC, 0xE7,
+ 0xCA, 0xB2, 0x84, 0xAB, 0xF2, 0x5D, 0xDF, 0x56,
+ 0x0C, 0x8C, 0x07, 0x3C, 0x3D, 0xA8, 0xDD, 0xBE,
+ 0xFF, 0x4E, 0x28, 0x0D, 0xB2, 0x2D, 0xE6, 0x9D,
+ 0x44, 0x21, 0xCB, 0xE7, 0x33, 0x63, 0x22, 0x8F,
+ 0x4C, 0xFF, 0xB6, 0x1D, 0x9A, 0x71, 0x3F, 0xB1,
+ 0x29, 0xAE, 0x3A, 0x35, 0xEE, 0x9C, 0x97, 0x68,
+ 0xA7, 0x52, 0x66, 0x01, 0xD8, 0x9A, 0x5D, 0xF4,
+ 0xB3, 0x2F, 0x5C, 0xD4, 0x0E, 0xF9, 0xCF, 0x07,
+ 0xF6, 0x8C, 0xBA, 0xA6, 0x8D, 0x6B, 0xC6, 0x01,
+ 0xC2, 0x69, 0xAE, 0x60, 0x08, 0x1A, 0x0E, 0x3F,
+ 0xAE, 0x60, 0x29, 0xF3, 0x48, 0x0D, 0xE0, 0xD0,
+ 0xAE, 0x52, 0x44, 0xE9, 0x7F, 0x1F, 0x92, 0x5F,
+ 0x71, 0xAD, 0xEC, 0x6B, 0x47, 0x66, 0x92, 0x22,
+ 0x27, 0xAE, 0x6E, 0x25, 0xCD, 0xF3, 0x5F, 0x55,
+ 0x59, 0xBD, 0x73, 0xCE, 0x2B, 0x7E, 0x99, 0x44,
+ 0x56, 0x70, 0xA3, 0xE7, 0x7A, 0x59, 0x75, 0xD8,
+ 0x48, 0x0C, 0x39, 0x2B, 0xD7, 0x53, 0xC6, 0xAD,
+ 0x4A, 0x6F, 0xB4, 0x14, 0x96, 0xDF, 0xF2, 0x4A,
+ 0x0C, 0xA2, 0xD5, 0x29, 0x98, 0x7C, 0x42, 0x87,
+ 0xD9, 0x1F, 0x97, 0x61, 0xD9, 0xBF, 0x99, 0x4F,
+ 0x2C, 0x4C, 0x75, 0xAC, 0xB8, 0x06, 0x75, 0xD6,
+ 0x87, 0x76, 0x7E, 0xE3, 0x23, 0x4B, 0xEA, 0x1A,
+ 0x1A, 0xF4, 0xB7, 0x09, 0xAF, 0x53, 0xEB, 0xA6,
+ 0x39, 0x10, 0xFE, 0xD4, 0xEB, 0x1B, 0xAE, 0x38,
+ 0x31, 0x33, 0xBA, 0x68, 0xEE, 0xC7, 0x65, 0x76,
+ 0xFB, 0x49, 0x77, 0xD4, 0x19, 0xC4, 0xE6, 0xA7,
+ 0x05, 0xFE, 0x2A, 0xDA, 0x39, 0x99, 0x1A, 0x92,
+ 0xD2, 0xF0, 0x61, 0x97, 0xF6, 0x06, 0x6C, 0x88,
+ 0x7B, 0x6F, 0x60, 0xE6, 0x70, 0x08, 0xF0, 0xB4,
+ 0x6B, 0x39, 0x6F, 0x05, 0x41, 0x81, 0xF9, 0xBE,
+ 0x7A, 0x51, 0xC4, 0x75, 0xB0, 0x6A, 0x89, 0xA0,
+ 0xA6, 0x9A, 0x5B, 0xEE, 0x7D, 0x78, 0x17, 0x5F,
+ 0x9F, 0x3B, 0x7D, 0xDD, 0x8A, 0x9E, 0xAA, 0x1A,
+ 0xDA, 0x49, 0x08, 0xE9, 0xFD, 0x91, 0xA6, 0xFA,
+ 0xCE, 0xCF, 0x67, 0xDF, 0x0F, 0xC9, 0xD6, 0x38,
+ 0xD9, 0xD5, 0xD1, 0xC0, 0x76, 0x59, 0x42, 0x53,
+ 0xBF, 0x48, 0xE9, 0x11, 0x74, 0xC7, 0x11, 0xD8,
+ 0xE7, 0x8E, 0xD3, 0xC8, 0x25, 0xA1, 0x26, 0x50,
+ 0xBB, 0xB4, 0x35, 0xAF, 0xAF, 0x06, 0x23, 0x69,
+ 0x3E, 0x30, 0xFD, 0x7B, 0x34, 0x83, 0x07, 0xD0,
+ 0xF0, 0x0F, 0x6C, 0x9A, 0x13, 0x5D, 0xC2, 0x7B,
+ 0xDF, 0x6F, 0xDD, 0x8E, 0xF4, 0x30, 0x82, 0x05,
+ 0x41, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7,
+ 0x0D, 0x01, 0x07, 0x01, 0xA0, 0x82, 0x05, 0x32,
+ 0x04, 0x82, 0x05, 0x2E, 0x30, 0x82, 0x05, 0x2A,
+ 0x30, 0x82, 0x05, 0x26, 0x06, 0x0B, 0x2A, 0x86,
+ 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01,
+ 0x02, 0xA0, 0x82, 0x04, 0xEE, 0x30, 0x82, 0x04,
+ 0xEA, 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48,
+ 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30,
+ 0x0E, 0x04, 0x08, 0x74, 0xC0, 0x84, 0x8F, 0xC7,
+ 0x74, 0x5E, 0x21, 0x02, 0x02, 0x08, 0x00, 0x04,
+ 0x82, 0x04, 0xC8, 0x1E, 0xF4, 0xE9, 0x07, 0x27,
+ 0x9E, 0x5A, 0xC9, 0x39, 0x1D, 0x37, 0x2C, 0x06,
+ 0x4B, 0x57, 0xEA, 0xC5, 0x42, 0x9A, 0x60, 0xD5,
+ 0x42, 0xB2, 0x34, 0x2D, 0xD3, 0x88, 0x7C, 0x78,
+ 0x87, 0xB6, 0xE9, 0x42, 0x44, 0x1F, 0x67, 0x32,
+ 0x92, 0x54, 0x22, 0xDA, 0xB2, 0x43, 0xE7, 0x40,
+ 0xBE, 0x1F, 0xAF, 0x3A, 0xCD, 0x2A, 0x9F, 0xD7,
+ 0x44, 0x5B, 0x37, 0x69, 0x85, 0xDF, 0xEB, 0x2A,
+ 0xB9, 0xE2, 0x92, 0x3B, 0xEA, 0xD5, 0x42, 0x53,
+ 0x95, 0x4A, 0xB0, 0x1B, 0xA5, 0xEF, 0xA6, 0x0D,
+ 0x29, 0xF4, 0x33, 0xFE, 0xD7, 0x49, 0x04, 0x1E,
+ 0x8C, 0xAD, 0x63, 0x1E, 0x79, 0x63, 0x74, 0x0C,
+ 0xE5, 0x5E, 0xA2, 0x2C, 0xBE, 0xB8, 0x90, 0xCE,
+ 0x06, 0x25, 0xBF, 0xD1, 0x5A, 0x50, 0xCF, 0x3B,
+ 0x52, 0xE2, 0xA7, 0xFF, 0x19, 0x02, 0xCF, 0xD0,
+ 0x9B, 0xD9, 0xF7, 0x28, 0x07, 0x38, 0x1F, 0xF2,
+ 0xAF, 0x44, 0x91, 0x3F, 0x0F, 0xB6, 0x6E, 0x8C,
+ 0xC0, 0x32, 0x92, 0xC0, 0xCD, 0x25, 0x98, 0x67,
+ 0xF1, 0x47, 0x52, 0x50, 0xF0, 0xA3, 0x7B, 0xE6,
+ 0x74, 0xDC, 0x72, 0x28, 0xC8, 0xAB, 0xB3, 0x31,
+ 0x7D, 0xA3, 0xF7, 0xC7, 0xD1, 0xE6, 0x99, 0xB4,
+ 0xB6, 0x5A, 0x3A, 0x4D, 0x83, 0x4F, 0xB8, 0xB5,
+ 0x86, 0xF8, 0x37, 0x7F, 0xA0, 0x16, 0x2F, 0x3C,
+ 0x62, 0x7A, 0xD4, 0x3A, 0xEB, 0xC2, 0xE8, 0x03,
+ 0x49, 0x17, 0x9E, 0xFB, 0xD7, 0xAF, 0x91, 0x32,
+ 0xFD, 0xEA, 0x4F, 0x64, 0xC6, 0x6E, 0x02, 0xEA,
+ 0xC4, 0xC8, 0x1F, 0x16, 0xC5, 0x4C, 0xFB, 0xC5,
+ 0x42, 0xF5, 0x85, 0x05, 0x92, 0x59, 0x4B, 0x31,
+ 0xE5, 0xE9, 0x69, 0xE7, 0x02, 0x98, 0x33, 0xBA,
+ 0x4C, 0x17, 0x09, 0xEF, 0x89, 0x20, 0xFA, 0x83,
+ 0x9F, 0xAE, 0x0E, 0x1B, 0x7D, 0x98, 0xB9, 0xF2,
+ 0x3C, 0x0F, 0xB7, 0x1C, 0x72, 0xDF, 0x17, 0x84,
+ 0x7F, 0x0A, 0xFD, 0x12, 0x3C, 0x6F, 0x68, 0x5D,
+ 0x45, 0xEB, 0xB8, 0xD6, 0x24, 0x65, 0x42, 0x75,
+ 0x5C, 0xC2, 0xF3, 0x3A, 0x6A, 0x4E, 0x51, 0x34,
+ 0x1B, 0xB6, 0x81, 0xB2, 0x8A, 0xEF, 0x28, 0xA4,
+ 0xC5, 0x88, 0x9A, 0x97, 0xE0, 0xEF, 0x31, 0x12,
+ 0x01, 0x7E, 0x1B, 0x43, 0x0F, 0x27, 0x80, 0x87,
+ 0x98, 0xC5, 0xD5, 0x83, 0xCB, 0x4B, 0xB7, 0x01,
+ 0x79, 0x60, 0xA1, 0x1A, 0x03, 0x05, 0xC6, 0x36,
+ 0x04, 0x31, 0x3C, 0x06, 0xDB, 0x08, 0xA8, 0xDA,
+ 0x8E, 0x32, 0x19, 0x91, 0xF1, 0x0D, 0x61, 0x6F,
+ 0xE4, 0xB2, 0x79, 0x8A, 0xDE, 0xF4, 0xF7, 0xFB,
+ 0x2C, 0x23, 0x5B, 0xD9, 0x64, 0x2F, 0xB7, 0xB3,
+ 0x8B, 0xCA, 0xB8, 0x8C, 0x1D, 0x3B, 0x49, 0x05,
+ 0x38, 0xA1, 0xE5, 0x8C, 0x1A, 0xDC, 0xA5, 0x61,
+ 0xFE, 0xF4, 0x2B, 0xDC, 0x77, 0x28, 0xF6, 0x19,
+ 0xE7, 0xB7, 0x8F, 0x4D, 0x27, 0x2D, 0xED, 0x8A,
+ 0x3F, 0x3D, 0xDC, 0x9F, 0xD1, 0x30, 0xFF, 0xD6,
+ 0xC3, 0xBE, 0x41, 0x25, 0xE3, 0xA5, 0x9B, 0x73,
+ 0xDF, 0x6A, 0xD9, 0xF9, 0x70, 0x84, 0x02, 0x4C,
+ 0x35, 0xD4, 0x3E, 0x05, 0x76, 0x3A, 0xDC, 0x6D,
+ 0x5A, 0x81, 0xB3, 0x94, 0xF7, 0x22, 0xF7, 0xDC,
+ 0xC1, 0x43, 0x31, 0x57, 0x5B, 0x42, 0x9A, 0x0B,
+ 0xF4, 0x95, 0x30, 0xA9, 0xBB, 0xD8, 0x06, 0xFB,
+ 0x1D, 0x6F, 0x9B, 0xC3, 0xBB, 0xF3, 0xBF, 0xFB,
+ 0xB4, 0x9F, 0x35, 0x64, 0x0A, 0x69, 0xB7, 0xD1,
+ 0x3E, 0xCA, 0x78, 0x07, 0x04, 0x03, 0x79, 0xD4,
+ 0xF3, 0xA8, 0xEC, 0x18, 0xDB, 0x03, 0x5E, 0x47,
+ 0xD7, 0xD0, 0x56, 0x2C, 0x74, 0x94, 0x86, 0x04,
+ 0x46, 0xB8, 0xD4, 0x35, 0x0A, 0x7B, 0xE6, 0x78,
+ 0xC4, 0x43, 0x3C, 0x56, 0xCC, 0x37, 0x8B, 0xFD,
+ 0xE8, 0xF4, 0x57, 0xEA, 0xAE, 0xCF, 0x36, 0x97,
+ 0x12, 0xAC, 0x39, 0xCF, 0x7C, 0xEF, 0x22, 0x67,
+ 0x01, 0xEC, 0xD8, 0x09, 0x49, 0x4E, 0xE3, 0x74,
+ 0xDD, 0x39, 0xE1, 0x39, 0xD7, 0x0C, 0x5F, 0x1B,
+ 0xCE, 0x69, 0xBC, 0x72, 0x44, 0x87, 0x64, 0x1C,
+ 0x08, 0x05, 0x93, 0x69, 0x6D, 0x7F, 0x90, 0x0A,
+ 0x2C, 0xCB, 0x8A, 0xBB, 0x7F, 0xE3, 0xE0, 0x80,
+ 0x31, 0xD0, 0x0A, 0x3A, 0x95, 0xFF, 0xF7, 0xB4,
+ 0x36, 0x38, 0x93, 0xE0, 0x0C, 0x11, 0x37, 0x12,
+ 0x06, 0xF6, 0xAD, 0xE9, 0xB1, 0x7A, 0x00, 0xF5,
+ 0xD2, 0x32, 0x6B, 0xD0, 0x27, 0xA5, 0x1B, 0x3D,
+ 0xE8, 0xDB, 0xCC, 0xA9, 0x1F, 0x1F, 0xB1, 0x99,
+ 0x3D, 0x7C, 0xB7, 0xCA, 0xDA, 0x27, 0x2C, 0x64,
+ 0x1C, 0x49, 0xB6, 0x87, 0x44, 0x06, 0x94, 0x9D,
+ 0xBC, 0x6B, 0x20, 0xA2, 0x68, 0x15, 0x1F, 0xE2,
+ 0xF2, 0xAD, 0x6D, 0x23, 0x2E, 0x2B, 0x74, 0xE2,
+ 0x5D, 0xE4, 0xB0, 0xC7, 0x84, 0xCB, 0x64, 0xBF,
+ 0xE0, 0xA8, 0x18, 0x83, 0xB4, 0xC9, 0xD9, 0x73,
+ 0xA8, 0xE6, 0xA9, 0x36, 0xD5, 0x63, 0x1E, 0x2C,
+ 0x2A, 0x55, 0x09, 0x77, 0x5E, 0xB3, 0x4B, 0xEA,
+ 0xB5, 0xD0, 0x14, 0x5F, 0xEB, 0x50, 0x7B, 0xAA,
+ 0xEF, 0x94, 0xBA, 0x2B, 0xD7, 0x8A, 0x07, 0xF1,
+ 0xF9, 0x5E, 0x12, 0x12, 0x21, 0x52, 0xE5, 0x0A,
+ 0x3E, 0xC0, 0xBC, 0x5D, 0x4C, 0xE2, 0x12, 0x7C,
+ 0x39, 0xF9, 0x16, 0x9D, 0xBD, 0x96, 0x83, 0x3B,
+ 0x7F, 0x3D, 0x6A, 0xEC, 0xF1, 0x25, 0xD2, 0xB0,
+ 0xB0, 0xEB, 0x20, 0x06, 0x07, 0xD6, 0xD9, 0x4C,
+ 0x07, 0x9A, 0x82, 0xC1, 0xFC, 0xF7, 0x66, 0x15,
+ 0xBD, 0x62, 0x65, 0xD8, 0x6C, 0xF6, 0x33, 0x7B,
+ 0x5A, 0x28, 0xEC, 0x90, 0xA1, 0x26, 0x9F, 0xC3,
+ 0x28, 0x4A, 0x64, 0x50, 0x5F, 0xCA, 0xE2, 0x6D,
+ 0xB8, 0x0F, 0xE2, 0x94, 0xB5, 0x8E, 0x1F, 0x8A,
+ 0x8F, 0x6B, 0xA6, 0x86, 0x1F, 0xEE, 0xDC, 0x24,
+ 0xB4, 0xB8, 0x25, 0xEC, 0x28, 0x2D, 0xF9, 0xCB,
+ 0x7D, 0x38, 0xFF, 0xC7, 0x74, 0x2E, 0xD3, 0x10,
+ 0xEC, 0x03, 0x31, 0xEE, 0x83, 0xE7, 0xA4, 0xF7,
+ 0xBA, 0x28, 0x21, 0xE0, 0x7F, 0xB4, 0xB7, 0xE1,
+ 0x7A, 0xF9, 0x2B, 0xB0, 0x2C, 0x3B, 0x80, 0x5F,
+ 0xE0, 0x5D, 0xB2, 0x7E, 0x59, 0xFF, 0x59, 0x07,
+ 0x58, 0x42, 0x57, 0xEE, 0x44, 0xF1, 0xB1, 0xAD,
+ 0xBA, 0xDE, 0xCB, 0x1D, 0x8A, 0x36, 0x67, 0xE8,
+ 0x45, 0xFF, 0x07, 0x8D, 0xEE, 0xA4, 0x51, 0x9C,
+ 0x4C, 0x83, 0x5D, 0x2E, 0x2F, 0xE1, 0x5B, 0x75,
+ 0xE8, 0x29, 0xCD, 0x0B, 0x07, 0x62, 0xE0, 0xC3,
+ 0x0D, 0x1D, 0xEA, 0xCF, 0xF0, 0x8A, 0x65, 0x27,
+ 0x70, 0x42, 0x9F, 0x26, 0x00, 0x15, 0x70, 0xC5,
+ 0x4A, 0xF6, 0x25, 0xD0, 0x40, 0x72, 0xE9, 0xC1,
+ 0x73, 0xFD, 0x48, 0x94, 0xA3, 0x8D, 0x66, 0x63,
+ 0x96, 0x4F, 0xF7, 0xEE, 0xFB, 0x4C, 0xC7, 0xB8,
+ 0x6B, 0xE9, 0x90, 0xE1, 0x2A, 0x66, 0x80, 0x99,
+ 0x3B, 0xB0, 0x1A, 0x6C, 0xF9, 0x0E, 0x72, 0xDA,
+ 0x8E, 0x4F, 0x46, 0xC2, 0x6A, 0x4B, 0x7A, 0x16,
+ 0xE5, 0x26, 0x0B, 0x5C, 0xD4, 0x47, 0x34, 0xE5,
+ 0x37, 0xBE, 0x68, 0x6C, 0xDA, 0xD3, 0x9B, 0x6F,
+ 0xAE, 0x51, 0x9C, 0x99, 0x0A, 0x5B, 0xF8, 0x37,
+ 0xBC, 0xDE, 0xFC, 0x93, 0xC5, 0xE7, 0x0F, 0xEF,
+ 0x0B, 0xA6, 0x07, 0xC2, 0xA6, 0xE6, 0xDA, 0x2D,
+ 0x1B, 0x49, 0xC9, 0xDE, 0x6B, 0x27, 0xDC, 0x00,
+ 0xEF, 0x23, 0x87, 0x0E, 0xEB, 0xD1, 0x48, 0x7D,
+ 0xB4, 0xF2, 0x58, 0xC6, 0x3C, 0xE2, 0x89, 0xBA,
+ 0xB0, 0x05, 0xAC, 0x94, 0x41, 0x9A, 0xA8, 0xFF,
+ 0x3E, 0xBC, 0x3A, 0x52, 0x9C, 0xF9, 0x7F, 0x07,
+ 0x8B, 0xB0, 0x2C, 0x71, 0x83, 0x7B, 0xCF, 0x2E,
+ 0x7F, 0x7C, 0x96, 0x65, 0xD9, 0x08, 0x17, 0xEC,
+ 0xFA, 0xDE, 0x4E, 0x40, 0x12, 0x26, 0x70, 0x71,
+ 0x65, 0xA5, 0xDC, 0x98, 0x47, 0xA3, 0xFC, 0xE0,
+ 0x9A, 0x16, 0xED, 0x45, 0x56, 0x72, 0x50, 0x05,
+ 0x28, 0x2C, 0x99, 0xEC, 0x20, 0x2E, 0x40, 0xC0,
+ 0x26, 0x69, 0xCD, 0x49, 0x45, 0x17, 0xA4, 0xA3,
+ 0x42, 0x0D, 0x14, 0x65, 0x87, 0x33, 0x8C, 0x92,
+ 0xC5, 0xC4, 0x61, 0xFD, 0xE8, 0x68, 0x56, 0x20,
+ 0x57, 0xF5, 0x8E, 0x5F, 0xCF, 0x7E, 0x97, 0xF6,
+ 0x49, 0x97, 0x0A, 0xFE, 0xD3, 0x60, 0x1A, 0x5B,
+ 0x0C, 0x75, 0xDD, 0x8E, 0x31, 0x78, 0x29, 0xA6,
+ 0xB1, 0x4D, 0xAA, 0xDF, 0x8A, 0xD1, 0xE6, 0x91,
+ 0xE3, 0x32, 0x3F, 0xEC, 0x8A, 0x1F, 0x0E, 0x35,
+ 0x07, 0x6E, 0x4B, 0x83, 0x3B, 0xE5, 0x67, 0x34,
+ 0x1F, 0x0C, 0x81, 0xD8, 0xD5, 0x25, 0x68, 0xE5,
+ 0x28, 0x1B, 0x5C, 0x81, 0x3E, 0xE3, 0x5C, 0xB4,
+ 0xB6, 0xBD, 0x62, 0x6A, 0x70, 0x33, 0xC2, 0xC5,
+ 0x75, 0x27, 0xF4, 0x30, 0xE1, 0x1D, 0xC1, 0x4C,
+ 0xC5, 0x02, 0x12, 0x46, 0xAC, 0xEC, 0xF9, 0xE8,
+ 0xE7, 0x58, 0x24, 0x11, 0xB1, 0xF3, 0xB7, 0x8C,
+ 0x3C, 0xA4, 0x0A, 0x94, 0xA6, 0x7C, 0x68, 0x54,
+ 0x5B, 0xB9, 0x4D, 0x57, 0x9C, 0xE7, 0x28, 0x09,
+ 0x6B, 0x89, 0x26, 0x5D, 0xE7, 0x50, 0xA9, 0x95,
+ 0x90, 0x91, 0x8E, 0x00, 0x59, 0xF8, 0x3A, 0x70,
+ 0xAF, 0x48, 0x2E, 0xE8, 0xC4, 0x34, 0x8C, 0xF4,
+ 0x5F, 0x7F, 0xCB, 0x07, 0xAA, 0xF0, 0xD9, 0xFB,
+ 0x5C, 0x32, 0x90, 0x22, 0x1A, 0xD2, 0x1A, 0xCF,
+ 0x92, 0x06, 0x02, 0xCF, 0x10, 0x18, 0x7B, 0x93,
+ 0xCC, 0x07, 0x4A, 0x31, 0x25, 0x30, 0x23, 0x06,
+ 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01,
+ 0x09, 0x15, 0x31, 0x16, 0x04, 0x14, 0xD1, 0xDE,
+ 0x23, 0x16, 0x9F, 0x6E, 0xF4, 0x42, 0x21, 0x23,
+ 0xE1, 0x11, 0xAA, 0xC8, 0x7C, 0x60, 0x4A, 0x78,
+ 0x9D, 0x24, 0x30, 0x31, 0x30, 0x21, 0x30, 0x09,
+ 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05,
+ 0x00, 0x04, 0x14, 0xD6, 0x4A, 0xBB, 0x75, 0xB1,
+ 0xF9, 0x9E, 0xD3, 0x58, 0x6D, 0xD1, 0x74, 0x9F,
+ 0x00, 0x8A, 0xF2, 0xC8, 0xAA, 0x52, 0x4D, 0x04,
+ 0x08, 0x77, 0x46, 0xE7, 0xBA, 0x25, 0x4B, 0xDA,
+ 0x41, 0x02, 0x02, 0x08, 0x00]
+};
+
+const WPA_EAP_CLIENT_LIST = [
+ {
+ ssid: 'WPA-EAP-TLS',
+ keyManagement: 'WPA-EAP',
+ eap: 'TLS',
+ identity: EAP_USERNAME,
+ serverCertificate: CLIENT_PKCS12_CERT.nickname,
+ userCertificate: CLIENT_PKCS12_CERT.nickname
+ }
+];
+
+/**
+ * Convert the given MozWifiNetwork object array to testAssociate chain.
+ *
+ * @param aNetworks
+ * An array of MozWifiNetwork which we want to convert.
+ *
+ * @return A promise chain which "then"s testAssociate accordingly.
+ */
+function convertToTestAssociateChain(aNetworks) {
+ let chain = Promise.resolve();
+
+ aNetworks.forEach(function (aNetwork) {
+ network = new window.MozWifiNetwork(aNetwork);
+ chain = chain.then(() => gTestSuite.testAssociate(network));
+ });
+
+ return chain;
+}
+
+gTestSuite.doTestWithCertificate(
+ new Blob([new Uint8Array(CLIENT_PKCS12_CERT.content)]),
+ CLIENT_PKCS12_CERT.password,
+ CLIENT_PKCS12_CERT.nickname,
+ CLIENT_PKCS12_CERT.usage,
+ function() {
+ return gTestSuite.ensureWifiEnabled(true)
+ // Load required server files.
+ .then(() => gTestSuite.writeFile(SERVER_EAP_USER_CONF.path, SERVER_EAP_USER_CONF.content))
+ .then(() => gTestSuite.writeFile(CA_CERT.path, CA_CERT.content))
+ .then(() => gTestSuite.writeFile(SERVER_CERT.path, SERVER_CERT.content))
+ .then(() => gTestSuite.writeFile(SERVER_KEY.path, SERVER_KEY.content))
+ // Start AP.
+ .then(() => gTestSuite.startHostapds(WPA_EAP_AP_LIST))
+ // Scan test.
+ .then(() => gTestSuite.testWifiScanWithRetry(SCAN_RETRY_CNT, WPA_EAP_AP_LIST))
+ // Associate test.
+ .then(() => convertToTestAssociateChain(WPA_EAP_CLIENT_LIST))
+ // Tear down.
+ .then(gTestSuite.killAllHostapd)
+});
diff --git a/dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_TTLS.js b/dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_TTLS.js
new file mode 100644
index 000000000..06e052909
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_associate_WPA_EAP_TTLS.js
@@ -0,0 +1,623 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const SCAN_RETRY_CNT = 5;
+
+const EAP_USERNAME = 'username';
+const EAP_PASSWORD = 'password';
+
+const SERVER_EAP_USER_CONF = {
+ path: HOSTAPD_CONFIG_PATH + 'hostapd.eap_user',
+ content: '* PEAP,TTLS,TLS\n' +
+ '"' + EAP_USERNAME + '" MSCHAPV2,TTLS-MSCHAPV2 "' + EAP_PASSWORD + '" [2]\n'
+};
+const CA_CERT = {
+ path: HOSTAPD_CONFIG_PATH + 'ca.pem',
+ content: '-----BEGIN CERTIFICATE-----\n' +
+ 'MIIDsTCCApmgAwIBAgIJAKxTf+8X8qngMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV\n' +
+ 'BAYTAlRXMRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQKDAhjaHVja2xlZTER\n' +
+ 'MA8GA1UEAwwIY2h1Y2tsZWUxJDAiBgkqhkiG9w0BCQEWFWNodWNrbGkwNzA2QGdt\n' +
+ 'YWlsLmNvbTAgFw0xNDEyMjQxMTI4NTBaGA8yMjg4MTAwNzExMjg1MFowbjELMAkG\n' +
+ 'A1UEBhMCVFcxEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAoMCGNodWNrbGVl\n' +
+ 'MREwDwYDVQQDDAhjaHVja2xlZTEkMCIGCSqGSIb3DQEJARYVY2h1Y2tsaTA3MDZA\n' +
+ 'Z21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo3c2yFxY\n' +
+ 'o6gGg0I83jy00ME+MAfzCd+4ShL45ZLqysQP93jRBfPzU9ZuZ29ysVwgWIdqkZao\n' +
+ 'XTuV/NAW2GMGd8W1jQJ3J81fjb9wvhlny3rrACwvUn1N1S1BnM+BAAiDLGxEmvAQ\n' +
+ 'onp2aaa6HsHsYS8ONX+d2Qh4LEA4vupeSGAqJychCZv/l+aq/ErFZhFYB3CPUQEt\n' +
+ 'cClO24ucsIYP95lA0zhscnmAj06qplFD4Bv6IVrdDqujy1zNwCQwsJq/8OQdaTN/\n' +
+ 'h3y9pWvNKMBMM2niOUAjtuNpqsSK/lTS1WAT3PdtVECX9fYBi0Bg+HM92xs/6gt6\n' +
+ 'kh9jPV8keXHvSwIDAQABo1AwTjAdBgNVHQ4EFgQU7hBqhuG04xeCzrQ3ngx18ZJ3\n' +
+ 'lUswHwYDVR0jBBgwFoAU7hBqhuG04xeCzrQ3ngx18ZJ3lUswDAYDVR0TBAUwAwEB\n' +
+ '/zANBgkqhkiG9w0BAQsFAAOCAQEAFYX2iy680GAnBTttk0gyX6gk+8pYr3D22k/G\n' +
+ '6rvcjefzS7ELQPRKr6mfmwXq3mMf/4jiS2zI5zmXsestPYzHYxf2viQ6t7vr9XiJ\n' +
+ '3WfFjNw4ERlRisAvg0aqqTNNQq5v2VME4sdFZagy217f73C7azwCHl0bqOLH05rl\n' +
+ '8RubOxiHEj7ZybJqnRciK/bht4D+rZkwf4bBBmoloqH7xT0+rFQclpYXDGGjNUQB\n' +
+ 'LcHLF10xcr7g3ZVVu82fe6+d85gIGOIMR9+TKhdw6gO3CNcnDAj6gxksghgtcxmh\n' +
+ 'OzOggCn7nlIwImtsg2sZkpWB4lEi9hdv4lkNuyFjOL3bnuc+NA==\n' +
+ '-----END CERTIFICATE-----\n'
+};
+
+const SERVER_CERT = {
+ path: HOSTAPD_CONFIG_PATH + 'server.pem',
+ content: '-----BEGIN CERTIFICATE-----\n' +
+ 'MIID1DCCArygAwIBAgIBADANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJUVzET\n' +
+ 'MBEGA1UECAwKU29tZS1TdGF0ZTERMA8GA1UECgwIY2h1Y2tsZWUxETAPBgNVBAMM\n' +
+ 'CGNodWNrbGVlMSQwIgYJKoZIhvcNAQkBFhVjaHVja2xpMDcwNkBnbWFpbC5jb20w\n' +
+ 'IBcNMTQxMjI0MTEyOTQ5WhgPMjI4ODEwMDcxMTI5NDlaMG4xCzAJBgNVBAYTAlRX\n' +
+ 'MRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQKDAhjaHVja2xlZTERMA8GA1UE\n' +
+ 'AwwIY2h1Y2tsZWUxJDAiBgkqhkiG9w0BCQEWFWNodWNrbGkwNzA2QGdtYWlsLmNv\n' +
+ 'bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMdhQmKilTJbWZRxTiSV\n' +
+ 'rqIU+LYW1RKghx5o+0JpNRJVLuz5kBMaNskbbfUSNuHbEq0QA9BDKAZWIc4LSotk\n' +
+ 'lCo8TbcO9CJvJPQGGjGdHcohWX5vy6BE/OVE46CUteMFyZF6F8R2fNUww08iR/u1\n' +
+ 'YZebL5pWO1j43sPpAzEy6Tij2ACPt6EZcFaZG3SF2mVJWkCQnBqrojP65WUvZQqp\n' +
+ 'seUhW2YAS8Nu0Yrohgxz6VYk+cNDuDZVGs6qWRStZzJfYrfc76DtkHof5B14M+xp\n' +
+ 'XJaBLxN+whvnYkDTfinaCxnW1O7eXUltr87fLc5zmeBkgwaiaQuIdcfZm7vDUiz8\n' +
+ 'vnUCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBH\n' +
+ 'ZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFKK4f9/YavTHOfEiAB83Deac\n' +
+ '6gT5MB8GA1UdIwQYMBaAFO4QaobhtOMXgs60N54MdfGSd5VLMA0GCSqGSIb3DQEB\n' +
+ 'CwUAA4IBAQBWnO9o9KSJIqjoz5Nwll63ULOdcvgGdOeJIw1fcKQ817Rsp+TVcjcH\n' +
+ 'IrIADsT/QZGXRO/l6p1750e2iFtJEo1hsRaxtA1wWn2I9HO3+av2spQhr3jpYGPf\n' +
+ 'zpsMTp4RNYV7Q8+q1kZIz9PY4V1T0p6lveK8+fUj2hSLnxSj0QiGSJJtnEC3w4Rv\n' +
+ 'C9T6oUwIeToULmi+8FXQFdEqwKRU98DPq3eLzN28ZxUgoPE1C8+42D2UW8uyp/Gm\n' +
+ 'tGOa/k7nzkCdVqZI7lX7f0AjEvQgjtAMQ/k7Mhxx7TzW2HO+1YPMoKji6Z4WkNwt\n' +
+ 'JEj9ZUBSNt8B26UksJMBDkcvSegF3a7o\n' +
+ '-----END CERTIFICATE-----\n'
+};
+
+const SERVER_KEY = {
+ path: HOSTAPD_CONFIG_PATH + 'server.key',
+ content: '-----BEGIN RSA PRIVATE KEY-----\n' +
+ 'MIIEpAIBAAKCAQEAx2FCYqKVMltZlHFOJJWuohT4thbVEqCHHmj7Qmk1ElUu7PmQ\n' +
+ 'Exo2yRtt9RI24dsSrRAD0EMoBlYhzgtKi2SUKjxNtw70Im8k9AYaMZ0dyiFZfm/L\n' +
+ 'oET85UTjoJS14wXJkXoXxHZ81TDDTyJH+7Vhl5svmlY7WPjew+kDMTLpOKPYAI+3\n' +
+ 'oRlwVpkbdIXaZUlaQJCcGquiM/rlZS9lCqmx5SFbZgBLw27RiuiGDHPpViT5w0O4\n' +
+ 'NlUazqpZFK1nMl9it9zvoO2Qeh/kHXgz7GlcloEvE37CG+diQNN+KdoLGdbU7t5d\n' +
+ 'SW2vzt8tznOZ4GSDBqJpC4h1x9mbu8NSLPy+dQIDAQABAoIBAASG4Mr8hgaurEoC\n' +
+ 'iJOsElr7vunjetMBcg/uskW/vcS8ymP3Bp5oafYG+WgnEbfvEW18f5mq7K24JuxW\n' +
+ 'tUqU7ghHdjxByqk9fMlNmiqmNpbwSufkAeuRpWxPNBvhRH/zEbCL5R5A0nTEtqqF\n' +
+ 'TL0aUSzwCRSoAJD0lZo9ICVt0n3GsDyM9rqQg/uZmh1qsRdwPsRuYORND9g48rKq\n' +
+ '6WN9leskSxhhsYE2D9ocOFd9bNt8Zxejh9ppVSnG/KsIdt18iBzcabatgAQ046fb\n' +
+ 'Z3vprcZJLg93Sg2gSuVqlSTs3M2W8VQnm22/EBMb1y0M48MSRCgnbPLG/CcCLLfF\n' +
+ 'LwxCOgECgYEA/eYt67xyJ6JeAdxdwOZuT1WWGbFpLiG9+2OgiHumyRQ5969XMTWo\n' +
+ 'fIhMKchDdjoy9RR236\/\/EFCs7UEyB7+a7ODRzNiK2zCD8Smjp+21fUPSthEeQesk\n' +
+ 'eiMYICIu5Ay35x9sxIX+XOUVvRhPOGcD29GVeRnKh1inTHOz2dje8LkCgYEAyQeY\n' +
+ 'STi9jjCEcHkM1E/UeDiLfHHepLXi8wS41JNRHl5Jacp7XB5djAjKu/jf367/VpFy\n' +
+ 'GDDMetE7n8eWkrnAvovxOwZ000YDMtL1sUYSjL+XtBS5s6VY1p4qaSAY9nUUGrJh\n' +
+ 'JvtvsuI7SKTtL+60vjBOH7zDnvOdBgAp0utLhZ0CgYEAuLzzqrPKB8afShFSchn4\n' +
+ 'J2dpuLYahsNsXW7HDqeR2nsKFosRETAusLXnXPtnAq4kB6jlOarwFqnsuRCX24Vx\n' +
+ 'r2uBm9/vYL7zMdUPTA+s30ErHuhjsKjsOKYyVqcooSwT32pBFNk+E89nutfmRG7I\n' +
+ 'IvhjHuNCNqqtx/Xj5d1jkZkCgYBQicppC2Jl5OoqZVTOem0U/RJk+PnJ41TZJ7sk\n' +
+ '7yBAmmWvDH\/\/l+rCf4M5a6vFYcbKV9rt9h711X2dtciNX/3oWQh8LUoAmrwNUJc+\n' +
+ 'PmSQHvIYI3WCk2vUD+nN1B4sHxu+1lg11eYaNKiroeeknG2tBI1ICcgVlmQCU25u\n' +
+ 'IfZPwQKBgQCdO6QHhPLtcHUDNFA6FQ1jKL1iEd7G0JLVRz4Xkpkn1Vrr5MD6JFDa\n' +
+ '5ccabADyl0lpFqDIVJQIzLku2hOD2i9aBNCY0pL391HeOS7CkZX+TdOY1tquoBq5\n' +
+ 'MnmixZjDCVd2VcrVyTA6ntOBoharKFW0rH1PqU+qu7dZF7CBPbAdEw==\n' +
+ '-----END RSA PRIVATE KEY-----\n'
+};
+
+const WPA_EAP_AP_LIST = [
+ {
+ ssid: 'WPA-EAP-TTLS',
+ ieee8021x: 1,
+ eapol_version: 1,
+ eap_server: 1,
+ eapol_key_index_workaround: 0,
+ eap_user_file: SERVER_EAP_USER_CONF.path,
+ ca_cert: CA_CERT.path,
+ server_cert: SERVER_CERT.path,
+ private_key: SERVER_KEY.path,
+ wpa: 3,
+ wpa_key_mgmt: 'WPA-EAP'
+ }
+];
+
+const CLIENT_PKCS12_CERT = {
+ nickname: 'client',
+ password: 'password',
+ usage: ['UserCert', 'ServerCert'],
+ content: [0x30, 0x82, 0x0E, 0x01, 0x02, 0x01, 0x03, 0x30,
+ 0x82, 0x0D, 0xC7, 0x06, 0x09, 0x2A, 0x86, 0x48,
+ 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01, 0xA0, 0x82,
+ 0x0D, 0xB8, 0x04, 0x82, 0x0D, 0xB4, 0x30, 0x82,
+ 0x0D, 0xB0, 0x30, 0x82, 0x08, 0x67, 0x06, 0x09,
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07,
+ 0x06, 0xA0, 0x82, 0x08, 0x58, 0x30, 0x82, 0x08,
+ 0x54, 0x02, 0x01, 0x00, 0x30, 0x82, 0x08, 0x4D,
+ 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D,
+ 0x01, 0x07, 0x01, 0x30, 0x1C, 0x06, 0x0A, 0x2A,
+ 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01,
+ 0x06, 0x30, 0x0E, 0x04, 0x08, 0x67, 0x7A, 0xF3,
+ 0x61, 0xBE, 0xE0, 0x51, 0xC1, 0x02, 0x02, 0x08,
+ 0x00, 0x80, 0x82, 0x08, 0x20, 0xFC, 0x6A, 0x79,
+ 0xA1, 0x6C, 0xAF, 0xBE, 0xEE, 0x62, 0x45, 0x33,
+ 0xB8, 0x48, 0xE1, 0x68, 0xA1, 0x15, 0x11, 0x4B,
+ 0x95, 0xCB, 0x77, 0xC0, 0x5D, 0xA2, 0xCB, 0xDB,
+ 0xD1, 0x83, 0x74, 0x60, 0xD7, 0xEC, 0x42, 0xA6,
+ 0x3A, 0x23, 0xF7, 0x85, 0xEB, 0xC1, 0xFE, 0x6A,
+ 0x57, 0x8E, 0xC1, 0x44, 0xF3, 0x1F, 0xFE, 0xB8,
+ 0x2D, 0x8C, 0x4D, 0xC9, 0x5B, 0xAE, 0x21, 0x2E,
+ 0x4C, 0x1A, 0xEB, 0x84, 0x09, 0xF3, 0x40, 0x92,
+ 0x39, 0x7F, 0x56, 0x02, 0x46, 0x61, 0x16, 0xDE,
+ 0x5C, 0x48, 0xB6, 0x0C, 0x1D, 0xD3, 0x5F, 0x10,
+ 0x9A, 0x39, 0xB8, 0x66, 0x31, 0xFC, 0x39, 0x71,
+ 0x87, 0x23, 0x46, 0x9D, 0xE8, 0x3C, 0x2B, 0xA1,
+ 0x39, 0x8A, 0xD3, 0xFF, 0xD9, 0x43, 0xB6, 0x61,
+ 0xC6, 0x67, 0x70, 0x40, 0xBD, 0xFE, 0xD3, 0xC1,
+ 0x68, 0xF5, 0xF7, 0xC8, 0x89, 0xD8, 0x17, 0xC5,
+ 0xE8, 0x3D, 0x29, 0xD5, 0x91, 0xDF, 0x1F, 0x56,
+ 0x74, 0x5A, 0xC4, 0xA8, 0x14, 0xBA, 0xD4, 0xFA,
+ 0x13, 0x49, 0x2A, 0x9F, 0x63, 0xF1, 0xB2, 0x45,
+ 0xF1, 0xF0, 0x2A, 0xDD, 0x75, 0x66, 0x8A, 0xF7,
+ 0xAB, 0x73, 0x86, 0x26, 0x9D, 0x1F, 0x07, 0xAD,
+ 0xD3, 0xFE, 0xE0, 0xA3, 0xED, 0xA0, 0x96, 0x3E,
+ 0x1E, 0x89, 0x86, 0x02, 0x4C, 0x28, 0xFD, 0x57,
+ 0xA1, 0x67, 0x55, 0xF0, 0x82, 0x3B, 0x7F, 0xCC,
+ 0x2A, 0x32, 0x01, 0x93, 0x1D, 0x8B, 0x66, 0x8A,
+ 0x20, 0x52, 0x84, 0xDD, 0x2C, 0xFD, 0xEE, 0x72,
+ 0xF3, 0x8C, 0x58, 0xB9, 0x99, 0xE5, 0xC1, 0x22,
+ 0x63, 0x59, 0x00, 0xE2, 0x76, 0xC5, 0x3A, 0x17,
+ 0x7F, 0x93, 0xE9, 0x67, 0x61, 0xAA, 0x10, 0xC3,
+ 0xD9, 0xC8, 0x24, 0x46, 0x5B, 0xBE, 0x8C, 0x1F,
+ 0x2D, 0x66, 0x48, 0xD2, 0x02, 0x11, 0xFB, 0x74,
+ 0x14, 0x76, 0x76, 0x5A, 0x98, 0x54, 0x35, 0xA7,
+ 0x85, 0x66, 0x20, 0x26, 0x8B, 0x13, 0x6F, 0x68,
+ 0xE3, 0xC9, 0x58, 0x7D, 0x1C, 0x3E, 0x01, 0x8D,
+ 0xF8, 0xD6, 0x7F, 0xCF, 0xA2, 0x07, 0xB7, 0x95,
+ 0xFD, 0xF0, 0x02, 0x34, 0x32, 0x30, 0xE8, 0xD4,
+ 0x57, 0x5E, 0x53, 0xFB, 0x54, 0xE2, 0x03, 0x32,
+ 0xCC, 0x52, 0x2E, 0xD2, 0x35, 0xD9, 0x58, 0x85,
+ 0x2D, 0xEC, 0x2D, 0x71, 0xD1, 0x8A, 0x29, 0xD0,
+ 0xB0, 0x24, 0xBD, 0x24, 0xDC, 0x1A, 0x28, 0x3F,
+ 0xA0, 0x12, 0x81, 0x15, 0x24, 0xC9, 0xB5, 0x4A,
+ 0x23, 0xB6, 0xA3, 0x45, 0x50, 0x2D, 0x73, 0x99,
+ 0x6B, 0x1C, 0xFB, 0xA4, 0x53, 0xD7, 0x5C, 0xF4,
+ 0x6C, 0xB0, 0xE5, 0x74, 0xB3, 0x76, 0xF8, 0xB1,
+ 0x0D, 0x59, 0x70, 0x9F, 0xCA, 0xDE, 0xF2, 0xAA,
+ 0x4C, 0x7D, 0x11, 0x54, 0xC4, 0x19, 0x0F, 0x36,
+ 0x4A, 0x62, 0xFF, 0x8B, 0x10, 0xCB, 0x93, 0x50,
+ 0xDA, 0x79, 0x5E, 0x4E, 0x09, 0x1F, 0x22, 0xC8,
+ 0x19, 0x85, 0xE9, 0xEE, 0xB7, 0x71, 0x65, 0xB9,
+ 0x10, 0xD2, 0x0A, 0x73, 0x5B, 0xA6, 0xDA, 0x37,
+ 0x46, 0x02, 0x00, 0x98, 0x9E, 0x20, 0x6C, 0x7D,
+ 0xC7, 0x69, 0xBB, 0xC2, 0x00, 0x40, 0x9C, 0x57,
+ 0x00, 0xC2, 0x36, 0x76, 0xE8, 0x2A, 0x8D, 0xAD,
+ 0x62, 0x57, 0xC8, 0xD0, 0x9D, 0x66, 0x27, 0x5A,
+ 0xD8, 0x0D, 0x35, 0x60, 0x28, 0x38, 0x62, 0x94,
+ 0x78, 0x36, 0x25, 0x58, 0xFD, 0xF8, 0x66, 0x1F,
+ 0x68, 0x04, 0x0F, 0xD8, 0x00, 0xDF, 0xA0, 0x6C,
+ 0x25, 0x42, 0x9A, 0x4C, 0xEB, 0x80, 0x13, 0x51,
+ 0x7D, 0x2D, 0xA8, 0x89, 0xD6, 0x1B, 0x67, 0x72,
+ 0x01, 0xF3, 0x2D, 0x16, 0x77, 0xFE, 0x22, 0xBC,
+ 0x8A, 0x45, 0x09, 0x1F, 0x9C, 0x2F, 0x2A, 0xA9,
+ 0x61, 0x5B, 0x4A, 0xE6, 0x64, 0x2C, 0x62, 0x1A,
+ 0x3A, 0x96, 0xE6, 0x0A, 0xAE, 0x05, 0x1A, 0xC8,
+ 0xCB, 0xD6, 0x8F, 0x3A, 0x4B, 0xE0, 0x7F, 0x82,
+ 0xB4, 0x98, 0xF1, 0x9D, 0xD7, 0x14, 0x76, 0x5E,
+ 0x77, 0x85, 0x87, 0xEC, 0x13, 0xDA, 0xFD, 0xAF,
+ 0xCB, 0xA3, 0x1C, 0x99, 0xC1, 0xFE, 0x17, 0x0C,
+ 0x40, 0x4D, 0x3C, 0x8F, 0x70, 0x86, 0x63, 0x64,
+ 0xB7, 0x75, 0xA8, 0x71, 0x36, 0xDC, 0x54, 0x10,
+ 0x57, 0x0C, 0xA8, 0xF2, 0xA1, 0xBB, 0xED, 0x03,
+ 0x41, 0x57, 0x34, 0x2C, 0x8F, 0x7C, 0xA0, 0x09,
+ 0xF3, 0x9E, 0x41, 0xB7, 0xA8, 0xD4, 0x66, 0x0D,
+ 0x0D, 0xC0, 0x6A, 0xFC, 0x6A, 0xA2, 0xAC, 0xE2,
+ 0x60, 0x00, 0xE3, 0xF7, 0x75, 0x43, 0x23, 0xEB,
+ 0xC8, 0x61, 0xFA, 0xB3, 0xB8, 0x28, 0xCE, 0xCA,
+ 0xF4, 0x47, 0x7F, 0x30, 0x6D, 0x61, 0x89, 0x47,
+ 0xA1, 0x4A, 0xFE, 0xD1, 0x21, 0x0B, 0x6D, 0xF4,
+ 0x3F, 0x00, 0x86, 0x30, 0x8E, 0x33, 0x21, 0x6F,
+ 0xDA, 0x15, 0xFD, 0x5F, 0xEC, 0x8E, 0xF1, 0x12,
+ 0x3F, 0xC9, 0x83, 0x0C, 0xCA, 0x22, 0x01, 0xF1,
+ 0x70, 0x5F, 0x1F, 0x66, 0xB5, 0xF8, 0x3E, 0x7A,
+ 0x6F, 0xDE, 0xDB, 0xA7, 0x8D, 0x18, 0x9E, 0xBE,
+ 0xDB, 0xAD, 0x3D, 0x66, 0x30, 0xC1, 0x6C, 0x0C,
+ 0x87, 0xB4, 0x65, 0x75, 0xE0, 0x9D, 0xEA, 0x16,
+ 0x0D, 0x07, 0x37, 0x33, 0xC5, 0xEC, 0x97, 0x93,
+ 0x37, 0xEB, 0x8E, 0x65, 0x9C, 0x40, 0x63, 0x6C,
+ 0x43, 0x60, 0xB0, 0x40, 0x4D, 0x85, 0xEF, 0xC2,
+ 0x47, 0x5F, 0xE7, 0x6B, 0xCB, 0x40, 0xE8, 0xEA,
+ 0xD8, 0xAB, 0xB1, 0x9A, 0x72, 0xDC, 0x4C, 0x14,
+ 0xFA, 0x43, 0x61, 0x5F, 0xA6, 0x5C, 0x3A, 0x05,
+ 0x17, 0x2E, 0x74, 0xF3, 0x5E, 0x45, 0xD9, 0x47,
+ 0xAA, 0x59, 0xB6, 0x8F, 0x42, 0x66, 0x42, 0x29,
+ 0x90, 0x95, 0x48, 0x46, 0x91, 0x88, 0x3C, 0x8C,
+ 0xDE, 0xCC, 0xED, 0xB3, 0xAA, 0x62, 0xEA, 0xBC,
+ 0xB4, 0x0C, 0x48, 0x4C, 0x53, 0x23, 0x5E, 0x24,
+ 0x85, 0xBF, 0x92, 0xDA, 0x14, 0xDB, 0x1A, 0x3D,
+ 0xEF, 0x30, 0xD9, 0x49, 0x64, 0x4D, 0xE5, 0x01,
+ 0xFC, 0xA4, 0x4B, 0xD1, 0x9F, 0xDE, 0x96, 0x7F,
+ 0x50, 0xBC, 0x4D, 0x38, 0x44, 0xE9, 0x23, 0x5F,
+ 0x37, 0x57, 0x1A, 0xA6, 0x52, 0x5A, 0x0F, 0x4F,
+ 0x87, 0x33, 0x4A, 0x7B, 0x66, 0xEE, 0x3D, 0x66,
+ 0x0A, 0x63, 0x39, 0x1F, 0x23, 0x38, 0x35, 0x73,
+ 0x60, 0x5E, 0x47, 0x20, 0x4F, 0xC0, 0xC8, 0x3C,
+ 0x09, 0xF9, 0x29, 0x4F, 0x5E, 0x55, 0x69, 0xC4,
+ 0x6B, 0xE8, 0xF8, 0x91, 0xC0, 0x22, 0x65, 0x15,
+ 0x1E, 0xFB, 0xB9, 0x61, 0xCE, 0x45, 0xBE, 0x2B,
+ 0xEE, 0xB9, 0x04, 0x2B, 0xFD, 0xAE, 0x61, 0x1C,
+ 0x3D, 0x3D, 0x7C, 0xBF, 0xC1, 0xF7, 0x3C, 0x4E,
+ 0x9E, 0x0E, 0x54, 0xC8, 0xAD, 0xA9, 0xDF, 0x43,
+ 0x49, 0xB9, 0x41, 0x05, 0xE5, 0xF1, 0x49, 0xAA,
+ 0x77, 0x6C, 0x34, 0x5B, 0x93, 0x24, 0x24, 0x23,
+ 0x74, 0x68, 0x11, 0xCE, 0x15, 0x80, 0xA1, 0xA4,
+ 0x1F, 0x8D, 0x81, 0xCD, 0xB2, 0x98, 0xCA, 0x14,
+ 0x0B, 0x0C, 0x61, 0x50, 0x69, 0x72, 0xAE, 0xFA,
+ 0x8B, 0xC0, 0x3F, 0x0D, 0xE7, 0xF2, 0x0F, 0xEB,
+ 0xC1, 0x11, 0xB9, 0x10, 0x03, 0x6A, 0xF5, 0x97,
+ 0x3C, 0x53, 0x2F, 0x67, 0x86, 0x09, 0x6A, 0xE3,
+ 0x28, 0xC0, 0x78, 0xC8, 0xB4, 0x39, 0x8E, 0xD1,
+ 0xCE, 0x25, 0xE8, 0x66, 0xF7, 0x09, 0x40, 0x7D,
+ 0x81, 0xFB, 0xAF, 0xFA, 0x59, 0xC4, 0x9B, 0x2B,
+ 0x83, 0x45, 0x5B, 0xA8, 0x66, 0x9E, 0x38, 0xC8,
+ 0xFD, 0xAC, 0xF2, 0x2D, 0x21, 0xDE, 0x50, 0x4C,
+ 0x03, 0xCB, 0x88, 0x42, 0xDD, 0x84, 0x09, 0x99,
+ 0x8E, 0x8B, 0x40, 0x97, 0x1B, 0x14, 0x85, 0x37,
+ 0x11, 0x01, 0xE0, 0x74, 0x6B, 0x33, 0x52, 0x8C,
+ 0x68, 0x3A, 0x89, 0xB2, 0xAF, 0x35, 0xE6, 0x65,
+ 0xC3, 0x58, 0x70, 0xD2, 0xE7, 0x1F, 0x1F, 0xF6,
+ 0xE5, 0x0A, 0xB1, 0xFE, 0xD0, 0xC9, 0x51, 0x50,
+ 0xE7, 0xFD, 0x58, 0xF5, 0xC4, 0x58, 0x65, 0x94,
+ 0xD1, 0x57, 0x55, 0x5E, 0xD2, 0x27, 0x98, 0xAF,
+ 0xE7, 0x55, 0x0B, 0x87, 0x50, 0x9B, 0xEF, 0xE8,
+ 0x2B, 0xFC, 0xE7, 0x3B, 0x4E, 0xD7, 0xB7, 0x4D,
+ 0xF4, 0xBC, 0xF4, 0x88, 0x63, 0xE4, 0x8A, 0x20,
+ 0x4B, 0x22, 0xB0, 0xA0, 0x53, 0x7F, 0xA8, 0xC9,
+ 0x0C, 0xF8, 0xD7, 0xBD, 0x46, 0x39, 0xA7, 0x7D,
+ 0xDD, 0x10, 0x91, 0x50, 0x54, 0x06, 0x47, 0xF0,
+ 0x3C, 0xAA, 0x43, 0x40, 0xF8, 0x54, 0xDD, 0x8A,
+ 0xEA, 0x8A, 0x0B, 0xA5, 0x7F, 0xCD, 0x5E, 0xAA,
+ 0x02, 0x2E, 0x1F, 0xC6, 0x50, 0x15, 0xF8, 0x0A,
+ 0x0C, 0x1B, 0x3C, 0x55, 0x3A, 0xC3, 0x6F, 0x88,
+ 0xD7, 0xBF, 0xB1, 0x02, 0xCC, 0xE0, 0x08, 0x29,
+ 0x97, 0xD2, 0xAA, 0x23, 0xC4, 0x6D, 0xE3, 0xE3,
+ 0x76, 0x39, 0x92, 0xC3, 0x2E, 0x7A, 0xE2, 0x98,
+ 0xD1, 0xFC, 0xAE, 0xCC, 0x95, 0xD8, 0xB4, 0xDC,
+ 0x92, 0xEA, 0x6A, 0x5F, 0xF2, 0x92, 0x17, 0x0B,
+ 0x8D, 0xC3, 0xFA, 0x9C, 0x62, 0xCE, 0x44, 0x8D,
+ 0xC3, 0x1E, 0xC3, 0xB2, 0xD5, 0x00, 0xCD, 0xB4,
+ 0x9E, 0x2D, 0x7B, 0xF2, 0x98, 0xA3, 0x00, 0x8B,
+ 0x81, 0x30, 0x77, 0x5B, 0x02, 0x99, 0xB1, 0xCD,
+ 0xC3, 0x1D, 0x74, 0x74, 0xEF, 0x41, 0xCB, 0x69,
+ 0x63, 0x8E, 0xA6, 0xD3, 0x2D, 0x3E, 0x1F, 0x1D,
+ 0x12, 0x9E, 0xD9, 0x18, 0x67, 0x06, 0xAF, 0x37,
+ 0x29, 0xAD, 0x65, 0xD8, 0xEB, 0x71, 0xC4, 0x7D,
+ 0x94, 0x3D, 0xEA, 0xCC, 0xDF, 0x72, 0x41, 0x51,
+ 0x3C, 0xA1, 0x66, 0x98, 0x32, 0x32, 0x40, 0x54,
+ 0xB0, 0x2F, 0xEB, 0xCE, 0xDF, 0x4A, 0x64, 0xFB,
+ 0x9A, 0x90, 0xDC, 0xF6, 0x6F, 0xA9, 0xD4, 0xCA,
+ 0xCB, 0x91, 0xC4, 0xFE, 0xEE, 0x9C, 0x01, 0x50,
+ 0x2E, 0xAC, 0xCC, 0x5F, 0x89, 0xD0, 0x91, 0xA3,
+ 0xD9, 0xF9, 0x4B, 0x8D, 0xDE, 0x6C, 0x60, 0x21,
+ 0x19, 0xB1, 0xD3, 0x4D, 0x75, 0x56, 0x6F, 0xB8,
+ 0x25, 0xA4, 0x92, 0x4F, 0x12, 0xF5, 0x8F, 0xC1,
+ 0x17, 0x4B, 0xB3, 0x34, 0x21, 0x22, 0xAC, 0x52,
+ 0xD2, 0x64, 0xC9, 0x9A, 0x7D, 0xFC, 0xC0, 0x0A,
+ 0x89, 0x34, 0xFF, 0x08, 0xD3, 0x04, 0xDC, 0xFE,
+ 0x7C, 0xB3, 0xB8, 0xFD, 0x85, 0xDD, 0x79, 0x51,
+ 0xA7, 0x89, 0xE8, 0xF1, 0x23, 0xB1, 0xDF, 0xD7,
+ 0x1F, 0x7B, 0xB1, 0x5D, 0x42, 0xF9, 0x61, 0xF8,
+ 0xDC, 0x81, 0x04, 0xF1, 0xCC, 0xFA, 0xD7, 0xED,
+ 0xBF, 0x47, 0xAC, 0xBD, 0xE5, 0xFA, 0xAC, 0xB3,
+ 0x1C, 0xD9, 0xA1, 0xB3, 0x60, 0xEE, 0x9C, 0x8A,
+ 0x36, 0x57, 0xB4, 0x2F, 0xA1, 0xA2, 0xF3, 0xE2,
+ 0x09, 0x9A, 0x6E, 0x43, 0x9B, 0xE5, 0x93, 0xB8,
+ 0x3D, 0x9E, 0x9F, 0xC1, 0xC6, 0x0D, 0x02, 0xEB,
+ 0x4D, 0x38, 0xE9, 0xB4, 0x9F, 0xEA, 0x33, 0x8C,
+ 0x07, 0xD8, 0xB4, 0x71, 0xAD, 0xE5, 0x43, 0xB2,
+ 0xCC, 0x55, 0x93, 0x6A, 0xDB, 0x1E, 0x80, 0xDB,
+ 0xC2, 0xEA, 0x42, 0x8E, 0xFC, 0x86, 0x44, 0xC9,
+ 0x8A, 0xC4, 0xF2, 0x46, 0xA7, 0x39, 0x50, 0x0D,
+ 0x1A, 0xAA, 0x07, 0x04, 0xBE, 0xD4, 0xCE, 0x62,
+ 0x4D, 0x0F, 0x91, 0x7D, 0x29, 0x88, 0x9C, 0x4C,
+ 0xAF, 0xF7, 0xD8, 0x40, 0x93, 0x88, 0xC7, 0x20,
+ 0xD2, 0x17, 0x2A, 0xC4, 0x92, 0x72, 0xD0, 0xC0,
+ 0x4E, 0x56, 0x47, 0xB1, 0x27, 0x02, 0xE6, 0x61,
+ 0x82, 0x5E, 0xC8, 0x2E, 0x90, 0xD2, 0x31, 0x22,
+ 0xE2, 0xA9, 0x4A, 0x91, 0x45, 0x69, 0xB1, 0xA5,
+ 0x0F, 0x66, 0x2C, 0x30, 0xAD, 0x7F, 0x1B, 0x0E,
+ 0x22, 0x17, 0x60, 0x2E, 0x3D, 0x7F, 0x7F, 0x8C,
+ 0x33, 0x51, 0xA0, 0x25, 0xDE, 0xFD, 0x75, 0xBC,
+ 0xEF, 0xE6, 0xE7, 0x20, 0x04, 0x5A, 0xEC, 0x50,
+ 0x21, 0x48, 0x56, 0x98, 0xE2, 0x33, 0x6D, 0x22,
+ 0x5C, 0xC3, 0xFB, 0xFC, 0x6F, 0xB3, 0xA7, 0x8E,
+ 0x6F, 0x67, 0x70, 0x9D, 0xDA, 0x02, 0x01, 0x59,
+ 0x7B, 0x3D, 0x2B, 0x38, 0xCC, 0x0F, 0x44, 0x3D,
+ 0xFB, 0x9A, 0xB3, 0x23, 0x15, 0x50, 0x6E, 0xBF,
+ 0x8B, 0xA1, 0x94, 0x33, 0xE5, 0x7B, 0x88, 0x4E,
+ 0xCB, 0x6D, 0x9F, 0xBF, 0xBC, 0x7A, 0xA8, 0x1E,
+ 0x68, 0x25, 0xED, 0x8E, 0x53, 0x21, 0x72, 0xC5,
+ 0x70, 0xB3, 0xE4, 0xA6, 0xA1, 0x5A, 0x2D, 0xC8,
+ 0x43, 0x9D, 0x60, 0x77, 0x78, 0xE0, 0xC4, 0xAF,
+ 0xC8, 0x29, 0xBA, 0xD0, 0x4D, 0x39, 0x83, 0x51,
+ 0xA7, 0x10, 0x7F, 0x0C, 0x34, 0x0E, 0x6C, 0x75,
+ 0x26, 0xD7, 0xD6, 0xC7, 0x32, 0x53, 0xAF, 0x4E,
+ 0xBE, 0xF2, 0xC2, 0x0F, 0x99, 0x23, 0xB9, 0xE1,
+ 0xC8, 0xB4, 0xBC, 0x5A, 0xC6, 0xCB, 0xEB, 0x4D,
+ 0x28, 0x56, 0x72, 0xFE, 0x1B, 0x2C, 0x5D, 0xE3,
+ 0xBC, 0xC7, 0xA3, 0xC0, 0x7D, 0x27, 0xF0, 0xD0,
+ 0x4F, 0x3F, 0x1F, 0xF7, 0x87, 0x15, 0xF2, 0xEA,
+ 0xD4, 0x03, 0x6D, 0x2F, 0xD4, 0x8E, 0x50, 0x4B,
+ 0x05, 0xBF, 0xF7, 0x8C, 0x67, 0x5A, 0xDC, 0x4D,
+ 0xCD, 0xCF, 0x9D, 0x02, 0xB6, 0xE7, 0xAE, 0x49,
+ 0xD1, 0x7C, 0x00, 0xE7, 0x3B, 0xEA, 0xFB, 0x0D,
+ 0x2A, 0x7B, 0x41, 0x33, 0x66, 0xD0, 0x29, 0x9F,
+ 0xB3, 0x8A, 0x71, 0xB0, 0xE2, 0x76, 0xA9, 0xDB,
+ 0xFD, 0x64, 0x04, 0x69, 0xDF, 0x89, 0x1F, 0x56,
+ 0x86, 0x92, 0xD9, 0xD9, 0xB9, 0xF3, 0x4F, 0xAC,
+ 0xAE, 0x61, 0x48, 0x20, 0xCE, 0x3C, 0x2B, 0x44,
+ 0xAB, 0x42, 0xFA, 0xAB, 0x2E, 0x94, 0x82, 0xC8,
+ 0xD9, 0x97, 0xCF, 0x27, 0xDF, 0xAC, 0xAC, 0xE7,
+ 0xCA, 0xB2, 0x84, 0xAB, 0xF2, 0x5D, 0xDF, 0x56,
+ 0x0C, 0x8C, 0x07, 0x3C, 0x3D, 0xA8, 0xDD, 0xBE,
+ 0xFF, 0x4E, 0x28, 0x0D, 0xB2, 0x2D, 0xE6, 0x9D,
+ 0x44, 0x21, 0xCB, 0xE7, 0x33, 0x63, 0x22, 0x8F,
+ 0x4C, 0xFF, 0xB6, 0x1D, 0x9A, 0x71, 0x3F, 0xB1,
+ 0x29, 0xAE, 0x3A, 0x35, 0xEE, 0x9C, 0x97, 0x68,
+ 0xA7, 0x52, 0x66, 0x01, 0xD8, 0x9A, 0x5D, 0xF4,
+ 0xB3, 0x2F, 0x5C, 0xD4, 0x0E, 0xF9, 0xCF, 0x07,
+ 0xF6, 0x8C, 0xBA, 0xA6, 0x8D, 0x6B, 0xC6, 0x01,
+ 0xC2, 0x69, 0xAE, 0x60, 0x08, 0x1A, 0x0E, 0x3F,
+ 0xAE, 0x60, 0x29, 0xF3, 0x48, 0x0D, 0xE0, 0xD0,
+ 0xAE, 0x52, 0x44, 0xE9, 0x7F, 0x1F, 0x92, 0x5F,
+ 0x71, 0xAD, 0xEC, 0x6B, 0x47, 0x66, 0x92, 0x22,
+ 0x27, 0xAE, 0x6E, 0x25, 0xCD, 0xF3, 0x5F, 0x55,
+ 0x59, 0xBD, 0x73, 0xCE, 0x2B, 0x7E, 0x99, 0x44,
+ 0x56, 0x70, 0xA3, 0xE7, 0x7A, 0x59, 0x75, 0xD8,
+ 0x48, 0x0C, 0x39, 0x2B, 0xD7, 0x53, 0xC6, 0xAD,
+ 0x4A, 0x6F, 0xB4, 0x14, 0x96, 0xDF, 0xF2, 0x4A,
+ 0x0C, 0xA2, 0xD5, 0x29, 0x98, 0x7C, 0x42, 0x87,
+ 0xD9, 0x1F, 0x97, 0x61, 0xD9, 0xBF, 0x99, 0x4F,
+ 0x2C, 0x4C, 0x75, 0xAC, 0xB8, 0x06, 0x75, 0xD6,
+ 0x87, 0x76, 0x7E, 0xE3, 0x23, 0x4B, 0xEA, 0x1A,
+ 0x1A, 0xF4, 0xB7, 0x09, 0xAF, 0x53, 0xEB, 0xA6,
+ 0x39, 0x10, 0xFE, 0xD4, 0xEB, 0x1B, 0xAE, 0x38,
+ 0x31, 0x33, 0xBA, 0x68, 0xEE, 0xC7, 0x65, 0x76,
+ 0xFB, 0x49, 0x77, 0xD4, 0x19, 0xC4, 0xE6, 0xA7,
+ 0x05, 0xFE, 0x2A, 0xDA, 0x39, 0x99, 0x1A, 0x92,
+ 0xD2, 0xF0, 0x61, 0x97, 0xF6, 0x06, 0x6C, 0x88,
+ 0x7B, 0x6F, 0x60, 0xE6, 0x70, 0x08, 0xF0, 0xB4,
+ 0x6B, 0x39, 0x6F, 0x05, 0x41, 0x81, 0xF9, 0xBE,
+ 0x7A, 0x51, 0xC4, 0x75, 0xB0, 0x6A, 0x89, 0xA0,
+ 0xA6, 0x9A, 0x5B, 0xEE, 0x7D, 0x78, 0x17, 0x5F,
+ 0x9F, 0x3B, 0x7D, 0xDD, 0x8A, 0x9E, 0xAA, 0x1A,
+ 0xDA, 0x49, 0x08, 0xE9, 0xFD, 0x91, 0xA6, 0xFA,
+ 0xCE, 0xCF, 0x67, 0xDF, 0x0F, 0xC9, 0xD6, 0x38,
+ 0xD9, 0xD5, 0xD1, 0xC0, 0x76, 0x59, 0x42, 0x53,
+ 0xBF, 0x48, 0xE9, 0x11, 0x74, 0xC7, 0x11, 0xD8,
+ 0xE7, 0x8E, 0xD3, 0xC8, 0x25, 0xA1, 0x26, 0x50,
+ 0xBB, 0xB4, 0x35, 0xAF, 0xAF, 0x06, 0x23, 0x69,
+ 0x3E, 0x30, 0xFD, 0x7B, 0x34, 0x83, 0x07, 0xD0,
+ 0xF0, 0x0F, 0x6C, 0x9A, 0x13, 0x5D, 0xC2, 0x7B,
+ 0xDF, 0x6F, 0xDD, 0x8E, 0xF4, 0x30, 0x82, 0x05,
+ 0x41, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7,
+ 0x0D, 0x01, 0x07, 0x01, 0xA0, 0x82, 0x05, 0x32,
+ 0x04, 0x82, 0x05, 0x2E, 0x30, 0x82, 0x05, 0x2A,
+ 0x30, 0x82, 0x05, 0x26, 0x06, 0x0B, 0x2A, 0x86,
+ 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01,
+ 0x02, 0xA0, 0x82, 0x04, 0xEE, 0x30, 0x82, 0x04,
+ 0xEA, 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48,
+ 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30,
+ 0x0E, 0x04, 0x08, 0x74, 0xC0, 0x84, 0x8F, 0xC7,
+ 0x74, 0x5E, 0x21, 0x02, 0x02, 0x08, 0x00, 0x04,
+ 0x82, 0x04, 0xC8, 0x1E, 0xF4, 0xE9, 0x07, 0x27,
+ 0x9E, 0x5A, 0xC9, 0x39, 0x1D, 0x37, 0x2C, 0x06,
+ 0x4B, 0x57, 0xEA, 0xC5, 0x42, 0x9A, 0x60, 0xD5,
+ 0x42, 0xB2, 0x34, 0x2D, 0xD3, 0x88, 0x7C, 0x78,
+ 0x87, 0xB6, 0xE9, 0x42, 0x44, 0x1F, 0x67, 0x32,
+ 0x92, 0x54, 0x22, 0xDA, 0xB2, 0x43, 0xE7, 0x40,
+ 0xBE, 0x1F, 0xAF, 0x3A, 0xCD, 0x2A, 0x9F, 0xD7,
+ 0x44, 0x5B, 0x37, 0x69, 0x85, 0xDF, 0xEB, 0x2A,
+ 0xB9, 0xE2, 0x92, 0x3B, 0xEA, 0xD5, 0x42, 0x53,
+ 0x95, 0x4A, 0xB0, 0x1B, 0xA5, 0xEF, 0xA6, 0x0D,
+ 0x29, 0xF4, 0x33, 0xFE, 0xD7, 0x49, 0x04, 0x1E,
+ 0x8C, 0xAD, 0x63, 0x1E, 0x79, 0x63, 0x74, 0x0C,
+ 0xE5, 0x5E, 0xA2, 0x2C, 0xBE, 0xB8, 0x90, 0xCE,
+ 0x06, 0x25, 0xBF, 0xD1, 0x5A, 0x50, 0xCF, 0x3B,
+ 0x52, 0xE2, 0xA7, 0xFF, 0x19, 0x02, 0xCF, 0xD0,
+ 0x9B, 0xD9, 0xF7, 0x28, 0x07, 0x38, 0x1F, 0xF2,
+ 0xAF, 0x44, 0x91, 0x3F, 0x0F, 0xB6, 0x6E, 0x8C,
+ 0xC0, 0x32, 0x92, 0xC0, 0xCD, 0x25, 0x98, 0x67,
+ 0xF1, 0x47, 0x52, 0x50, 0xF0, 0xA3, 0x7B, 0xE6,
+ 0x74, 0xDC, 0x72, 0x28, 0xC8, 0xAB, 0xB3, 0x31,
+ 0x7D, 0xA3, 0xF7, 0xC7, 0xD1, 0xE6, 0x99, 0xB4,
+ 0xB6, 0x5A, 0x3A, 0x4D, 0x83, 0x4F, 0xB8, 0xB5,
+ 0x86, 0xF8, 0x37, 0x7F, 0xA0, 0x16, 0x2F, 0x3C,
+ 0x62, 0x7A, 0xD4, 0x3A, 0xEB, 0xC2, 0xE8, 0x03,
+ 0x49, 0x17, 0x9E, 0xFB, 0xD7, 0xAF, 0x91, 0x32,
+ 0xFD, 0xEA, 0x4F, 0x64, 0xC6, 0x6E, 0x02, 0xEA,
+ 0xC4, 0xC8, 0x1F, 0x16, 0xC5, 0x4C, 0xFB, 0xC5,
+ 0x42, 0xF5, 0x85, 0x05, 0x92, 0x59, 0x4B, 0x31,
+ 0xE5, 0xE9, 0x69, 0xE7, 0x02, 0x98, 0x33, 0xBA,
+ 0x4C, 0x17, 0x09, 0xEF, 0x89, 0x20, 0xFA, 0x83,
+ 0x9F, 0xAE, 0x0E, 0x1B, 0x7D, 0x98, 0xB9, 0xF2,
+ 0x3C, 0x0F, 0xB7, 0x1C, 0x72, 0xDF, 0x17, 0x84,
+ 0x7F, 0x0A, 0xFD, 0x12, 0x3C, 0x6F, 0x68, 0x5D,
+ 0x45, 0xEB, 0xB8, 0xD6, 0x24, 0x65, 0x42, 0x75,
+ 0x5C, 0xC2, 0xF3, 0x3A, 0x6A, 0x4E, 0x51, 0x34,
+ 0x1B, 0xB6, 0x81, 0xB2, 0x8A, 0xEF, 0x28, 0xA4,
+ 0xC5, 0x88, 0x9A, 0x97, 0xE0, 0xEF, 0x31, 0x12,
+ 0x01, 0x7E, 0x1B, 0x43, 0x0F, 0x27, 0x80, 0x87,
+ 0x98, 0xC5, 0xD5, 0x83, 0xCB, 0x4B, 0xB7, 0x01,
+ 0x79, 0x60, 0xA1, 0x1A, 0x03, 0x05, 0xC6, 0x36,
+ 0x04, 0x31, 0x3C, 0x06, 0xDB, 0x08, 0xA8, 0xDA,
+ 0x8E, 0x32, 0x19, 0x91, 0xF1, 0x0D, 0x61, 0x6F,
+ 0xE4, 0xB2, 0x79, 0x8A, 0xDE, 0xF4, 0xF7, 0xFB,
+ 0x2C, 0x23, 0x5B, 0xD9, 0x64, 0x2F, 0xB7, 0xB3,
+ 0x8B, 0xCA, 0xB8, 0x8C, 0x1D, 0x3B, 0x49, 0x05,
+ 0x38, 0xA1, 0xE5, 0x8C, 0x1A, 0xDC, 0xA5, 0x61,
+ 0xFE, 0xF4, 0x2B, 0xDC, 0x77, 0x28, 0xF6, 0x19,
+ 0xE7, 0xB7, 0x8F, 0x4D, 0x27, 0x2D, 0xED, 0x8A,
+ 0x3F, 0x3D, 0xDC, 0x9F, 0xD1, 0x30, 0xFF, 0xD6,
+ 0xC3, 0xBE, 0x41, 0x25, 0xE3, 0xA5, 0x9B, 0x73,
+ 0xDF, 0x6A, 0xD9, 0xF9, 0x70, 0x84, 0x02, 0x4C,
+ 0x35, 0xD4, 0x3E, 0x05, 0x76, 0x3A, 0xDC, 0x6D,
+ 0x5A, 0x81, 0xB3, 0x94, 0xF7, 0x22, 0xF7, 0xDC,
+ 0xC1, 0x43, 0x31, 0x57, 0x5B, 0x42, 0x9A, 0x0B,
+ 0xF4, 0x95, 0x30, 0xA9, 0xBB, 0xD8, 0x06, 0xFB,
+ 0x1D, 0x6F, 0x9B, 0xC3, 0xBB, 0xF3, 0xBF, 0xFB,
+ 0xB4, 0x9F, 0x35, 0x64, 0x0A, 0x69, 0xB7, 0xD1,
+ 0x3E, 0xCA, 0x78, 0x07, 0x04, 0x03, 0x79, 0xD4,
+ 0xF3, 0xA8, 0xEC, 0x18, 0xDB, 0x03, 0x5E, 0x47,
+ 0xD7, 0xD0, 0x56, 0x2C, 0x74, 0x94, 0x86, 0x04,
+ 0x46, 0xB8, 0xD4, 0x35, 0x0A, 0x7B, 0xE6, 0x78,
+ 0xC4, 0x43, 0x3C, 0x56, 0xCC, 0x37, 0x8B, 0xFD,
+ 0xE8, 0xF4, 0x57, 0xEA, 0xAE, 0xCF, 0x36, 0x97,
+ 0x12, 0xAC, 0x39, 0xCF, 0x7C, 0xEF, 0x22, 0x67,
+ 0x01, 0xEC, 0xD8, 0x09, 0x49, 0x4E, 0xE3, 0x74,
+ 0xDD, 0x39, 0xE1, 0x39, 0xD7, 0x0C, 0x5F, 0x1B,
+ 0xCE, 0x69, 0xBC, 0x72, 0x44, 0x87, 0x64, 0x1C,
+ 0x08, 0x05, 0x93, 0x69, 0x6D, 0x7F, 0x90, 0x0A,
+ 0x2C, 0xCB, 0x8A, 0xBB, 0x7F, 0xE3, 0xE0, 0x80,
+ 0x31, 0xD0, 0x0A, 0x3A, 0x95, 0xFF, 0xF7, 0xB4,
+ 0x36, 0x38, 0x93, 0xE0, 0x0C, 0x11, 0x37, 0x12,
+ 0x06, 0xF6, 0xAD, 0xE9, 0xB1, 0x7A, 0x00, 0xF5,
+ 0xD2, 0x32, 0x6B, 0xD0, 0x27, 0xA5, 0x1B, 0x3D,
+ 0xE8, 0xDB, 0xCC, 0xA9, 0x1F, 0x1F, 0xB1, 0x99,
+ 0x3D, 0x7C, 0xB7, 0xCA, 0xDA, 0x27, 0x2C, 0x64,
+ 0x1C, 0x49, 0xB6, 0x87, 0x44, 0x06, 0x94, 0x9D,
+ 0xBC, 0x6B, 0x20, 0xA2, 0x68, 0x15, 0x1F, 0xE2,
+ 0xF2, 0xAD, 0x6D, 0x23, 0x2E, 0x2B, 0x74, 0xE2,
+ 0x5D, 0xE4, 0xB0, 0xC7, 0x84, 0xCB, 0x64, 0xBF,
+ 0xE0, 0xA8, 0x18, 0x83, 0xB4, 0xC9, 0xD9, 0x73,
+ 0xA8, 0xE6, 0xA9, 0x36, 0xD5, 0x63, 0x1E, 0x2C,
+ 0x2A, 0x55, 0x09, 0x77, 0x5E, 0xB3, 0x4B, 0xEA,
+ 0xB5, 0xD0, 0x14, 0x5F, 0xEB, 0x50, 0x7B, 0xAA,
+ 0xEF, 0x94, 0xBA, 0x2B, 0xD7, 0x8A, 0x07, 0xF1,
+ 0xF9, 0x5E, 0x12, 0x12, 0x21, 0x52, 0xE5, 0x0A,
+ 0x3E, 0xC0, 0xBC, 0x5D, 0x4C, 0xE2, 0x12, 0x7C,
+ 0x39, 0xF9, 0x16, 0x9D, 0xBD, 0x96, 0x83, 0x3B,
+ 0x7F, 0x3D, 0x6A, 0xEC, 0xF1, 0x25, 0xD2, 0xB0,
+ 0xB0, 0xEB, 0x20, 0x06, 0x07, 0xD6, 0xD9, 0x4C,
+ 0x07, 0x9A, 0x82, 0xC1, 0xFC, 0xF7, 0x66, 0x15,
+ 0xBD, 0x62, 0x65, 0xD8, 0x6C, 0xF6, 0x33, 0x7B,
+ 0x5A, 0x28, 0xEC, 0x90, 0xA1, 0x26, 0x9F, 0xC3,
+ 0x28, 0x4A, 0x64, 0x50, 0x5F, 0xCA, 0xE2, 0x6D,
+ 0xB8, 0x0F, 0xE2, 0x94, 0xB5, 0x8E, 0x1F, 0x8A,
+ 0x8F, 0x6B, 0xA6, 0x86, 0x1F, 0xEE, 0xDC, 0x24,
+ 0xB4, 0xB8, 0x25, 0xEC, 0x28, 0x2D, 0xF9, 0xCB,
+ 0x7D, 0x38, 0xFF, 0xC7, 0x74, 0x2E, 0xD3, 0x10,
+ 0xEC, 0x03, 0x31, 0xEE, 0x83, 0xE7, 0xA4, 0xF7,
+ 0xBA, 0x28, 0x21, 0xE0, 0x7F, 0xB4, 0xB7, 0xE1,
+ 0x7A, 0xF9, 0x2B, 0xB0, 0x2C, 0x3B, 0x80, 0x5F,
+ 0xE0, 0x5D, 0xB2, 0x7E, 0x59, 0xFF, 0x59, 0x07,
+ 0x58, 0x42, 0x57, 0xEE, 0x44, 0xF1, 0xB1, 0xAD,
+ 0xBA, 0xDE, 0xCB, 0x1D, 0x8A, 0x36, 0x67, 0xE8,
+ 0x45, 0xFF, 0x07, 0x8D, 0xEE, 0xA4, 0x51, 0x9C,
+ 0x4C, 0x83, 0x5D, 0x2E, 0x2F, 0xE1, 0x5B, 0x75,
+ 0xE8, 0x29, 0xCD, 0x0B, 0x07, 0x62, 0xE0, 0xC3,
+ 0x0D, 0x1D, 0xEA, 0xCF, 0xF0, 0x8A, 0x65, 0x27,
+ 0x70, 0x42, 0x9F, 0x26, 0x00, 0x15, 0x70, 0xC5,
+ 0x4A, 0xF6, 0x25, 0xD0, 0x40, 0x72, 0xE9, 0xC1,
+ 0x73, 0xFD, 0x48, 0x94, 0xA3, 0x8D, 0x66, 0x63,
+ 0x96, 0x4F, 0xF7, 0xEE, 0xFB, 0x4C, 0xC7, 0xB8,
+ 0x6B, 0xE9, 0x90, 0xE1, 0x2A, 0x66, 0x80, 0x99,
+ 0x3B, 0xB0, 0x1A, 0x6C, 0xF9, 0x0E, 0x72, 0xDA,
+ 0x8E, 0x4F, 0x46, 0xC2, 0x6A, 0x4B, 0x7A, 0x16,
+ 0xE5, 0x26, 0x0B, 0x5C, 0xD4, 0x47, 0x34, 0xE5,
+ 0x37, 0xBE, 0x68, 0x6C, 0xDA, 0xD3, 0x9B, 0x6F,
+ 0xAE, 0x51, 0x9C, 0x99, 0x0A, 0x5B, 0xF8, 0x37,
+ 0xBC, 0xDE, 0xFC, 0x93, 0xC5, 0xE7, 0x0F, 0xEF,
+ 0x0B, 0xA6, 0x07, 0xC2, 0xA6, 0xE6, 0xDA, 0x2D,
+ 0x1B, 0x49, 0xC9, 0xDE, 0x6B, 0x27, 0xDC, 0x00,
+ 0xEF, 0x23, 0x87, 0x0E, 0xEB, 0xD1, 0x48, 0x7D,
+ 0xB4, 0xF2, 0x58, 0xC6, 0x3C, 0xE2, 0x89, 0xBA,
+ 0xB0, 0x05, 0xAC, 0x94, 0x41, 0x9A, 0xA8, 0xFF,
+ 0x3E, 0xBC, 0x3A, 0x52, 0x9C, 0xF9, 0x7F, 0x07,
+ 0x8B, 0xB0, 0x2C, 0x71, 0x83, 0x7B, 0xCF, 0x2E,
+ 0x7F, 0x7C, 0x96, 0x65, 0xD9, 0x08, 0x17, 0xEC,
+ 0xFA, 0xDE, 0x4E, 0x40, 0x12, 0x26, 0x70, 0x71,
+ 0x65, 0xA5, 0xDC, 0x98, 0x47, 0xA3, 0xFC, 0xE0,
+ 0x9A, 0x16, 0xED, 0x45, 0x56, 0x72, 0x50, 0x05,
+ 0x28, 0x2C, 0x99, 0xEC, 0x20, 0x2E, 0x40, 0xC0,
+ 0x26, 0x69, 0xCD, 0x49, 0x45, 0x17, 0xA4, 0xA3,
+ 0x42, 0x0D, 0x14, 0x65, 0x87, 0x33, 0x8C, 0x92,
+ 0xC5, 0xC4, 0x61, 0xFD, 0xE8, 0x68, 0x56, 0x20,
+ 0x57, 0xF5, 0x8E, 0x5F, 0xCF, 0x7E, 0x97, 0xF6,
+ 0x49, 0x97, 0x0A, 0xFE, 0xD3, 0x60, 0x1A, 0x5B,
+ 0x0C, 0x75, 0xDD, 0x8E, 0x31, 0x78, 0x29, 0xA6,
+ 0xB1, 0x4D, 0xAA, 0xDF, 0x8A, 0xD1, 0xE6, 0x91,
+ 0xE3, 0x32, 0x3F, 0xEC, 0x8A, 0x1F, 0x0E, 0x35,
+ 0x07, 0x6E, 0x4B, 0x83, 0x3B, 0xE5, 0x67, 0x34,
+ 0x1F, 0x0C, 0x81, 0xD8, 0xD5, 0x25, 0x68, 0xE5,
+ 0x28, 0x1B, 0x5C, 0x81, 0x3E, 0xE3, 0x5C, 0xB4,
+ 0xB6, 0xBD, 0x62, 0x6A, 0x70, 0x33, 0xC2, 0xC5,
+ 0x75, 0x27, 0xF4, 0x30, 0xE1, 0x1D, 0xC1, 0x4C,
+ 0xC5, 0x02, 0x12, 0x46, 0xAC, 0xEC, 0xF9, 0xE8,
+ 0xE7, 0x58, 0x24, 0x11, 0xB1, 0xF3, 0xB7, 0x8C,
+ 0x3C, 0xA4, 0x0A, 0x94, 0xA6, 0x7C, 0x68, 0x54,
+ 0x5B, 0xB9, 0x4D, 0x57, 0x9C, 0xE7, 0x28, 0x09,
+ 0x6B, 0x89, 0x26, 0x5D, 0xE7, 0x50, 0xA9, 0x95,
+ 0x90, 0x91, 0x8E, 0x00, 0x59, 0xF8, 0x3A, 0x70,
+ 0xAF, 0x48, 0x2E, 0xE8, 0xC4, 0x34, 0x8C, 0xF4,
+ 0x5F, 0x7F, 0xCB, 0x07, 0xAA, 0xF0, 0xD9, 0xFB,
+ 0x5C, 0x32, 0x90, 0x22, 0x1A, 0xD2, 0x1A, 0xCF,
+ 0x92, 0x06, 0x02, 0xCF, 0x10, 0x18, 0x7B, 0x93,
+ 0xCC, 0x07, 0x4A, 0x31, 0x25, 0x30, 0x23, 0x06,
+ 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01,
+ 0x09, 0x15, 0x31, 0x16, 0x04, 0x14, 0xD1, 0xDE,
+ 0x23, 0x16, 0x9F, 0x6E, 0xF4, 0x42, 0x21, 0x23,
+ 0xE1, 0x11, 0xAA, 0xC8, 0x7C, 0x60, 0x4A, 0x78,
+ 0x9D, 0x24, 0x30, 0x31, 0x30, 0x21, 0x30, 0x09,
+ 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05,
+ 0x00, 0x04, 0x14, 0xD6, 0x4A, 0xBB, 0x75, 0xB1,
+ 0xF9, 0x9E, 0xD3, 0x58, 0x6D, 0xD1, 0x74, 0x9F,
+ 0x00, 0x8A, 0xF2, 0xC8, 0xAA, 0x52, 0x4D, 0x04,
+ 0x08, 0x77, 0x46, 0xE7, 0xBA, 0x25, 0x4B, 0xDA,
+ 0x41, 0x02, 0x02, 0x08, 0x00]
+};
+
+const WPA_EAP_CLIENT_LIST = [
+ {
+ ssid: 'WPA-EAP-TTLS',
+ keyManagement: 'WPA-EAP',
+ eap: 'TTLS',
+ identity: EAP_USERNAME,
+ password: EAP_PASSWORD,
+ serverCertificate: CLIENT_PKCS12_CERT.nickname,
+ phase2: 'MSCHAPV2'
+ }
+];
+
+/**
+ * Convert the given MozWifiNetwork object array to testAssociate chain.
+ *
+ * @param aNetworks
+ * An array of MozWifiNetwork which we want to convert.
+ *
+ * @return A promise chain which "then"s testAssociate accordingly.
+ */
+function convertToTestAssociateChain(aNetworks) {
+ let chain = Promise.resolve();
+
+ aNetworks.forEach(function (aNetwork) {
+ network = new window.MozWifiNetwork(aNetwork);
+ chain = chain.then(() => gTestSuite.testAssociate(network));
+ });
+
+ return chain;
+}
+
+gTestSuite.doTestWithCertificate(
+ new Blob([new Uint8Array(CLIENT_PKCS12_CERT.content)]),
+ CLIENT_PKCS12_CERT.password,
+ CLIENT_PKCS12_CERT.nickname,
+ CLIENT_PKCS12_CERT.usage,
+ function() {
+ return gTestSuite.ensureWifiEnabled(true)
+ // Load required server files.
+ .then(() => gTestSuite.writeFile(SERVER_EAP_USER_CONF.path, SERVER_EAP_USER_CONF.content))
+ .then(() => gTestSuite.writeFile(CA_CERT.path, CA_CERT.content))
+ .then(() => gTestSuite.writeFile(SERVER_CERT.path, SERVER_CERT.content))
+ .then(() => gTestSuite.writeFile(SERVER_KEY.path, SERVER_KEY.content))
+ // Start AP.
+ .then(() => gTestSuite.startHostapds(WPA_EAP_AP_LIST))
+ // Scan test.
+ .then(() => gTestSuite.testWifiScanWithRetry(SCAN_RETRY_CNT, WPA_EAP_AP_LIST))
+ // Associate test.
+ .then(() => convertToTestAssociateChain(WPA_EAP_CLIENT_LIST))
+ // Tear down.
+ .then(gTestSuite.killAllHostapd)
+});
diff --git a/dom/wifi/test/marionette/test_wifi_associate_wo_connect.js b/dom/wifi/test/marionette/test_wifi_associate_wo_connect.js
new file mode 100644
index 000000000..3fba0b2c6
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_associate_wo_connect.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+/**
+ * Associate with the given networks but don't connect to it.
+ *
+ * Issue a association-only request, which will not have us connect
+ * to the network. Instead, the network config will be added as the
+ * known networks. After calling the "associate" API, we will wait
+ * a while to make sure the "connected" event is not received.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aNetwork
+ * MozWifiNetwork object.
+ *
+ * @return A deferred promise.
+ */
+function associateButDontConnect(aNetwork) {
+ log('Associating with ' + aNetwork.ssid);
+ aNetwork.dontConnect = true;
+
+ let promises = [];
+ promises.push(gTestSuite.waitForTimeout(10 * 1000)
+ .then(() => { throw 'timeout'; }));
+
+ promises.push(gTestSuite.testAssociate(aNetwork));
+
+ return Promise.all(promises)
+ .then(() => { throw 'unexpected state'; },
+ function(aReason) {
+ is(typeof aReason, 'string', 'typeof aReason');
+ is(aReason, 'timeout', aReason);
+ });
+}
+
+gTestSuite.doTest(function() {
+ let firstNetwork;
+ return gTestSuite.ensureWifiEnabled(true)
+ .then(gTestSuite.requestWifiScan)
+ .then(function(aNetworks) {
+ firstNetwork = aNetworks[0];
+ return associateButDontConnect(firstNetwork);
+ })
+ .then(gTestSuite.getKnownNetworks)
+ .then(function(aKnownNetworks) {
+ is(1, aKnownNetworks.length, 'There should be only one known network!');
+ is(aKnownNetworks[0].ssid, firstNetwork.ssid,
+ 'The only one known network should be ' + firstNetwork.ssid)
+ });
+});
diff --git a/dom/wifi/test/marionette/test_wifi_auto_connect.js b/dom/wifi/test/marionette/test_wifi_auto_connect.js
new file mode 100644
index 000000000..7add9f03d
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_auto_connect.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const TESTING_HOSTAPD = [{ ssid: 'ap0' }];
+
+gTestSuite.doTestWithoutStockAp(function() {
+ let firstNetwork;
+ return gTestSuite.ensureWifiEnabled(true)
+ // Start custom hostapd for testing.
+ .then(() => gTestSuite.startHostapds(TESTING_HOSTAPD))
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd', TESTING_HOSTAPD.length))
+
+ // Request the first scan.
+ .then(gTestSuite.requestWifiScan)
+ .then(function(networks) {
+ firstNetwork = networks[0];
+ return gTestSuite.testAssociate(firstNetwork);
+ })
+
+ // Note that due to Bug 1168285, we need to re-start testing hostapd
+ // after wifi has been re-enabled.
+
+ // Disable wifi and kill running hostapd.
+ .then(() => gTestSuite.requestWifiEnabled(false))
+ .then(gTestSuite.killAllHostapd)
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd', 0))
+
+ // Re-enable wifi.
+ .then(() => gTestSuite.requestWifiEnabled(true))
+
+ // Restart hostapd.
+ .then(() => gTestSuite.startHostapds(TESTING_HOSTAPD))
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd', TESTING_HOSTAPD.length))
+
+ // Wait for connection automatically.
+ .then(() => gTestSuite.waitForConnected(firstNetwork))
+
+ // Kill running hostapd.
+ .then(gTestSuite.killAllHostapd)
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd', 0))
+});
diff --git a/dom/wifi/test/marionette/test_wifi_enable.js b/dom/wifi/test/marionette/test_wifi_enable.js
new file mode 100644
index 000000000..6a1bec7a2
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_enable.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+gTestSuite.doTest(function() {
+ return Promise.resolve()
+ .then(() => gTestSuite.ensureWifiEnabled(false))
+ .then(() => gTestSuite.requestWifiEnabled(true));
+}); \ No newline at end of file
diff --git a/dom/wifi/test/marionette/test_wifi_enable_api.js b/dom/wifi/test/marionette/test_wifi_enable_api.js
new file mode 100644
index 000000000..3728330a2
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_enable_api.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+gTestSuite.doTest(function() {
+ return Promise.resolve()
+ .then(() => gTestSuite.ensureWifiEnabled(false, true))
+ .then(() => gTestSuite.requestWifiEnabled(true, true))
+ .then(() => gTestSuite.requestWifiEnabled(false, true))
+ .then(() => gTestSuite.ensureWifiEnabled(true, true));
+});
diff --git a/dom/wifi/test/marionette/test_wifi_manage_pkcs12_certificate.js b/dom/wifi/test/marionette/test_wifi_manage_pkcs12_certificate.js
new file mode 100644
index 000000000..f85ef00c9
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_manage_pkcs12_certificate.js
@@ -0,0 +1,338 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+// Binary form of test certificate.
+var testCertInfo = {
+ nickname: 'Test Certificate',
+ password: '12345678',
+ usage: ['UserCert', 'ServerCert'],
+ blob: [0x30, 0x82, 0x09, 0xF1, 0x02, 0x01, 0x03, 0x30,
+ 0x82, 0x09, 0xB7, 0x06, 0x09, 0x2A, 0x86, 0x48,
+ 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01, 0xA0, 0x82,
+ 0x09, 0xA8, 0x04, 0x82, 0x09, 0xA4, 0x30, 0x82,
+ 0x09, 0xA0, 0x30, 0x82, 0x06, 0x9F, 0x06, 0x09,
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07,
+ 0x06, 0xA0, 0x82, 0x06, 0x90, 0x30, 0x82, 0x06,
+ 0x8C, 0x02, 0x01, 0x00, 0x30, 0x82, 0x06, 0x85,
+ 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D,
+ 0x01, 0x07, 0x01, 0x30, 0x1C, 0x06, 0x0A, 0x2A,
+ 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01,
+ 0x06, 0x30, 0x0E, 0x04, 0x08, 0x13, 0xB5, 0x2F,
+ 0x5A, 0xB9, 0x49, 0xE6, 0x0B, 0x02, 0x02, 0x08,
+ 0x00, 0x80, 0x82, 0x06, 0x58, 0x35, 0x77, 0x6B,
+ 0xBF, 0x5C, 0x06, 0x09, 0xD8, 0xF0, 0x36, 0x06,
+ 0x69, 0x8D, 0xA2, 0x86, 0xCF, 0x6B, 0x73, 0x86,
+ 0x14, 0xFA, 0x51, 0x9A, 0x87, 0x73, 0x29, 0x71,
+ 0xC5, 0xB1, 0x4F, 0xFB, 0xEC, 0x64, 0x84, 0x20,
+ 0xFC, 0x06, 0x4A, 0x93, 0x74, 0x01, 0xFB, 0xEB,
+ 0x1F, 0xDC, 0xF8, 0xF7, 0xBB, 0xDC, 0x42, 0xA1,
+ 0x4A, 0x71, 0xDE, 0x08, 0x33, 0x7A, 0xCA, 0xD3,
+ 0xD8, 0x40, 0x24, 0x47, 0xAE, 0x41, 0x42, 0x8E,
+ 0xC8, 0x4E, 0xBE, 0x8B, 0xB3, 0xE5, 0x77, 0xAC,
+ 0xBD, 0x98, 0x0C, 0x0E, 0x53, 0xBE, 0x38, 0xB7,
+ 0xEA, 0xD2, 0x29, 0x35, 0xD2, 0xC4, 0xF4, 0xC7,
+ 0xD8, 0xB1, 0x73, 0x2A, 0x13, 0x11, 0x65, 0xF7,
+ 0x0C, 0x8B, 0xC0, 0x43, 0xFB, 0x31, 0x6C, 0xD2,
+ 0xE4, 0x43, 0x85, 0x51, 0x16, 0xBF, 0x35, 0xB5,
+ 0x05, 0x6B, 0x86, 0x11, 0xEA, 0x78, 0x64, 0x9F,
+ 0x42, 0x29, 0xB9, 0x79, 0xAF, 0xB0, 0x7C, 0xBF,
+ 0xC0, 0x89, 0xAD, 0xC7, 0x37, 0xD2, 0x30, 0x8C,
+ 0xDC, 0xF6, 0x77, 0x5E, 0x1F, 0x26, 0x28, 0x8F,
+ 0xAC, 0x19, 0x6C, 0xA0, 0x15, 0xC7, 0x12, 0xA3,
+ 0x0A, 0xD5, 0xC6, 0x15, 0x60, 0x58, 0x16, 0xB8,
+ 0x30, 0x12, 0x3C, 0x78, 0x3C, 0x93, 0x23, 0xA1,
+ 0x56, 0x75, 0x0B, 0x77, 0xAA, 0x0B, 0x0B, 0x2B,
+ 0x91, 0xB6, 0x41, 0xAB, 0xF5, 0x09, 0x4C, 0x1E,
+ 0x36, 0xC0, 0x88, 0xC3, 0x08, 0xF2, 0x65, 0xCB,
+ 0x58, 0x8F, 0x94, 0xB4, 0xB4, 0x05, 0xCC, 0x44,
+ 0x49, 0x73, 0x1B, 0x25, 0x6F, 0x5D, 0x83, 0xBD,
+ 0xF0, 0x70, 0xD0, 0xE8, 0x0D, 0x18, 0x2E, 0x44,
+ 0xD7, 0x89, 0x64, 0x6A, 0xED, 0x23, 0x30, 0xDF,
+ 0xAD, 0x84, 0x3B, 0x74, 0x2C, 0x0D, 0x2B, 0x51,
+ 0x84, 0xA2, 0xA4, 0x9E, 0x42, 0xC3, 0x81, 0x69,
+ 0xFA, 0x56, 0x76, 0x9F, 0xD9, 0x02, 0x64, 0x04,
+ 0xFE, 0xF0, 0xD9, 0x01, 0xBC, 0xE2, 0xC9, 0xDD,
+ 0x88, 0xAC, 0xFA, 0x24, 0x7E, 0xB1, 0xF8, 0x39,
+ 0x27, 0xA2, 0xEB, 0xE4, 0x53, 0xC1, 0xF3, 0xFE,
+ 0x2D, 0x9A, 0x49, 0x73, 0xFF, 0x7C, 0x8E, 0x39,
+ 0xF7, 0x15, 0x27, 0xB3, 0x47, 0x48, 0x92, 0x8C,
+ 0x57, 0x60, 0x9C, 0x97, 0xBA, 0x80, 0xD2, 0x25,
+ 0x80, 0x94, 0xCE, 0x2C, 0x0C, 0x00, 0x44, 0x8C,
+ 0x8C, 0x37, 0x82, 0x5D, 0x5F, 0x62, 0x8B, 0x05,
+ 0x6F, 0xB0, 0x07, 0x34, 0xF9, 0xC3, 0xA1, 0x34,
+ 0x3D, 0xE4, 0x90, 0xB0, 0x03, 0x59, 0x97, 0x6E,
+ 0xFB, 0xF2, 0x92, 0xE5, 0xB5, 0x30, 0x7C, 0x0D,
+ 0x3B, 0x8F, 0x90, 0x8E, 0x04, 0x47, 0x01, 0x0E,
+ 0x88, 0x50, 0x4A, 0x88, 0xA0, 0xFF, 0xB7, 0x9E,
+ 0x2B, 0x2C, 0x98, 0xD0, 0x3E, 0x16, 0x35, 0x5B,
+ 0xD5, 0xEA, 0x54, 0x86, 0xE0, 0xFB, 0x9F, 0x2F,
+ 0x62, 0x89, 0x36, 0x36, 0x9D, 0x6E, 0x62, 0xCB,
+ 0xC8, 0x6C, 0x62, 0x34, 0x8F, 0x66, 0x07, 0x62,
+ 0xA7, 0x00, 0x90, 0x31, 0xFA, 0x5D, 0xDD, 0x12,
+ 0x33, 0x69, 0xD0, 0x74, 0x0E, 0x0B, 0x42, 0x9A,
+ 0xF3, 0x40, 0x7E, 0x3E, 0x48, 0x1D, 0xF2, 0x5C,
+ 0x71, 0x0B, 0x78, 0x7E, 0xD5, 0x15, 0xA4, 0x16,
+ 0x1E, 0xBD, 0x71, 0x18, 0x87, 0x3A, 0xC9, 0xE3,
+ 0x45, 0xEE, 0x70, 0xA2, 0x4C, 0x50, 0xF5, 0x16,
+ 0x5C, 0xF8, 0x76, 0xE6, 0x9F, 0x8D, 0x86, 0x41,
+ 0x7E, 0xF8, 0x60, 0x3D, 0x75, 0x6D, 0x55, 0x96,
+ 0x9E, 0x43, 0x48, 0x82, 0xF7, 0xB6, 0xAC, 0x98,
+ 0x6F, 0x10, 0xAA, 0x20, 0x64, 0xD0, 0x7C, 0x25,
+ 0x24, 0xF7, 0xD8, 0xA4, 0xCC, 0x2D, 0xBF, 0x85,
+ 0x62, 0x6C, 0x4F, 0xFF, 0x9D, 0x71, 0x04, 0x98,
+ 0x61, 0xB0, 0xBC, 0x31, 0xC1, 0xE9, 0xB8, 0x29,
+ 0xA5, 0xEB, 0xD1, 0x1D, 0x65, 0x8E, 0xAE, 0x38,
+ 0x55, 0x65, 0x22, 0xC7, 0xFD, 0x7E, 0xF2, 0x6A,
+ 0xB6, 0xB1, 0x51, 0x37, 0x4B, 0x05, 0x8F, 0xA7,
+ 0x2D, 0x3F, 0x5C, 0x04, 0x2B, 0xBA, 0x2C, 0x37,
+ 0xCA, 0xDE, 0xD5, 0x3E, 0xA0, 0xA5, 0x86, 0x59,
+ 0xA7, 0xD7, 0x38, 0x07, 0xFB, 0x79, 0xF6, 0x2D,
+ 0xE1, 0xAA, 0x7C, 0xD1, 0x91, 0xBE, 0x39, 0xDF,
+ 0x53, 0x3C, 0xD1, 0x44, 0x2C, 0xF9, 0x12, 0x7D,
+ 0xB1, 0xCD, 0xF3, 0x35, 0x1F, 0x85, 0xA6, 0x64,
+ 0x2F, 0xFD, 0x28, 0xF2, 0x85, 0xA8, 0xA7, 0x1F,
+ 0x7F, 0xD9, 0x79, 0x30, 0x9B, 0xFC, 0x69, 0x3A,
+ 0x9B, 0x1F, 0x55, 0x70, 0xC9, 0x60, 0x82, 0x3D,
+ 0xE9, 0x5A, 0x37, 0x5F, 0x8C, 0xBD, 0x19, 0x5D,
+ 0xCC, 0x1C, 0xBE, 0x26, 0x4A, 0xEA, 0x8B, 0x39,
+ 0xCE, 0x0D, 0xBD, 0x63, 0x05, 0x98, 0x75, 0xAB,
+ 0x08, 0x79, 0x90, 0xC7, 0x20, 0xFF, 0xE4, 0x0D,
+ 0xB1, 0xA0, 0x92, 0x2B, 0x0C, 0x4B, 0x0C, 0xDC,
+ 0xB9, 0x72, 0x2A, 0xA4, 0xCC, 0xA6, 0x32, 0xA3,
+ 0x57, 0x82, 0xB4, 0xB9, 0x0F, 0x81, 0xC5, 0xD9,
+ 0x7C, 0xB8, 0x0F, 0x7D, 0xEA, 0x5D, 0xD3, 0xC4,
+ 0x2F, 0x31, 0x79, 0x11, 0xAD, 0x36, 0x56, 0x1F,
+ 0xFA, 0xE3, 0xCE, 0xD2, 0x29, 0x23, 0xE8, 0x2C,
+ 0xDF, 0x7D, 0x94, 0x28, 0x28, 0x9A, 0x0E, 0x64,
+ 0xFC, 0x07, 0x11, 0x96, 0x06, 0x1A, 0x39, 0xCD,
+ 0x04, 0x37, 0x37, 0xDB, 0xFE, 0x68, 0x37, 0xF5,
+ 0x59, 0x54, 0xBC, 0xEF, 0xDB, 0x0C, 0x80, 0xCD,
+ 0xD3, 0x46, 0xA8, 0xA2, 0xBE, 0xE0, 0x63, 0x80,
+ 0xA1, 0x5F, 0x5D, 0xF1, 0xFB, 0x96, 0x8C, 0x06,
+ 0x38, 0xB6, 0xCB, 0x70, 0xB0, 0xFB, 0xD3, 0x26,
+ 0xB3, 0x8B, 0xC6, 0x85, 0x34, 0xB7, 0xAB, 0x5F,
+ 0x7E, 0xC6, 0xAA, 0x79, 0x5B, 0x48, 0x11, 0x65,
+ 0x9E, 0x2A, 0xCD, 0x6A, 0xF0, 0xB2, 0x93, 0xF5,
+ 0x2B, 0x88, 0x45, 0xB7, 0xC9, 0xBE, 0x1A, 0x72,
+ 0x60, 0x62, 0xA4, 0xA5, 0x3B, 0xC2, 0x1C, 0xC6,
+ 0x21, 0x09, 0xA9, 0x40, 0xF6, 0x58, 0x2B, 0xE5,
+ 0x70, 0xDC, 0xFC, 0x47, 0x3B, 0x08, 0xEE, 0xA9,
+ 0x94, 0x26, 0x43, 0xFE, 0xA7, 0x75, 0xD6, 0x4E,
+ 0x52, 0xF6, 0x46, 0xD1, 0x80, 0xEB, 0x3B, 0x8E,
+ 0xBE, 0x54, 0x4F, 0xBD, 0x42, 0x0E, 0x41, 0xF9,
+ 0x36, 0x7D, 0xB6, 0x7F, 0x99, 0x20, 0xC9, 0x63,
+ 0xE7, 0x93, 0x02, 0x62, 0x59, 0x94, 0xCB, 0xC6,
+ 0x62, 0xA9, 0x26, 0xE1, 0x1E, 0x03, 0x5A, 0x41,
+ 0x2F, 0x43, 0x28, 0x75, 0xB7, 0x0C, 0x02, 0x9C,
+ 0x1E, 0xE0, 0x40, 0xB3, 0xE2, 0x9A, 0xED, 0xC6,
+ 0x20, 0x49, 0xEC, 0xDD, 0xC5, 0x64, 0x95, 0x83,
+ 0x51, 0xAE, 0x46, 0x9D, 0x70, 0x17, 0xC6, 0x47,
+ 0xD3, 0x82, 0xCC, 0x6A, 0x5D, 0x93, 0xB2, 0x85,
+ 0x5B, 0x25, 0x05, 0xE7, 0x26, 0x2E, 0xD3, 0xDA,
+ 0x1C, 0xD9, 0x06, 0xB6, 0x16, 0x69, 0x8C, 0x7F,
+ 0xC8, 0xCF, 0x95, 0x18, 0xB5, 0x98, 0xC0, 0x42,
+ 0x61, 0xDE, 0x77, 0x41, 0x3A, 0xF6, 0xE1, 0xB0,
+ 0xE8, 0x64, 0x4A, 0xC2, 0x58, 0xBE, 0x27, 0xC6,
+ 0x9B, 0x0D, 0x47, 0x1A, 0x09, 0x56, 0x7D, 0x2B,
+ 0x19, 0x01, 0x88, 0xC7, 0xFB, 0x1E, 0xCF, 0x5E,
+ 0xF6, 0xB0, 0x82, 0x87, 0xC0, 0xBE, 0xD6, 0xA5,
+ 0xC3, 0xAC, 0x3A, 0x97, 0x88, 0x25, 0x81, 0xAA,
+ 0x3A, 0xCE, 0x66, 0x88, 0x0F, 0xC3, 0x02, 0x50,
+ 0x1C, 0xC3, 0x2B, 0xBC, 0x53, 0x52, 0xFE, 0xD2,
+ 0x3F, 0x50, 0xC8, 0xB2, 0x19, 0x0A, 0x14, 0xB1,
+ 0x73, 0x18, 0xB4, 0xDF, 0xBD, 0xED, 0x43, 0xC5,
+ 0x91, 0xF4, 0x91, 0xBA, 0x7E, 0xB0, 0x7E, 0xA9,
+ 0x43, 0x67, 0x06, 0xCF, 0x51, 0xC1, 0xBF, 0x63,
+ 0x7E, 0x91, 0x76, 0xFF, 0x4F, 0x48, 0x91, 0xCF,
+ 0xDF, 0x01, 0x4D, 0x7E, 0x81, 0x22, 0xFB, 0x79,
+ 0xFC, 0x1D, 0xE3, 0xA7, 0x45, 0x16, 0xFB, 0xF2,
+ 0x83, 0xC7, 0xAE, 0xC6, 0xC5, 0x81, 0xDB, 0xA2,
+ 0x9F, 0x2F, 0xEA, 0xE6, 0x8E, 0x12, 0x8D, 0x43,
+ 0x14, 0x26, 0x25, 0x0E, 0xB4, 0x18, 0xE8, 0x41,
+ 0x84, 0xA3, 0x04, 0xDF, 0x97, 0xFF, 0xAA, 0x45,
+ 0xEC, 0x18, 0xAA, 0xB1, 0xFC, 0xDC, 0xB9, 0xAB,
+ 0xEE, 0xD1, 0xC4, 0x9E, 0x42, 0x3F, 0x5B, 0x8F,
+ 0x9F, 0x22, 0xAF, 0xCC, 0x6F, 0xA0, 0x41, 0x41,
+ 0xCB, 0xD3, 0xAC, 0x96, 0x20, 0xF1, 0x63, 0x56,
+ 0x65, 0xCE, 0x83, 0xC6, 0x62, 0x04, 0x85, 0x16,
+ 0x7F, 0x4E, 0xFB, 0xA0, 0x68, 0x11, 0x85, 0x5B,
+ 0x51, 0xB6, 0x9F, 0xA2, 0xF5, 0xA1, 0xCF, 0x01,
+ 0x9A, 0x80, 0x68, 0xC3, 0xE9, 0x7F, 0x9E, 0x2E,
+ 0x83, 0x84, 0xDC, 0x3C, 0x35, 0xCF, 0x24, 0xBF,
+ 0xF5, 0x00, 0x91, 0x45, 0x14, 0x65, 0xE0, 0xC5,
+ 0x75, 0xDA, 0xEF, 0x14, 0xBD, 0xDB, 0x28, 0x8D,
+ 0x30, 0x96, 0xC9, 0xFE, 0xA8, 0x49, 0x76, 0xC9,
+ 0xED, 0x90, 0x4C, 0x2E, 0xF1, 0x14, 0x2C, 0xF7,
+ 0x13, 0x7C, 0xF1, 0xCC, 0x67, 0xA5, 0x11, 0x55,
+ 0xBD, 0x66, 0x13, 0x8A, 0x76, 0xF9, 0xAC, 0xC9,
+ 0x51, 0x8A, 0xBB, 0x5D, 0x29, 0xEF, 0xF6, 0x37,
+ 0xA0, 0x3E, 0x99, 0x77, 0x6B, 0xE5, 0xCD, 0x06,
+ 0xAC, 0x57, 0x07, 0x37, 0x44, 0x3D, 0x5D, 0xD7,
+ 0xB6, 0x5C, 0xCB, 0x77, 0xD0, 0x4C, 0x28, 0x9D,
+ 0x12, 0x69, 0x5A, 0x68, 0xD1, 0x15, 0x30, 0xC4,
+ 0x30, 0xD2, 0x20, 0xDF, 0xD5, 0x73, 0x9F, 0x83,
+ 0xE9, 0x4C, 0x55, 0xF5, 0xAF, 0xAA, 0x37, 0xF8,
+ 0x28, 0xB6, 0x3F, 0x99, 0x4B, 0x15, 0x1E, 0x40,
+ 0xAB, 0x4F, 0x58, 0x3D, 0x3B, 0x81, 0x7D, 0x62,
+ 0x28, 0x6E, 0x73, 0x58, 0x50, 0x36, 0x49, 0x01,
+ 0xF7, 0x04, 0x3A, 0x23, 0x28, 0xDA, 0x15, 0xC5,
+ 0xE3, 0xF6, 0x6F, 0xE1, 0x79, 0x07, 0xFB, 0xAA,
+ 0xFF, 0x44, 0x48, 0x53, 0x9E, 0x7F, 0x8D, 0x89,
+ 0x88, 0x1A, 0x9A, 0xF9, 0x47, 0x58, 0x20, 0xBB,
+ 0x79, 0x4A, 0x2A, 0x14, 0x03, 0x9B, 0x65, 0x4C,
+ 0x67, 0x02, 0x02, 0xFE, 0xEB, 0xCD, 0xCB, 0x84,
+ 0xF5, 0xCE, 0x32, 0x59, 0xBC, 0xEA, 0xEC, 0xB1,
+ 0x3C, 0x22, 0xCF, 0x9D, 0xB0, 0x34, 0x6D, 0xE6,
+ 0x5A, 0x37, 0xC0, 0x22, 0xAA, 0xF3, 0xB5, 0x71,
+ 0x90, 0x21, 0xE0, 0xB6, 0x19, 0xE9, 0xB3, 0x10,
+ 0xCE, 0x5B, 0xF9, 0xD4, 0x25, 0x30, 0x7D, 0xF6,
+ 0x7D, 0xB6, 0x16, 0xFC, 0x20, 0x3C, 0x2F, 0x96,
+ 0xD5, 0x79, 0x90, 0x88, 0x24, 0x5D, 0x46, 0x64,
+ 0x99, 0xC1, 0xF8, 0x7F, 0x96, 0xA7, 0xB5, 0xA9,
+ 0x47, 0xA3, 0x14, 0xED, 0x93, 0xED, 0x30, 0x56,
+ 0x58, 0xA5, 0xD4, 0x54, 0x2A, 0xF3, 0x89, 0x27,
+ 0x7C, 0x55, 0x41, 0x11, 0x27, 0x9D, 0xF6, 0x4D,
+ 0xA6, 0xB1, 0x00, 0xE0, 0xB0, 0xF6, 0x1E, 0xAB,
+ 0x20, 0x1F, 0xAE, 0x8A, 0x82, 0xA7, 0x49, 0xFC,
+ 0xBB, 0x66, 0xAC, 0x97, 0x95, 0x49, 0x29, 0xCB,
+ 0x6F, 0xF4, 0xC1, 0xB7, 0x6B, 0xF9, 0x8C, 0x25,
+ 0xC6, 0xF0, 0xB5, 0x81, 0xB0, 0xA2, 0x4D, 0xCC,
+ 0x2E, 0xD0, 0x13, 0x5F, 0x96, 0x3F, 0xCD, 0xD0,
+ 0x52, 0xD1, 0xFE, 0xF9, 0xC2, 0x7E, 0x9D, 0xAB,
+ 0xCB, 0x95, 0x0F, 0x27, 0x01, 0x9E, 0x23, 0x6B,
+ 0x19, 0xFF, 0x52, 0x55, 0x71, 0x0A, 0xD4, 0xBB,
+ 0x43, 0x65, 0x29, 0x66, 0xBE, 0x2D, 0x6C, 0xE3,
+ 0x2A, 0x7C, 0xB4, 0x02, 0x32, 0x59, 0x94, 0x80,
+ 0x14, 0xE7, 0x62, 0xE4, 0xCE, 0xB0, 0xCA, 0xCA,
+ 0x37, 0xD7, 0x0C, 0x68, 0x29, 0xE2, 0x92, 0xE1,
+ 0xEB, 0x91, 0xE5, 0xA1, 0x0C, 0xFC, 0x55, 0xCB,
+ 0x56, 0xB3, 0x96, 0xFB, 0x64, 0xD9, 0x53, 0x8E,
+ 0x82, 0x2B, 0xDF, 0x7F, 0xCB, 0x2A, 0xF6, 0x3C,
+ 0xA7, 0x89, 0x52, 0x13, 0x2C, 0x6A, 0x93, 0xA2,
+ 0x74, 0xF5, 0x91, 0x00, 0x17, 0xAA, 0x74, 0x45,
+ 0x63, 0x5A, 0xE5, 0xC3, 0x16, 0xFC, 0x0E, 0xF7,
+ 0xF3, 0xA4, 0x55, 0x8A, 0xA2, 0x60, 0x24, 0x57,
+ 0x25, 0x2C, 0x94, 0xE0, 0xF5, 0x32, 0x54, 0x4A,
+ 0x2D, 0x63, 0x5F, 0xD8, 0x35, 0x96, 0xBD, 0xFE,
+ 0x90, 0x33, 0x17, 0xF6, 0xB5, 0x81, 0x02, 0xFA,
+ 0x5C, 0x94, 0x3A, 0xBE, 0x22, 0xB9, 0xFC, 0x3E,
+ 0x09, 0xE4, 0x76, 0xD7, 0x03, 0x38, 0x38, 0xC2,
+ 0xC2, 0x0D, 0x66, 0x3C, 0xD0, 0x91, 0x5C, 0xF4,
+ 0x0E, 0xC2, 0xDE, 0x46, 0x90, 0x2E, 0xF5, 0x22,
+ 0xA9, 0x3D, 0x15, 0x20, 0x5F, 0x17, 0x10, 0x5A,
+ 0x54, 0x63, 0x93, 0x7B, 0xC3, 0x00, 0x3D, 0x42,
+ 0x73, 0xF1, 0xAD, 0xC1, 0xDE, 0x76, 0x18, 0x9C,
+ 0x68, 0x17, 0xBF, 0x3B, 0xE0, 0x30, 0x82, 0x02,
+ 0xF9, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7,
+ 0x0D, 0x01, 0x07, 0x01, 0xA0, 0x82, 0x02, 0xEA,
+ 0x04, 0x82, 0x02, 0xE6, 0x30, 0x82, 0x02, 0xE2,
+ 0x30, 0x82, 0x02, 0xDE, 0x06, 0x0B, 0x2A, 0x86,
+ 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01,
+ 0x02, 0xA0, 0x82, 0x02, 0xA6, 0x30, 0x82, 0x02,
+ 0xA2, 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48,
+ 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30,
+ 0x0E, 0x04, 0x08, 0x34, 0x37, 0x27, 0x5F, 0xE8,
+ 0xD6, 0x00, 0x0D, 0x02, 0x02, 0x08, 0x00, 0x04,
+ 0x82, 0x02, 0x80, 0xC0, 0xE6, 0xB1, 0x63, 0x73,
+ 0xFC, 0xBF, 0x50, 0xFB, 0x54, 0xCF, 0x67, 0x16,
+ 0xF8, 0x28, 0x48, 0x13, 0x7F, 0xF2, 0xBD, 0x66,
+ 0x70, 0xC7, 0xF6, 0x01, 0xD0, 0x58, 0xF4, 0xA4,
+ 0xD9, 0x45, 0xE2, 0x63, 0x92, 0x7F, 0x78, 0x2B,
+ 0xB6, 0xDB, 0x16, 0x44, 0x1D, 0x11, 0xCB, 0xC3,
+ 0x20, 0xA9, 0x8A, 0x96, 0x13, 0xB8, 0x6E, 0xF3,
+ 0xDA, 0x46, 0x05, 0x2C, 0xF9, 0x67, 0xBB, 0x05,
+ 0x88, 0xC0, 0xC8, 0x60, 0x09, 0xA3, 0x82, 0x27,
+ 0x33, 0xEB, 0xEE, 0x43, 0x98, 0xE9, 0xE2, 0x24,
+ 0xA8, 0x06, 0xD5, 0xFF, 0xF5, 0xC0, 0x79, 0x4B,
+ 0x06, 0x40, 0xE6, 0x28, 0xC6, 0x6E, 0x4E, 0x03,
+ 0xCC, 0x9B, 0xB6, 0xBD, 0xB6, 0x81, 0x88, 0x5C,
+ 0x34, 0x6B, 0x8B, 0x15, 0x23, 0x75, 0x21, 0xAC,
+ 0x79, 0xFD, 0xDB, 0x80, 0x1D, 0x20, 0x84, 0xF1,
+ 0x47, 0xAF, 0x7B, 0x40, 0x6C, 0xD2, 0x64, 0x52,
+ 0x11, 0x1B, 0x01, 0x1E, 0xB5, 0xA9, 0x4B, 0xC4,
+ 0x51, 0x54, 0x40, 0xE2, 0xC8, 0xEB, 0x20, 0x48,
+ 0x2D, 0x40, 0xF8, 0xC6, 0x58, 0x5A, 0xE3, 0x34,
+ 0xD8, 0x79, 0x04, 0xD7, 0xD6, 0x07, 0xF2, 0x12,
+ 0x66, 0xC8, 0x31, 0x37, 0x71, 0x60, 0xF4, 0x75,
+ 0xDC, 0x60, 0x54, 0x19, 0x6A, 0x75, 0x56, 0xC5,
+ 0xA9, 0x67, 0x4A, 0x03, 0x7A, 0xFD, 0x12, 0x59,
+ 0x2B, 0x74, 0xE6, 0xA5, 0xE2, 0xF8, 0xBB, 0x1E,
+ 0x76, 0x96, 0xD4, 0xD4, 0x3F, 0x8B, 0xAD, 0x90,
+ 0xAF, 0x04, 0x41, 0xDB, 0xD8, 0xCC, 0x2D, 0x37,
+ 0x06, 0x20, 0x9B, 0xE7, 0x98, 0x87, 0x12, 0xAC,
+ 0x70, 0xC9, 0xF4, 0x1C, 0x28, 0xFB, 0x2C, 0x9E,
+ 0x18, 0xE1, 0x6D, 0x79, 0x34, 0xBC, 0xAC, 0xCB,
+ 0x75, 0x92, 0x7E, 0x8E, 0x7C, 0xA9, 0x0B, 0x86,
+ 0x4A, 0x88, 0xFB, 0xE8, 0xBE, 0x6A, 0x32, 0xEF,
+ 0x58, 0xCC, 0x4C, 0x89, 0x50, 0xEF, 0xDF, 0xE0,
+ 0xDD, 0x35, 0x07, 0x8C, 0x01, 0x8B, 0x57, 0x38,
+ 0xB5, 0x64, 0xBB, 0x0A, 0xC2, 0xD8, 0xAB, 0xC5,
+ 0x45, 0xA6, 0x83, 0xBF, 0xA6, 0xA2, 0xCC, 0x06,
+ 0x64, 0xBE, 0x84, 0x04, 0x55, 0x8E, 0xF4, 0x4C,
+ 0xB5, 0xBC, 0xE5, 0x97, 0x2B, 0x3C, 0x42, 0x44,
+ 0x91, 0x9B, 0xB2, 0x65, 0x70, 0x02, 0xC5, 0xB7,
+ 0x71, 0xB3, 0xF0, 0xAA, 0x46, 0x4F, 0x42, 0x40,
+ 0x53, 0x65, 0x89, 0xA5, 0x6C, 0xBC, 0xB5, 0x6C,
+ 0x0C, 0x3B, 0x50, 0x46, 0x67, 0xFA, 0x14, 0x68,
+ 0x01, 0xE7, 0xA6, 0xD4, 0xB5, 0xD0, 0x82, 0x44,
+ 0x92, 0x2C, 0xE3, 0x43, 0x5D, 0x34, 0x7C, 0x04,
+ 0xA3, 0x4D, 0x2F, 0x5A, 0x75, 0xE7, 0x0B, 0x64,
+ 0xD0, 0xAE, 0x7F, 0xCB, 0xDD, 0x7D, 0x05, 0x88,
+ 0x4C, 0x34, 0xBB, 0xF4, 0x00, 0xCE, 0x1C, 0x13,
+ 0x4E, 0xA3, 0xE3, 0x60, 0x4B, 0x50, 0x4E, 0xE1,
+ 0x26, 0x22, 0x51, 0xD4, 0x32, 0x60, 0xC6, 0x3E,
+ 0x7D, 0x4A, 0x3E, 0x56, 0x78, 0xBD, 0x5F, 0x23,
+ 0x7F, 0x0A, 0xA0, 0xC1, 0x1A, 0x60, 0xA2, 0x7C,
+ 0x9E, 0x17, 0x6F, 0xD8, 0x73, 0x0F, 0x1A, 0x1F,
+ 0x47, 0x58, 0x44, 0x20, 0x80, 0xC6, 0x5D, 0x6E,
+ 0xEC, 0xFF, 0xCA, 0x65, 0xA1, 0xFB, 0xEE, 0xF2,
+ 0x56, 0x1A, 0x16, 0x9E, 0x4D, 0xCA, 0x67, 0x81,
+ 0x23, 0xDE, 0xBE, 0x5E, 0x31, 0x56, 0xF0, 0x34,
+ 0xBA, 0x12, 0xFC, 0x07, 0x03, 0x96, 0xD2, 0x8E,
+ 0xCE, 0xA6, 0xF6, 0x74, 0x07, 0x4F, 0x63, 0x40,
+ 0x14, 0x0A, 0xD6, 0x45, 0xB4, 0xF1, 0x72, 0x87,
+ 0x34, 0x89, 0x5C, 0x06, 0x1B, 0x8C, 0x0E, 0xA2,
+ 0x84, 0x50, 0x12, 0xAD, 0x26, 0x5B, 0x4F, 0x6B,
+ 0x23, 0x9D, 0x3C, 0xBB, 0x8A, 0xDA, 0x08, 0x4B,
+ 0x93, 0x47, 0x02, 0x96, 0x76, 0xD4, 0x87, 0xE9,
+ 0x4B, 0x69, 0x82, 0xD6, 0xCC, 0x69, 0x02, 0xC0,
+ 0xA4, 0x75, 0x7A, 0x90, 0xFD, 0xF6, 0xD6, 0x9D,
+ 0xE2, 0x4C, 0xB6, 0xFA, 0x61, 0xA5, 0x7C, 0x18,
+ 0xEA, 0x84, 0xA1, 0x74, 0x85, 0x2E, 0xCA, 0xF9,
+ 0x17, 0x29, 0xFF, 0x67, 0x70, 0xC9, 0x6F, 0xF1,
+ 0x41, 0xEF, 0xA1, 0x59, 0x54, 0xA0, 0x99, 0x14,
+ 0x48, 0x74, 0x5D, 0x14, 0x3E, 0x04, 0xCE, 0xF7,
+ 0x16, 0x9F, 0x8A, 0x41, 0xF4, 0xAE, 0xB3, 0x10,
+ 0xCE, 0x19, 0xC2, 0x83, 0x7B, 0xD0, 0x26, 0x1E,
+ 0x75, 0x8A, 0x0A, 0x40, 0x4A, 0xB8, 0xE0, 0x5C,
+ 0x13, 0x8B, 0xCC, 0x6F, 0xF3, 0x00, 0xB3, 0x64,
+ 0x1B, 0x3C, 0x3D, 0x08, 0x3B, 0x9F, 0xD0, 0x9B,
+ 0xE5, 0x72, 0x45, 0x96, 0x95, 0x4D, 0x66, 0xC7,
+ 0x79, 0x5D, 0x3A, 0x1A, 0x94, 0x64, 0x94, 0x07,
+ 0x1A, 0xE8, 0x7C, 0xD1, 0x1C, 0xB1, 0x7E, 0x32,
+ 0x28, 0x1A, 0x90, 0x22, 0xD9, 0x86, 0x9B, 0x9C,
+ 0x9B, 0x0C, 0x04, 0x31, 0x85, 0x10, 0x42, 0x50,
+ 0x40, 0x11, 0x72, 0xAB, 0x94, 0x0C, 0xAF, 0xC3,
+ 0x22, 0x1A, 0xC1, 0x31, 0x25, 0x30, 0x23, 0x06,
+ 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01,
+ 0x09, 0x15, 0x31, 0x16, 0x04, 0x14, 0xFD, 0x78,
+ 0xA7, 0x70, 0x1F, 0x8A, 0xE9, 0x07, 0xB9, 0xCA,
+ 0x3C, 0xD1, 0xE8, 0xDC, 0x68, 0xFF, 0x02, 0x61,
+ 0x29, 0x97, 0x30, 0x31, 0x30, 0x21, 0x30, 0x09,
+ 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05,
+ 0x00, 0x04, 0x14, 0x22, 0x7E, 0x99, 0x10, 0xB3,
+ 0x99, 0x79, 0xE7, 0x14, 0x7F, 0x91, 0x59, 0x24,
+ 0x4F, 0x2F, 0xCF, 0xE8, 0x53, 0x1D, 0x0F, 0x04,
+ 0x08, 0x30, 0x1E, 0x5C, 0xE4, 0x3C, 0x66, 0xDF,
+ 0xB0, 0x02, 0x02, 0x08, 0x00]
+};
+
+gTestSuite.doTestWithCertificate(
+ new Blob([new Uint8Array(testCertInfo.blob)]),
+ testCertInfo.password,
+ testCertInfo.nickname,
+ testCertInfo.usage
+);
diff --git a/dom/wifi/test/marionette/test_wifi_manage_server_certificate.js b/dom/wifi/test/marionette/test_wifi_manage_server_certificate.js
new file mode 100644
index 000000000..dfaf524b2
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_manage_server_certificate.js
@@ -0,0 +1,106 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+// Binary form of test certificate.
+var testCertInfo = {
+ nickname: 'Test Certificate',
+ password: '',
+ usage: ['ServerCert'],
+ blob: [0x30, 0x82, 0x02, 0xae, 0x30, 0x82, 0x02, 0x17,
+ 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x09, 0x00,
+ 0x92, 0x49, 0xe2, 0x62, 0x71, 0xf6, 0xc7, 0x92,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30,
+ 0x70, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+ 0x04, 0x06, 0x13, 0x02, 0x54, 0x57, 0x31, 0x0f,
+ 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c,
+ 0x06, 0x54, 0x61, 0x69, 0x70, 0x65, 0x69, 0x31,
+ 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x0c, 0x07, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c,
+ 0x61, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+ 0x04, 0x0b, 0x0c, 0x02, 0x51, 0x41, 0x31, 0x0e,
+ 0x30, 0x0c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c,
+ 0x05, 0x47, 0x65, 0x72, 0x72, 0x79, 0x31, 0x21,
+ 0x30, 0x1f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x12, 0x67,
+ 0x63, 0x68, 0x61, 0x6e, 0x67, 0x40, 0x6d, 0x6f,
+ 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30,
+ 0x35, 0x32, 0x33, 0x30, 0x39, 0x34, 0x32, 0x33,
+ 0x37, 0x5a, 0x17, 0x0d, 0x31, 0x37, 0x30, 0x35,
+ 0x32, 0x32, 0x30, 0x39, 0x34, 0x32, 0x33, 0x37,
+ 0x5a, 0x30, 0x70, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x54, 0x57,
+ 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04,
+ 0x08, 0x0c, 0x06, 0x54, 0x61, 0x69, 0x70, 0x65,
+ 0x69, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x0c, 0x07, 0x4d, 0x6f, 0x7a, 0x69,
+ 0x6c, 0x6c, 0x61, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x02, 0x51, 0x41,
+ 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x0c, 0x05, 0x47, 0x65, 0x72, 0x72, 0x79,
+ 0x31, 0x21, 0x30, 0x1f, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16,
+ 0x12, 0x67, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x40,
+ 0x6d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x30, 0x81, 0x9f, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d,
+ 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00,
+ 0xd3, 0xdb, 0x54, 0xcc, 0xca, 0x0b, 0xee, 0xf9,
+ 0x8a, 0x37, 0x0d, 0x06, 0x8b, 0x20, 0x00, 0x4a,
+ 0x55, 0x84, 0x90, 0x1a, 0xb7, 0x9c, 0x91, 0xb2,
+ 0x38, 0x6b, 0x8b, 0x32, 0x7a, 0x89, 0x9e, 0x79,
+ 0x71, 0x88, 0x43, 0x21, 0x94, 0x18, 0xa8, 0xfc,
+ 0xe3, 0x7a, 0x8a, 0xb3, 0xa1, 0xf7, 0x23, 0xe7,
+ 0x1a, 0xe3, 0xe7, 0x0d, 0xf1, 0x66, 0x21, 0x58,
+ 0x21, 0x85, 0x5b, 0x35, 0xec, 0x68, 0xd2, 0xfd,
+ 0x44, 0x76, 0x93, 0x05, 0xbb, 0x89, 0x7d, 0x92,
+ 0xf9, 0xce, 0x75, 0xa3, 0xeb, 0x39, 0xc1, 0x7d,
+ 0x7e, 0x50, 0xf9, 0xb8, 0x60, 0x61, 0xf7, 0x2f,
+ 0x54, 0x39, 0xfe, 0x8a, 0x20, 0xb2, 0x0d, 0x48,
+ 0x7f, 0x18, 0x0d, 0x02, 0xcc, 0x7b, 0x8e, 0x31,
+ 0xe9, 0xbe, 0xfc, 0x96, 0x2e, 0x63, 0x6f, 0xfa,
+ 0x4c, 0xc4, 0xcf, 0x8a, 0xe4, 0x13, 0x67, 0xf1,
+ 0xec, 0x3e, 0xd8, 0x23, 0xa1, 0xbf, 0x67, 0x71,
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x50, 0x30,
+ 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+ 0x04, 0x16, 0x04, 0x14, 0x13, 0xe1, 0xac, 0xa4,
+ 0x75, 0x3d, 0x2c, 0x5f, 0xe5, 0x41, 0x42, 0x90,
+ 0x5a, 0x48, 0x5c, 0x46, 0xbc, 0x24, 0x8e, 0xa1,
+ 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0x13, 0xe1, 0xac,
+ 0xa4, 0x75, 0x3d, 0x2c, 0x5f, 0xe5, 0x41, 0x42,
+ 0x90, 0x5a, 0x48, 0x5c, 0x46, 0xbc, 0x24, 0x8e,
+ 0xa1, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81,
+ 0x81, 0x00, 0xaa, 0x6b, 0x62, 0x53, 0x74, 0x2a,
+ 0x20, 0x76, 0xab, 0xd2, 0x60, 0x06, 0xfd, 0x88,
+ 0xf5, 0x1c, 0x85, 0xe6, 0x57, 0xf1, 0xf0, 0x18,
+ 0x97, 0x7c, 0x70, 0xb8, 0xb4, 0x7c, 0xcc, 0x58,
+ 0x8d, 0xf4, 0x7c, 0xb6, 0x34, 0xcc, 0x15, 0x79,
+ 0xaf, 0x75, 0xa9, 0x0b, 0xd1, 0xea, 0xf8, 0x85,
+ 0x7c, 0xe8, 0x19, 0xe9, 0x13, 0x90, 0x84, 0x5f,
+ 0x21, 0x94, 0x0a, 0x4d, 0x15, 0xef, 0xd1, 0x16,
+ 0xd4, 0xba, 0x2c, 0x59, 0x1b, 0x83, 0x23, 0xf5,
+ 0xa5, 0xcd, 0xbd, 0xda, 0x32, 0x73, 0x46, 0x49,
+ 0x98, 0xf3, 0xfb, 0x50, 0x6e, 0x30, 0xd7, 0x3e,
+ 0x31, 0xd6, 0xe8, 0x65, 0x2f, 0x5a, 0xf1, 0x0f,
+ 0x7b, 0x0a, 0x21, 0x61, 0x8e, 0x45, 0x29, 0x4f,
+ 0x7a, 0x04, 0xda, 0x29, 0xfc, 0x6f, 0xc5, 0x5e,
+ 0xee, 0xe1, 0x0f, 0xd5, 0x4b, 0xb7, 0xc9, 0x6a,
+ 0x8e, 0x7c, 0x19, 0xef, 0x6e, 0x64, 0x98, 0xfe,
+ 0xe3, 0x35]
+};
+
+gTestSuite.doTestWithCertificate(
+ new Blob([new Uint8Array(testCertInfo.blob)]),
+ testCertInfo.password,
+ testCertInfo.nickname,
+ testCertInfo.usage
+);
diff --git a/dom/wifi/test/marionette/test_wifi_manage_user_certificate.js b/dom/wifi/test/marionette/test_wifi_manage_user_certificate.js
new file mode 100644
index 000000000..950ceae4e
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_manage_user_certificate.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+// Binary form of test certificate.
+var testCertInfo = {
+ nickname: 'Test Certificate',
+ password: '',
+ usage: ['UserCert'],
+ blob: '-----BEGIN CERTIFICATE-----\n' +
+ 'MIICTjCCAbegAwIBAgICNV4wDQYJKoZIhvcNAQEEBQAwgYUxCzAJBgNVBAYTAklU\n' +
+ 'MRYwFAYDVQQKEw1aZXJvc2hlbGwubmV0MRAwDgYDVQQLEwdFeGFtcGxlMR0wGwYD\n' +
+ 'VQQDExRaZXJvU2hlbGwgRXhhbXBsZSBDQTEtMCsGCSqGSIb3DQEJARYeRnVsdmlv\n' +
+ 'LlJpY2NpYXJkaUB6ZXJvc2hlbGwubmV0MB4XDTEzMDMxMTAzMzg1MloXDTE0MDMx\n' +
+ 'MTAzMzg1MlowIzEOMAwGA1UECxMFVXNlcnMxETAPBgNVBAMTCGNodWNrbGVlMIGf\n' +
+ 'MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvVzFhQAVqAIHW5DlAhp4FEGEei7k7\n' +
+ 'uVUeqkH7JAsww6zmDLg9yZlcZAc95N0lkz022gLXehH2M0R1FOR++nkqofzWfc7w\n' +
+ 'n79ith+dU2GQMeKq7vPGDYXpgIkEKbYfzKj3fY3129MlTxJQt1UD/ejz38V8HKgw\n' +
+ 'qKSuwo0NVeY66QIDAQABoy4wLDALBgNVHQ8EBAMCBLAwHQYDVR0lBBYwFAYIKwYB\n' +
+ 'BQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBBAUAA4GBAJWgfX5vYSD7MGZk1rTF\n' +
+ 'DSziWYGqpR+Moo3qQ+9qLG8m+XVM9hckWpY31A5sWAeCZCe1SSNLFbbgsaOyPZE2\n' +
+ 'NqMyvs61Vszpc2mmWAYT6j2OU2tw8p5pcUZd6eIp7Gc3fLymiX/WoSmilZKmrGUZ\n' +
+ 'Q15R+TCpclUsaNrUGjybgaw7\n' +
+ '-----END CERTIFICATE-----'
+};
+
+gTestSuite.doTestWithCertificate(
+ new Blob([testCertInfo.blob]),
+ testCertInfo.password,
+ testCertInfo.nickname,
+ testCertInfo.usage
+);
diff --git a/dom/wifi/test/marionette/test_wifi_scan.js b/dom/wifi/test/marionette/test_wifi_scan.js
new file mode 100644
index 000000000..4bb7d1a3d
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_scan.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const SCAN_RETRY_CNT = 5;
+
+/**
+ * Test scan with no AP present.
+ *
+ * The precondition is:
+ * 1) Wifi is enabled.
+ * 2) All the hostapds are turned off.
+ *
+ * @return deferred promise.
+ */
+function testScanNoAp() {
+ return gTestSuite.testWifiScanWithRetry(SCAN_RETRY_CNT, []);
+}
+
+/**
+ * Test scan with APs present.
+ *
+ * The precondition is:
+ * 1) Wifi is enabled.
+ * 2) All the hostapds are turned off.
+ *
+ * @return deferred promise.
+ */
+function testScanWithAps() {
+ return gTestSuite.startHostapds(HOSTAPD_CONFIG_LIST)
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd', HOSTAPD_CONFIG_LIST.length))
+ .then(() => gTestSuite.testWifiScanWithRetry(SCAN_RETRY_CNT, HOSTAPD_CONFIG_LIST))
+ .then(gTestSuite.killAllHostapd)
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd', 0));
+}
+
+gTestSuite.doTestWithoutStockAp(function() {
+ return gTestSuite.ensureWifiEnabled(true)
+ .then(testScanNoAp)
+ .then(testScanWithAps);
+});
diff --git a/dom/wifi/test/marionette/test_wifi_static_ip.js b/dom/wifi/test/marionette/test_wifi_static_ip.js
new file mode 100644
index 000000000..90436b6fe
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_static_ip.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const STATIC_IP_CONFIG = {
+ enabled: true,
+ ipaddr: "192.168.111.222",
+ proxy: "",
+ maskLength: 24,
+ gateway: "192.168.111.1",
+ dns1: "8.8.8.8",
+ dns2: "8.8.4.4",
+};
+
+const TESTING_HOSTAPD = [{ ssid: 'ap0' }];
+
+function testAssociateWithStaticIp(aNetwork, aStaticIpConfig) {
+ return gTestSuite.setStaticIpMode(aNetwork, aStaticIpConfig)
+ .then(() => gTestSuite.testAssociate(aNetwork))
+ // Check ip address and prefix.
+ .then(() => gTestSuite.exeAndParseNetcfg())
+ .then((aResult) => {
+ is(aResult["wlan0"].ip, aStaticIpConfig.ipaddr, "Check ip address");
+ is(aResult["wlan0"].prefix, aStaticIpConfig.maskLength, "Check prefix");
+ })
+ // Check routing.
+ .then(() => gTestSuite.exeAndParseIpRoute())
+ .then((aResult) => {
+ is(aResult["wlan0"].src, aStaticIpConfig.ipaddr, "Check ip address");
+ is(aResult["wlan0"].default, true, "Check default route");
+ is(aResult["wlan0"].gateway, aStaticIpConfig.gateway, "Check gateway");
+ });
+}
+
+function findDesireNetwork(aNetworks) {
+ let i = gTestSuite.getFirstIndexBySsid(TESTING_HOSTAPD[0].ssid, aNetworks);
+
+ if (-1 !== i) {
+ return aNetworks[i];
+ }
+
+ return aNetworks[0];
+}
+
+// Start test.
+gTestSuite.doTestWithoutStockAp(function() {
+ return gTestSuite.ensureWifiEnabled(true)
+
+ // Start custom hostapd for testing.
+ .then(() => gTestSuite.startHostapds(TESTING_HOSTAPD))
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd',
+ TESTING_HOSTAPD.length))
+
+ // Perform a wifi scan, and then run the static ip test
+ .then(() => gTestSuite.requestWifiScan())
+ .then((aNetworks) => findDesireNetwork(aNetworks))
+ .then((aNetwork) => testAssociateWithStaticIp(aNetwork,
+ STATIC_IP_CONFIG))
+
+ // Kill running hostapd.
+ .then(gTestSuite.killAllHostapd)
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd', 0));
+});
diff --git a/dom/wifi/test/marionette/test_wifi_tethering_wifi_active.js b/dom/wifi/test/marionette/test_wifi_tethering_wifi_active.js
new file mode 100644
index 000000000..af0df01a0
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_tethering_wifi_active.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const TESTING_HOSTAPD = [{ ssid: 'ap0' }];
+
+function connectToFirstNetwork() {
+ let firstNetwork;
+ return gTestSuite.requestWifiScan()
+ .then(function (networks) {
+ firstNetwork = networks[0];
+ return gTestSuite.testAssociate(firstNetwork);
+ })
+ .then(() => firstNetwork);
+}
+
+gTestSuite.doTestTethering(function() {
+ let firstNetwork;
+
+ return gTestSuite.ensureWifiEnabled(true)
+ // Start custom hostapd for testing.
+ .then(() => gTestSuite.startHostapds(TESTING_HOSTAPD))
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd', TESTING_HOSTAPD.length))
+
+ // Connect to the testing AP and wait for data becoming disconnected.
+ .then(function () {
+ return Promise.all([
+ // 1) Set up the event listener first:
+ // RIL data should become disconnected after connecting to wifi.
+ gTestSuite.waitForRilDataConnected(false),
+
+ // 2) Connect to the first scanned network.
+ connectToFirstNetwork()
+ .then(aFirstNetwork => firstNetwork = aFirstNetwork)
+ ]);
+ })
+ .then(function() {
+ return Promise.all([
+ // 1) Set up the event listeners first:
+ // Wifi should be turned off and RIL data should be connected.
+ gTestSuite.waitForWifiManagerEventOnce('disabled'),
+ gTestSuite.waitForRilDataConnected(true),
+
+ // 2) Start wifi tethering.
+ gTestSuite.requestTetheringEnabled(true)
+ ]);
+ })
+ .then(function() {
+ return Promise.all([
+ // 1) Set up the event listeners first:
+ // Wifi should be enabled, RIL data should become disconnected and
+ // we should connect to an wifi AP.
+ gTestSuite.waitForWifiManagerEventOnce('enabled'),
+
+ // Due to Bug 1168285, after re-enablin wifi, the remembered network
+ // will not be connected automatically. Leave "auto connect test"
+ // covered by test_wifi_auto_connect.js.
+ //gTestSuite.waitForRilDataConnected(false),
+ //gTestSuite.waitForConnected(firstNetwork),
+
+ // 2) Stop wifi tethering.
+ gTestSuite.requestTetheringEnabled(false)
+ ]);
+ })
+ // Remove wlan0 from default route by disabling wifi. Otherwise,
+ // it will cause the subsequent test cases loading page error.
+ .then(() => gTestSuite.requestWifiEnabled(false))
+
+ // Kill running hostapd.
+ .then(gTestSuite.killAllHostapd)
+ .then(() => gTestSuite.verifyNumOfProcesses('hostapd', 0));
+}); \ No newline at end of file
diff --git a/dom/wifi/test/marionette/test_wifi_tethering_wifi_disabled.js b/dom/wifi/test/marionette/test_wifi_tethering_wifi_disabled.js
new file mode 100644
index 000000000..860a91cf6
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_tethering_wifi_disabled.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+gTestSuite.doTestTethering(function() {
+ return gTestSuite.ensureWifiEnabled(false)
+ .then(() => gTestSuite.requestTetheringEnabled(true))
+ .then(() => gTestSuite.requestTetheringEnabled(false))
+});
diff --git a/dom/wifi/test/marionette/test_wifi_tethering_wifi_inactive.js b/dom/wifi/test/marionette/test_wifi_tethering_wifi_inactive.js
new file mode 100644
index 000000000..2de6ab667
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_tethering_wifi_inactive.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+gTestSuite.doTestTethering(function() {
+ return gTestSuite.ensureWifiEnabled(true)
+ .then(function() {
+ return Promise.all([
+ gTestSuite.waitForWifiManagerEventOnce('disabled'),
+ gTestSuite.requestTetheringEnabled(true)
+ ]);
+ })
+ .then(function() {
+ return Promise.all([
+ gTestSuite.waitForWifiManagerEventOnce('enabled'),
+ gTestSuite.requestTetheringEnabled(false)
+ ]);
+ });
+});