diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/wifi | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/wifi')
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) + ]); + }); +}); |