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/WifiP2pManager.jsm | |
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/WifiP2pManager.jsm')
-rw-r--r-- | dom/wifi/WifiP2pManager.jsm | 1649 |
1 files changed, 1649 insertions, 0 deletions
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; |