michael@0: /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/StateMachine.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/systemlibs.js"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gSysMsgr", michael@0: "@mozilla.org/system-message-internal;1", michael@0: "nsISystemMessagesInternal"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager", michael@0: "@mozilla.org/network/manager;1", michael@0: "nsINetworkManager"); michael@0: michael@0: const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed"; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["WifiP2pManager"]; michael@0: michael@0: const EVENT_IGNORED = -1; michael@0: const EVENT_UNKNOWN = -2; michael@0: michael@0: // Events from supplicant for p2p. michael@0: const EVENT_P2P_DEVICE_FOUND = 0; michael@0: const EVENT_P2P_DEVICE_LOST = 1; michael@0: const EVENT_P2P_GROUP_STARTED = 2; michael@0: const EVENT_P2P_GROUP_REMOVED = 3; michael@0: const EVENT_P2P_PROV_DISC_PBC_REQ = 4; michael@0: const EVENT_P2P_PROV_DISC_PBC_RESP = 5; michael@0: const EVENT_P2P_PROV_DISC_SHOW_PIN = 6; michael@0: const EVENT_P2P_PROV_DISC_ENTER_PIN = 7; michael@0: const EVENT_P2P_GO_NEG_REQUEST = 8; michael@0: const EVENT_P2P_GO_NEG_SUCCESS = 9; michael@0: const EVENT_P2P_GO_NEG_FAILURE = 10; michael@0: const EVENT_P2P_GROUP_FORMATION_SUCCESS = 11; michael@0: const EVENT_P2P_GROUP_FORMATION_FAILURE = 12; michael@0: const EVENT_P2P_FIND_STOPPED = 13; michael@0: const EVENT_P2P_INVITATION_RESULT = 14; michael@0: const EVENT_P2P_INVITATION_RECEIVED = 15; michael@0: const EVENT_P2P_PROV_DISC_FAILURE = 16; michael@0: michael@0: // Events from supplicant but not p2p specific. michael@0: const EVENT_AP_STA_DISCONNECTED = 100; michael@0: const EVENT_AP_STA_CONNECTED = 101; michael@0: michael@0: // Events from DOM. michael@0: const EVENT_P2P_SET_PAIRING_CONFIRMATION = 1000; michael@0: const EVENT_P2P_CMD_CONNECT = 1001; michael@0: const EVENT_P2P_CMD_DISCONNECT = 1002; michael@0: const EVENT_P2P_CMD_ENABLE = 1003; michael@0: const EVENT_P2P_CMD_DISABLE = 1004; michael@0: const EVENT_P2P_CMD_ENABLE_SCAN = 1005; michael@0: const EVENT_P2P_CMD_DISABLE_SCAN = 1006; michael@0: const EVENT_P2P_CMD_BLOCK_SCAN = 1007; michael@0: const EVENT_P2P_CMD_UNBLOCK_SCAN = 1008; michael@0: michael@0: // Internal events. michael@0: const EVENT_TIMEOUT_PAIRING_CONFIRMATION = 10000; michael@0: const EVENT_TIMEOUT_NEG_REQ = 10001; michael@0: const EVENT_TIMEOUT_CONNECTING = 10002; michael@0: const EVENT_P2P_ENABLE_SUCCESS = 10003; michael@0: const EVENT_P2P_ENABLE_FAILED = 10004; michael@0: const EVENT_P2P_DISABLE_SUCCESS = 10005; michael@0: michael@0: // WPS method string. michael@0: const WPS_METHOD_PBC = "pbc"; michael@0: const WPS_METHOD_DISPLAY = "display"; michael@0: const WPS_METHOD_KEYPAD = "keypad"; michael@0: michael@0: // Role string. michael@0: const P2P_ROLE_GO = "GO"; michael@0: const P2P_ROLE_CLIENT = "client"; michael@0: michael@0: // System message for pairing request. michael@0: const PAIRING_REQUEST_SYS_MSG = "wifip2p-pairing-request"; michael@0: michael@0: // Configuration. michael@0: const P2P_INTERFACE_NAME = "p2p0"; michael@0: const DEFAULT_GO_INTENT = 15; michael@0: const DEFAULT_P2P_DEVICE_NAME = "FirefoxPhone"; michael@0: const P2P_SCAN_TIMEOUT_SEC = 120; michael@0: const DEFAULT_P2P_WPS_METHODS = "virtual_push_button physical_display keypad"; // For wpa_supplicant. michael@0: const DEFAULT_P2P_DEVICE_TYPE = "10-0050F204-5"; // For wpa_supplicant. michael@0: michael@0: const GO_NETWORK_INTERFACE = { michael@0: ip: "192.168.2.1", michael@0: maskLength: 24, michael@0: gateway: "192.168.2.1", michael@0: dns1: "0.0.0.0", michael@0: dns2: "0.0.0.0", michael@0: dhcpServer: "192.168.2.1" michael@0: }; michael@0: michael@0: const GO_DHCP_SERVER_IP_RANGE = { michael@0: startIp: "192.168.2.10", michael@0: endIp: "192.168.2.30" michael@0: }; michael@0: michael@0: let gDebug = false; michael@0: michael@0: // Device Capability bitmap michael@0: const DEVICE_CAPAB_SERVICE_DISCOVERY = 1; michael@0: const DEVICE_CAPAB_CLIENT_DISCOVERABILITY = 1<<1; michael@0: const DEVICE_CAPAB_CONCURRENT_OPER = 1<<2; michael@0: const DEVICE_CAPAB_INFRA_MANAGED = 1<<3; michael@0: const DEVICE_CAPAB_DEVICE_LIMIT = 1<<4; michael@0: const DEVICE_CAPAB_INVITATION_PROCEDURE = 1<<5; michael@0: michael@0: // Group Capability bitmap michael@0: const GROUP_CAPAB_GROUP_OWNER = 1; michael@0: const GROUP_CAPAB_PERSISTENT_GROUP = 1<<1; michael@0: const GROUP_CAPAB_GROUP_LIMIT = 1<<2; michael@0: const GROUP_CAPAB_INTRA_BSS_DIST = 1<<3; michael@0: const GROUP_CAPAB_CROSS_CONN = 1<<4; michael@0: const GROUP_CAPAB_PERSISTENT_RECONN = 1<<5; michael@0: const GROUP_CAPAB_GROUP_FORMATION = 1<<6; michael@0: michael@0: // Constants defined in wpa_supplicants. michael@0: const DEV_PW_REGISTRAR_SPECIFIED = 5; michael@0: const DEV_PW_USER_SPECIFIED = 1; michael@0: const DEV_PW_PUSHBUTTON = 4; michael@0: michael@0: this.WifiP2pManager = function (aP2pCommand, aNetUtil) { michael@0: function debug(aMsg) { michael@0: if (gDebug) { michael@0: dump('-------------- WifiP2pManager: ' + aMsg); michael@0: } michael@0: } michael@0: michael@0: let manager = {}; michael@0: michael@0: let _stateMachine = P2pStateMachine(aP2pCommand, aNetUtil); michael@0: michael@0: // Set debug flag to true or false. michael@0: // michael@0: // @param aDebug Boolean to indicate enabling or disabling the debug flag. michael@0: manager.setDebug = function(aDebug) { michael@0: gDebug = aDebug; michael@0: }; michael@0: michael@0: // Set observer of observing internal state machine events. michael@0: // michael@0: // @param aObserver Used to notify WifiWorker what's happening michael@0: // in the internal p2p state machine. michael@0: manager.setObserver = function(aObserver) { michael@0: _stateMachine.setObserver(aObserver); michael@0: }; michael@0: michael@0: // Handle wpa_supplicant events. michael@0: // michael@0: // @param aEventString string from wpa_supplicant. michael@0: manager.handleEvent = function(aEventString) { michael@0: let event = parseEventString(aEventString); michael@0: if (EVENT_UNKNOWN === event.id || EVENT_IGNORED === event.id) { michael@0: debug('Unknow or ignored event: ' + aEventString); michael@0: return false; michael@0: } michael@0: return _stateMachine.sendEvent(event); michael@0: }; michael@0: michael@0: // Set the confirmation of pairing request. michael@0: // michael@0: // @param aResult Object of confirmation result which contains: michael@0: // .accepted: user granted. michael@0: // .pin: pin code which is displaying or input by user. michael@0: // .wpsMethod: string of "pbc" or "display" or "keypad". michael@0: manager.setPairingConfirmation = function(aResult) { michael@0: let event = { michael@0: id: EVENT_P2P_SET_PAIRING_CONFIRMATION, michael@0: info: { michael@0: accepted: aResult.accepted, michael@0: pin: aResult.pin michael@0: } michael@0: }; michael@0: _stateMachine.sendEvent(event); michael@0: }; michael@0: michael@0: // Connect to a known peer. michael@0: // michael@0: // @param aAddress MAC address of the peer to connect. michael@0: // @param aWpsMethod String of "pbc" or "display" or "keypad". michael@0: // @param aGoIntent Number from 0 to 15. michael@0: // @param aCallback Callback |true| on attempting to connect. michael@0: // |false| on failed to connect. michael@0: manager.connect = function(aAddress, aWpsMethod, aGoIntent, aCallback) { michael@0: let event = { michael@0: id: EVENT_P2P_CMD_CONNECT, michael@0: info: { michael@0: wpsMethod: aWpsMethod, michael@0: address: aAddress, michael@0: goIntent: aGoIntent, michael@0: onDoConnect: aCallback michael@0: } michael@0: }; michael@0: _stateMachine.sendEvent(event); michael@0: }; michael@0: michael@0: // Disconnect with a known peer. michael@0: // michael@0: // @param aAddress The address the user desires to disconect. michael@0: // @param aCallback Callback |true| on "attempting" to disconnect. michael@0: // |false| on failed to disconnect. michael@0: manager.disconnect = function(aAddress, aCallback) { michael@0: let event = { michael@0: id: EVENT_P2P_CMD_DISCONNECT, michael@0: info: { michael@0: address: aAddress, michael@0: onDoDisconnect: aCallback michael@0: } michael@0: }; michael@0: _stateMachine.sendEvent(event); michael@0: }; michael@0: michael@0: // Enable/disable wifi p2p. michael@0: // michael@0: // @param aEnabled |true| to enable, |false| to disable. michael@0: // @param aCallbacks object for callbacks: michael@0: // .onEnabled michael@0: // .onDisabled michael@0: // .onSupplicantConnected michael@0: manager.setEnabled = function(aEnabled, aCallbacks) { michael@0: let event = { michael@0: id: (aEnabled ? EVENT_P2P_CMD_ENABLE : EVENT_P2P_CMD_DISABLE), michael@0: info: { michael@0: onEnabled: aCallbacks.onEnabled, michael@0: onDisabled: aCallbacks.onDisabled, michael@0: onSupplicantConnected: aCallbacks.onSupplicantConnected michael@0: } michael@0: }; michael@0: _stateMachine.sendEvent(event); michael@0: }; michael@0: michael@0: // Enable/disable the wifi p2p scan. michael@0: // michael@0: // @param aEnabled |true| to enable scan, |false| to disable scan. michael@0: // @param aCallback Callback |true| on success to enable/disable scan. michael@0: // |false| on failed to enable/disable scan. michael@0: manager.setScanEnabled = function(aEnabled, aCallback) { michael@0: let event = { michael@0: id: (aEnabled ? EVENT_P2P_CMD_ENABLE_SCAN : EVENT_P2P_CMD_DISABLE_SCAN), michael@0: info: { callback: aCallback } michael@0: }; michael@0: _stateMachine.sendEvent(event); michael@0: }; michael@0: michael@0: // Block wifi p2p scan. michael@0: manager.blockScan = function() { michael@0: _stateMachine.sendEvent({ id: EVENT_P2P_CMD_BLOCK_SCAN }); michael@0: }; michael@0: michael@0: // Un-block and do the pending scan if any. michael@0: manager.unblockScan = function() { michael@0: _stateMachine.sendEvent({ id: EVENT_P2P_CMD_UNBLOCK_SCAN }); michael@0: }; michael@0: michael@0: // Set the p2p device name. michael@0: manager.setDeviceName = function(newDeivceName, callback) { michael@0: aP2pCommand.setDeviceName(newDeivceName, callback); michael@0: }; michael@0: michael@0: // Parse wps_supplicant event string. michael@0: // michael@0: // @param aEventString The raw event string from wpa_supplicant. michael@0: // michael@0: // @return Object: michael@0: // .id: a number to represent an event. michael@0: // .info: the additional information carried by this event string. michael@0: function parseEventString(aEventString) { michael@0: if (isIgnoredEvent(aEventString)) { michael@0: return { id: EVENT_IGNORED }; michael@0: } michael@0: michael@0: let match = RegExp("p2p_dev_addr=([0-9a-fA-F:]+) " + michael@0: "pri_dev_type=([0-9a-zA-Z-]+) " + michael@0: "name='(.*)' " + michael@0: "config_methods=0x([0-9a-fA-F]+) " + michael@0: "dev_capab=0x([0-9a-fA-F]+) " + michael@0: "group_capab=0x([0-9a-fA-F]+) ").exec(aEventString + ' '); michael@0: michael@0: let tokens = aEventString.split(" "); michael@0: michael@0: let id = EVENT_UNKNOWN; michael@0: michael@0: // general info. michael@0: let info = {}; michael@0: michael@0: if (match) { michael@0: info = { michael@0: address: match[1] ? match[1] : null, michael@0: type: match[2] ? match[2] : null, michael@0: name: match[3] ? match[3] : null, michael@0: wpsFlag: match[4] ? parseInt(match[4], 16) : null, michael@0: devFlag: match[5] ? parseInt(match[5], 16) : null, michael@0: groupFlag: match[6] ? parseInt(match[6], 16) : null michael@0: }; michael@0: } michael@0: michael@0: if (0 === aEventString.indexOf("P2P-DEVICE-FOUND")) { michael@0: id = EVENT_P2P_DEVICE_FOUND; michael@0: info.wpsCapabilities = wpsFlagToCapabilities(info.wpsFlag); michael@0: info.isGroupOwner = isPeerGroupOwner(info.groupFlag); michael@0: } else if (0 === aEventString.indexOf("P2P-DEVICE-LOST")) { michael@0: // e.g. "P2P-DEVICE-LOST p2p_dev_addr=5e:0a:5b:15:1f:80". michael@0: id = EVENT_P2P_DEVICE_LOST; michael@0: info.address = /p2p_dev_addr=([0-9a-f:]+)/.exec(aEventString)[1]; michael@0: } else if (0 === aEventString.indexOf("P2P-GROUP-STARTED")) { michael@0: // e.g. "P2P-GROUP-STARTED wlan0-p2p-0 GO ssid="DIRECT-3F Testing michael@0: // passphrase="12345678" go_dev_addr=02:40:61:c2:f3:b7 [PERSISTENT]". michael@0: michael@0: id = EVENT_P2P_GROUP_STARTED; michael@0: let groupMatch = RegExp('ssid="(.*)" ' + michael@0: 'freq=([0-9]*) ' + michael@0: '(passphrase|psk)=([^ ]+) ' + michael@0: 'go_dev_addr=([0-9a-f:]+)').exec(aEventString); michael@0: info.ssid = groupMatch[1]; michael@0: info.freq = groupMatch[2]; michael@0: if ('passphrase' === groupMatch[3]) { michael@0: let s = groupMatch[4]; // e.g. "G7jHkkz9". michael@0: info.passphrase = s.substring(1, s.length-1); // Trim the double quote. michael@0: } else { // psk michael@0: info.psk = groupMatch[4]; michael@0: } michael@0: info.goAddress = groupMatch[5]; michael@0: info.ifname = tokens[1]; michael@0: info.role = tokens[2]; michael@0: } else if (0 === aEventString.indexOf("P2P-GROUP-REMOVED")) { michael@0: id = EVENT_P2P_GROUP_REMOVED; michael@0: // e.g. "P2P-GROUP-REMOVED wlan0-p2p-0 GO". michael@0: info.ifname = tokens[1]; michael@0: info.role = tokens[2]; michael@0: } else if (0 === aEventString.indexOf("P2P-PROV-DISC-PBC-REQ")) { michael@0: id = EVENT_P2P_PROV_DISC_PBC_REQ; michael@0: info.wpsMethod = WPS_METHOD_PBC; michael@0: } else if (0 === aEventString.indexOf("P2P-PROV-DISC-PBC-RESP")) { michael@0: id = EVENT_P2P_PROV_DISC_PBC_RESP; michael@0: // The address is different from the general pattern. michael@0: info.address = aEventString.split(" ")[1]; michael@0: info.wpsMethod = WPS_METHOD_PBC; michael@0: } else if (0 === aEventString.indexOf("P2P-PROV-DISC-SHOW-PIN")) { michael@0: id = EVENT_P2P_PROV_DISC_SHOW_PIN; michael@0: // Obtain peer address and pin from tokens. michael@0: info.address = tokens[1]; michael@0: info.pin = tokens[2]; michael@0: info.wpsMethod = WPS_METHOD_DISPLAY; michael@0: } else if (0 === aEventString.indexOf("P2P-PROV-DISC-ENTER-PIN")) { michael@0: id = EVENT_P2P_PROV_DISC_ENTER_PIN; michael@0: // Obtain peer address from tokens. michael@0: info.address = tokens[1]; michael@0: info.wpsMethod = WPS_METHOD_KEYPAD; michael@0: } else if (0 === aEventString.indexOf("P2P-GO-NEG-REQUEST")) { michael@0: id = EVENT_P2P_GO_NEG_REQUEST; michael@0: info.address = tokens[1]; michael@0: switch (parseInt(tokens[2].split("=")[1], 10)) { michael@0: case DEV_PW_REGISTRAR_SPECIFIED: // (5) Peer is display. michael@0: info.wpsMethod = WPS_METHOD_KEYPAD; michael@0: break; michael@0: case DEV_PW_USER_SPECIFIED: // (1) Peer is keypad. michael@0: info.wpsMethod = WPS_METHOD_DISPLAY; michael@0: break; michael@0: case DEV_PW_PUSHBUTTON: // (4) Peer is pbc. michael@0: info.wpsMethod = WPS_METHOD_PBC; michael@0: break; michael@0: default: michael@0: debug('Unknown wps method from event P2P-GO-NEG-REQUEST'); michael@0: break; michael@0: } michael@0: } else if (0 === aEventString.indexOf("P2P-GO-NEG-SUCCESS")) { michael@0: id = EVENT_P2P_GO_NEG_SUCCESS; michael@0: } else if (0 === aEventString.indexOf("P2P-GO-NEG-FAILURE")) { michael@0: id = EVENT_P2P_GO_NEG_FAILURE; michael@0: } else if (0 === aEventString.indexOf("P2P-GROUP-FORMATION-FAILURE")) { michael@0: id = EVENT_P2P_GROUP_FORMATION_FAILURE; michael@0: } else if (0 === aEventString.indexOf("P2P-GROUP-FORMATION-SUCCESS")) { michael@0: id = EVENT_P2P_GROUP_FORMATION_SUCCESS; michael@0: } else if (0 === aEventString.indexOf("P2P-FIND-STOPPED")) { michael@0: id = EVENT_P2P_FIND_STOPPED; michael@0: } else if (0 === aEventString.indexOf("P2P-INVITATION-RESULT")) { michael@0: id = EVENT_P2P_INVITATION_RESULT; michael@0: info.status = /status=([0-9]+)/.exec(aEventString)[1]; michael@0: } else if (0 === aEventString.indexOf("P2P-INVITATION-RECEIVED")) { michael@0: // e.g. "P2P-INVITATION-RECEIVED sa=32:85:a9:da:e6:1f persistent=7". michael@0: id = EVENT_P2P_INVITATION_RECEIVED; michael@0: info.address = /sa=([0-9a-f:]+)/.exec(aEventString)[1]; michael@0: info.netId = /persistent=([0-9]+)/.exec(aEventString)[1]; michael@0: } else if (0 === aEventString.indexOf("P2P-PROV-DISC-FAILURE")) { michael@0: id = EVENT_P2P_PROV_DISC_FAILURE; michael@0: } else { michael@0: // Not P2P event but we do receive it. Try to recognize it. michael@0: if (0 === aEventString.indexOf("AP-STA-DISCONNECTED")) { michael@0: id = EVENT_AP_STA_DISCONNECTED; michael@0: info.address = tokens[1]; michael@0: } else if (0 === aEventString.indexOf("AP-STA-CONNECTED")) { michael@0: id = EVENT_AP_STA_CONNECTED; michael@0: info.address = tokens[1]; michael@0: } else { michael@0: // Neither P2P event nor recognized supplicant event. michael@0: debug('Unknwon event string: ' + aEventString); michael@0: } michael@0: } michael@0: michael@0: let event = {id: id, info: info}; michael@0: debug('Event parsing result: ' + aEventString + ": " + JSON.stringify(event)); michael@0: michael@0: return event; michael@0: } michael@0: michael@0: function isIgnoredEvent(aEventString) { michael@0: const IGNORED_EVENTS = [ michael@0: "CTRL-EVENT-BSS-ADDED", michael@0: "CTRL-EVENT-BSS-REMOVED", michael@0: "CTRL-EVENT-SCAN-RESULTS", michael@0: "CTRL-EVENT-STATE-CHANGE", michael@0: "WPS-AP-AVAILABLE", michael@0: "WPS-ENROLLEE-SEEN" michael@0: ]; michael@0: for(let i = 0; i < IGNORED_EVENTS.length; i++) { michael@0: if (0 === aEventString.indexOf(IGNORED_EVENTS[i])) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: function isPeerGroupOwner(aGroupFlag) { michael@0: return (aGroupFlag & GROUP_CAPAB_GROUP_OWNER) !== 0; michael@0: } michael@0: michael@0: // Convert flag to a wps capability array. michael@0: // michael@0: // @param aWpsFlag Number that represents the wps capabilities. michael@0: // @return Array of WPS flag. michael@0: function wpsFlagToCapabilities(aWpsFlag) { michael@0: let wpsCapabilities = []; michael@0: if (aWpsFlag & 0x8) { michael@0: wpsCapabilities.push(WPS_METHOD_DISPLAY); michael@0: } michael@0: if (aWpsFlag & 0x80) { michael@0: wpsCapabilities.push(WPS_METHOD_PBC); michael@0: } michael@0: if (aWpsFlag & 0x100) { michael@0: wpsCapabilities.push(WPS_METHOD_KEYPAD); michael@0: } michael@0: return wpsCapabilities; michael@0: } michael@0: michael@0: _stateMachine.start(); michael@0: return manager; michael@0: }; michael@0: michael@0: function P2pStateMachine(aP2pCommand, aNetUtil) { michael@0: function debug(aMsg) { michael@0: if (gDebug) { michael@0: dump('-------------- WifiP2pStateMachine: ' + aMsg); michael@0: } michael@0: } michael@0: michael@0: let p2pSm = {}; // The state machine to return. michael@0: michael@0: let _sm = StateMachine('WIFIP2P'); // The general purpose state machine. michael@0: michael@0: // Information we need to keep track across states. michael@0: let _observer; michael@0: michael@0: let _onEnabled; michael@0: let _onDisabled; michael@0: let _onSupplicantConnected; michael@0: let _savedConfig = {}; // Configuration used to do P2P_CONNECT. michael@0: let _groupInfo = {}; // The information of the group we have formed. michael@0: let _removedGroupInfo = {}; // Used to store the group info we are going to remove. michael@0: michael@0: let _scanBlocked = false; michael@0: let _scanPostponded = false; michael@0: michael@0: let _localDevice = { michael@0: address: "", michael@0: deviceName: DEFAULT_P2P_DEVICE_NAME + "_" + libcutils.property_get("ro.build.product"), michael@0: wpsCapabilities: [WPS_METHOD_PBC, WPS_METHOD_KEYPAD, WPS_METHOD_DISPLAY] michael@0: }; michael@0: michael@0: let _p2pNetworkInterface = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]), michael@0: michael@0: state: Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED, michael@0: type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI_P2P, michael@0: name: P2P_INTERFACE_NAME, michael@0: ips: [], michael@0: prefixLengths: [], michael@0: dnses: [], michael@0: gateways: [], michael@0: httpProxyHost: null, michael@0: httpProxyPort: null, michael@0: michael@0: // help michael@0: registered: false, michael@0: michael@0: getAddresses: function (ips, prefixLengths) { michael@0: ips.value = this.ips.slice(); michael@0: prefixLengths.value = this.prefixLengths.slice(); michael@0: michael@0: return this.ips.length; michael@0: }, michael@0: michael@0: getGateways: function (count) { michael@0: if (count) { michael@0: count.value = this.gateways.length; michael@0: } michael@0: return this.gateways.slice(); michael@0: }, michael@0: michael@0: getDnses: function (count) { michael@0: if (count) { michael@0: count.value = this.dnses.length; michael@0: } michael@0: return this.dnses.slice(); michael@0: } michael@0: }; michael@0: michael@0: //--------------------------------------------------------- michael@0: // State machine APIs. michael@0: //--------------------------------------------------------- michael@0: michael@0: // Register the observer which is implemented in WifiP2pWorkerObserver.jsm. michael@0: // michael@0: // @param aObserver: michael@0: // .onEnabled michael@0: // .onDisbaled michael@0: // .onPeerFound michael@0: // .onPeerLost michael@0: // .onConnecting michael@0: // .onConnected michael@0: // .onDisconnected michael@0: // .onLocalDeviceChanged michael@0: p2pSm.setObserver = function(aObserver) { michael@0: _observer = aObserver; michael@0: }; michael@0: michael@0: p2pSm.start = function() { michael@0: _sm.start(stateDisabled); michael@0: }; michael@0: michael@0: p2pSm.sendEvent = function(aEvent) { michael@0: let willBeHandled = isInP2pManagedState(_sm.getCurrentState()); michael@0: _sm.sendEvent(aEvent); michael@0: return willBeHandled; michael@0: }; michael@0: michael@0: // Initialize internal state machine _sm. michael@0: _sm.setDefaultEventHandler(handleEventCommon); michael@0: michael@0: //---------------------------------------------------------- michael@0: // State definition. michael@0: //---------------------------------------------------------- michael@0: michael@0: // The initial state. michael@0: var stateDisabled = _sm.makeState("DISABLED", { michael@0: enter: function() { michael@0: _onEnabled = null; michael@0: _onSupplicantConnected = null; michael@0: _savedConfig = null; michael@0: _groupInfo = null; michael@0: _removedGroupInfo = null; michael@0: _scanBlocked = false; michael@0: _scanPostponded = false; michael@0: michael@0: unregisterP2pNetworkInteface(); michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: switch (aEvent.id) { michael@0: case EVENT_P2P_CMD_ENABLE: michael@0: _onEnabled = aEvent.info.onEnabled; michael@0: _onSupplicantConnected = aEvent.info.onSupplicantConnected; michael@0: _sm.gotoState(stateEnabling); michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // End of switch. michael@0: return true; michael@0: } michael@0: }); michael@0: michael@0: // The state where we are trying to enable wifi p2p. michael@0: var stateEnabling = _sm.makeState("ENABLING", { michael@0: enter: function() { michael@0: michael@0: function onFailure() michael@0: { michael@0: _onEnabled(false); michael@0: _sm.gotoState(stateDisabled); michael@0: } michael@0: michael@0: function onSuccess() michael@0: { michael@0: _onEnabled(true); michael@0: _sm.gotoState(stateInactive); michael@0: } michael@0: michael@0: _sm.pause(); michael@0: michael@0: // Step 1: Connect to p2p0. michael@0: aP2pCommand.connectToSupplicant(function (status) { michael@0: let detail; michael@0: michael@0: if (0 !== status) { michael@0: debug('Failed to connect to p2p0'); michael@0: onFailure(); michael@0: return; michael@0: } michael@0: michael@0: debug('wpa_supplicant p2p0 connected!'); michael@0: _onSupplicantConnected(); michael@0: michael@0: // Step 2: Get MAC address. michael@0: if (!_localDevice.address) { michael@0: aP2pCommand.getMacAddress(function (address) { michael@0: if (!address) { michael@0: debug('Failed to get MAC address....'); michael@0: onFailure(); michael@0: return; michael@0: } michael@0: debug('Got mac address: ' + address); michael@0: _localDevice.address = address; michael@0: _observer.onLocalDeviceChanged(_localDevice); michael@0: }); michael@0: } michael@0: michael@0: // Step 3: Enable p2p with the device name and wps methods. michael@0: detail = { deviceName: _localDevice.deviceName, michael@0: deviceType: libcutils.property_get("ro.moz.wifi.p2p_device_type") || DEFAULT_P2P_DEVICE_TYPE, michael@0: wpsMethods: libcutils.property_get("ro.moz.wifi.p2p_wps_methods") || DEFAULT_P2P_WPS_METHODS }; michael@0: michael@0: aP2pCommand.p2pEnable(detail, function (success) { michael@0: if (!success) { michael@0: debug('Failed to enable p2p'); michael@0: onFailure(); michael@0: return; michael@0: } michael@0: michael@0: debug('P2P is enabled! Enabling net interface...'); michael@0: michael@0: // Step 4: Enable p2p0 net interface. wpa_supplicant may have michael@0: // already done it for us. michael@0: aNetUtil.enableInterface(P2P_INTERFACE_NAME, function (success) { michael@0: onSuccess(); michael@0: }); michael@0: }); michael@0: }); michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: // We won't receive any event since all of them will be blocked. michael@0: return true; michael@0: } michael@0: }); michael@0: michael@0: // The state just after enabling wifi direct. michael@0: var stateInactive = _sm.makeState("INACTIVE", { michael@0: enter: function() { michael@0: registerP2pNetworkInteface(); michael@0: michael@0: if (_sm.getPreviousState() !== stateEnabling) { michael@0: _observer.onDisconnected(_savedConfig); michael@0: } michael@0: michael@0: _savedConfig = null; // Used to connect p2p peer. michael@0: _groupInfo = null; // The information of the formed group. michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: switch (aEvent.id) { michael@0: // Receiving the following 3 states implies someone is trying to michael@0: // connect to me. michael@0: case EVENT_P2P_PROV_DISC_PBC_REQ: michael@0: case EVENT_P2P_PROV_DISC_SHOW_PIN: michael@0: case EVENT_P2P_PROV_DISC_ENTER_PIN: michael@0: debug('Someone is trying to connect to me: ' + JSON.stringify(aEvent.info)); michael@0: michael@0: _savedConfig = { michael@0: name: aEvent.info.name, michael@0: address: aEvent.info.address, michael@0: wpsMethod: aEvent.info.wpsMethod, michael@0: goIntent: DEFAULT_GO_INTENT, michael@0: pin: aEvent.info.pin // EVENT_P2P_PROV_DISC_SHOW_PIN only. michael@0: }; michael@0: michael@0: _sm.gotoState(stateWaitingForConfirmation); michael@0: break; michael@0: michael@0: // Connect to a peer. michael@0: case EVENT_P2P_CMD_CONNECT: michael@0: debug('Trying to connect to peer: ' + JSON.stringify(aEvent.info)); michael@0: michael@0: _savedConfig = { michael@0: address: aEvent.info.address, michael@0: wpsMethod: aEvent.info.wpsMethod, michael@0: goIntent: aEvent.info.goIntent michael@0: }; michael@0: michael@0: _sm.gotoState(stateProvisionDiscovery); michael@0: aEvent.info.onDoConnect(true); michael@0: break; michael@0: michael@0: case EVENT_P2P_INVITATION_RECEIVED: michael@0: _savedConfig = { michael@0: address: aEvent.info.address, michael@0: wpsMethod: WPS_METHOD_PBC, michael@0: goIntent: DEFAULT_GO_INTENT, michael@0: netId: aEvent.info.netId michael@0: }; michael@0: _sm.gotoState(stateWaitingForInvitationConfirmation); michael@0: break; michael@0: michael@0: case EVENT_P2P_GROUP_STARTED: michael@0: // Most likely the peer just reinvoked a peristen group and succeeeded. michael@0: michael@0: _savedConfig = { address: aEvent.info.goAddress }; michael@0: michael@0: _sm.pause(); michael@0: handleGroupStarted(aEvent.info, function (success) { michael@0: _sm.resume(); michael@0: }); michael@0: break; michael@0: michael@0: case EVENT_AP_STA_DISCONNECTED: michael@0: // We will hit this case when we used to be a group owner and michael@0: // requested to remove the group we owned. michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // End of switch. michael@0: return true; michael@0: }, michael@0: }); michael@0: michael@0: // Waiting for user's confirmation. michael@0: var stateWaitingForConfirmation = _sm.makeState("WAITING_FOR_CONFIRMATION", { michael@0: timeoutTimer: null, michael@0: michael@0: enter: function() { michael@0: gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig); michael@0: this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION); michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: switch (aEvent.id) { michael@0: case EVENT_P2P_SET_PAIRING_CONFIRMATION: michael@0: if (!aEvent.info.accepted) { michael@0: debug('User rejected this request'); michael@0: _sm.gotoState(stateInactive); // Reset to inactive state. michael@0: break; michael@0: } michael@0: michael@0: debug('User accepted this request'); michael@0: michael@0: // The only information we may have to grab from user. michael@0: _savedConfig.pin = aEvent.info.pin; michael@0: michael@0: // The case that user requested to form a group ealier on. michael@0: // Just go to connecting state and do p2p_connect. michael@0: if (_sm.getPreviousState() === stateProvisionDiscovery) { michael@0: _sm.gotoState(stateConnecting); michael@0: break; michael@0: } michael@0: michael@0: // Otherwise, wait for EVENT_P2P_GO_NEG_REQUEST. michael@0: _sm.gotoState(stateWaitingForNegReq); michael@0: break; michael@0: michael@0: case EVENT_TIMEOUT_PAIRING_CONFIRMATION: michael@0: debug('Confirmation timeout!'); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: case EVENT_P2P_GO_NEG_REQUEST: michael@0: _sm.deferEvent(aEvent); michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // End of switch. michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: exit: function() { michael@0: this.timeoutTimer.cancel(); michael@0: this.timeoutTimer = null; michael@0: } michael@0: }); michael@0: michael@0: var stateWaitingForNegReq = _sm.makeState("WAITING_FOR_NEG_REQ", { michael@0: timeoutTimer: null, michael@0: michael@0: enter: function() { michael@0: debug('Wait for EVENT_P2P_GO_NEG_REQUEST'); michael@0: this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_NEG_REQ); michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: switch (aEvent.id) { michael@0: case EVENT_P2P_GO_NEG_REQUEST: michael@0: if (aEvent.info.wpsMethod !== _savedConfig.wpsMethod) { michael@0: debug('Unmatched wps method: ' + aEvent.info.wpsMethod + ", " + _savedConfig.wpsMetho); michael@0: } michael@0: _sm.gotoState(stateConnecting); michael@0: break; michael@0: michael@0: case EVENT_TIMEOUT_NEG_REQ: michael@0: debug("Waiting for NEG-REQ timeout"); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // End of switch. michael@0: return true; michael@0: }, michael@0: michael@0: exit: function() { michael@0: this.timeoutTimer.cancel(); michael@0: this.timeoutTimer = null; michael@0: } michael@0: }); michael@0: michael@0: // Waiting for user's confirmation for invitation. michael@0: var stateWaitingForInvitationConfirmation = _sm.makeState("WAITING_FOR_INV_CONFIRMATION", { michael@0: timeoutTimer: null, michael@0: michael@0: enter: function() { michael@0: gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig); michael@0: this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION); michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: switch (aEvent.id) { michael@0: case EVENT_P2P_SET_PAIRING_CONFIRMATION: michael@0: if (!aEvent.info.accepted) { michael@0: debug('User rejected this request'); michael@0: _sm.gotoState(stateInactive); // Reset to inactive state. michael@0: break; michael@0: } michael@0: michael@0: debug('User accepted this request'); michael@0: _sm.pause(); michael@0: aP2pCommand.p2pGetGroupCapab(_savedConfig.address, function (gc) { michael@0: let isPeeGroupOwner = gc & GROUP_CAPAB_GROUP_OWNER; michael@0: _sm.gotoState(isPeeGroupOwner ? stateGroupAdding : stateReinvoking); michael@0: }); michael@0: michael@0: break; michael@0: michael@0: case EVENT_TIMEOUT_PAIRING_CONFIRMATION: michael@0: debug('Confirmation timeout!'); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // End of switch. michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: exit: function() { michael@0: this.timeoutTimer.cancel(); michael@0: this.timeoutTimer = null; michael@0: } michael@0: }); michael@0: michael@0: var stateGroupAdding = _sm.makeState("GROUP_ADDING", { michael@0: timeoutTimer: null, michael@0: michael@0: enter: function() { michael@0: let self = this; michael@0: michael@0: _observer.onConnecting(_savedConfig); michael@0: michael@0: _sm.pause(); michael@0: aP2pCommand.p2pGroupAdd(_savedConfig.netId, function (success) { michael@0: if (!success) { michael@0: _sm.gotoState(stateInactive); michael@0: return; michael@0: } michael@0: // Waiting for EVENT_P2P_GROUP_STARTED. michael@0: self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING); michael@0: _sm.resume(); michael@0: }); michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: switch (aEvent.id) { michael@0: case EVENT_P2P_GROUP_STARTED: michael@0: _sm.pause(); michael@0: handleGroupStarted(aEvent.info, function (success) { michael@0: _sm.resume(); michael@0: }); michael@0: break; michael@0: michael@0: case EVENT_P2P_GO_NEG_FAILURE: michael@0: debug('Negotiation failure. Go back to inactive state'); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: case EVENT_TIMEOUT_CONNECTING: michael@0: debug('Connecting timeout! Go back to inactive state'); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: case EVENT_P2P_GROUP_FORMATION_SUCCESS: michael@0: case EVENT_P2P_GO_NEG_SUCCESS: michael@0: break; michael@0: michael@0: case EVENT_P2P_GROUP_FORMATION_FAILURE: michael@0: debug('Group formation failure'); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: case EVENT_P2P_GROUP_REMOVED: michael@0: debug('Received P2P-GROUP-REMOVED due to previous failed handleGroupdStarted()'); michael@0: _removedGroupInfo = { michael@0: role: aEvent.info.role, michael@0: ifname: aEvent.info.ifname michael@0: }; michael@0: _sm.gotoState(stateDisconnecting); michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // End of switch. michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: exit: function() { michael@0: this.timeoutTimer.cancel(); michael@0: this.timeoutTimer = null; michael@0: } michael@0: }); michael@0: michael@0: var stateReinvoking = _sm.makeState("REINVOKING", { michael@0: timeoutTimer: null, michael@0: michael@0: enter: function() { michael@0: let self = this; michael@0: michael@0: _observer.onConnecting(_savedConfig); michael@0: _sm.pause(); michael@0: aP2pCommand.p2pReinvoke(_savedConfig.netId, _savedConfig.address, function(success) { michael@0: if (!success) { michael@0: _sm.gotoState(stateInactive); michael@0: return; michael@0: } michael@0: // Waiting for EVENT_P2P_GROUP_STARTED. michael@0: self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING); michael@0: _sm.resume(); michael@0: }); michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: switch (aEvent.id) { michael@0: case EVENT_P2P_GROUP_STARTED: michael@0: _sm.pause(); michael@0: handleGroupStarted(aEvent.info, function(success) { michael@0: _sm.resume(); michael@0: }); michael@0: break; michael@0: michael@0: case EVENT_P2P_GO_NEG_FAILURE: michael@0: debug('Negotiation failure. Go back to inactive state'); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: case EVENT_TIMEOUT_CONNECTING: michael@0: debug('Connecting timeout! Go back to inactive state'); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: case EVENT_P2P_GROUP_FORMATION_SUCCESS: michael@0: case EVENT_P2P_GO_NEG_SUCCESS: michael@0: break; michael@0: michael@0: case EVENT_P2P_GROUP_FORMATION_FAILURE: michael@0: debug('Group formation failure'); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: case EVENT_P2P_GROUP_REMOVED: michael@0: debug('Received P2P-GROUP-REMOVED due to previous failed handleGroupdStarted()'); michael@0: _removedGroupInfo = { michael@0: role: aEvent.info.role, michael@0: ifname: aEvent.info.ifname michael@0: }; michael@0: _sm.gotoState(stateDisconnecting); michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // End of switch. michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: exit: function() { michael@0: this.timeoutTimer.cancel(); michael@0: } michael@0: }); michael@0: michael@0: var stateProvisionDiscovery = _sm.makeState("PROVISION_DISCOVERY", { michael@0: enter: function() { michael@0: function onDiscoveryCommandSent(success) { michael@0: if (!success) { michael@0: _sm.gotoState(stateInactive); michael@0: debug('Failed to send p2p_prov_disc. Go back to inactive state.'); michael@0: return; michael@0: } michael@0: michael@0: debug('p2p_prov_disc has been sent.'); michael@0: michael@0: _sm.resume(); michael@0: // Waiting for EVENT_P2P_PROV_DISC_PBC_RESP or michael@0: // EVENT_P2P_PROV_DISC_SHOW_PIN or michael@0: // EVENT_P2P_PROV_DISC_ENTER_PIN. michael@0: } michael@0: michael@0: _sm.pause(); michael@0: aP2pCommand.p2pProvDiscovery(_savedConfig.address, michael@0: toPeerWpsMethod(_savedConfig.wpsMethod), michael@0: onDiscoveryCommandSent); michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: switch (aEvent.id) { michael@0: case EVENT_P2P_PROV_DISC_PBC_RESP: michael@0: _sm.gotoState(stateConnecting); // No need for local user grant. michael@0: break; michael@0: case EVENT_P2P_PROV_DISC_SHOW_PIN: michael@0: case EVENT_P2P_PROV_DISC_ENTER_PIN: michael@0: if (aEvent.info.wpsMethod !== _savedConfig.wpsMethod) { michael@0: debug('Unmatched wps method: ' + aEvent.info.wpsMethod + ":" + _savedConfig.wpsMethod); michael@0: } michael@0: if (EVENT_P2P_PROV_DISC_SHOW_PIN === aEvent.id) { michael@0: _savedConfig.pin = aEvent.info.pin; michael@0: } michael@0: _sm.gotoState(stateWaitingForConfirmation); michael@0: break; michael@0: michael@0: case EVENT_P2P_PROV_DISC_FAILURE: michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // End of switch. michael@0: return true; michael@0: } michael@0: }); michael@0: michael@0: // We are going to connect to the peer. michael@0: // |_savedConfig| is supposed to have been filled properly. michael@0: var stateConnecting = _sm.makeState("CONNECTING", { michael@0: timeoutTimer: null, michael@0: michael@0: enter: function() { michael@0: let self = this; michael@0: michael@0: if (null === _savedConfig.goIntent) { michael@0: _savedConfig.goIntent = DEFAULT_GO_INTENT; michael@0: } michael@0: michael@0: _observer.onConnecting(_savedConfig); michael@0: michael@0: let wpsMethodWithPin; michael@0: if (WPS_METHOD_KEYPAD === _savedConfig.wpsMethod || michael@0: WPS_METHOD_DISPLAY === _savedConfig.wpsMethod) { michael@0: // e.g. '12345678 display or '12345678 keypad'. michael@0: wpsMethodWithPin = (_savedConfig.pin + ' ' + _savedConfig.wpsMethod); michael@0: } else { michael@0: // e.g. 'pbc'. michael@0: wpsMethodWithPin = _savedConfig.wpsMethod; michael@0: } michael@0: michael@0: _sm.pause(); michael@0: michael@0: aP2pCommand.p2pGetGroupCapab(_savedConfig.address, function(gc) { michael@0: debug('group capabilities of ' + _savedConfig.address + ': ' + gc); michael@0: michael@0: let isPeerGroupOwner = gc & GROUP_CAPAB_GROUP_OWNER; michael@0: let config = { address: _savedConfig.address, michael@0: wpsMethodWithPin: wpsMethodWithPin, michael@0: goIntent: _savedConfig.goIntent, michael@0: joinExistingGroup: isPeerGroupOwner }; michael@0: michael@0: aP2pCommand.p2pConnect(config, function (success) { michael@0: if (!success) { michael@0: debug('Failed to send p2p_connect'); michael@0: _sm.gotoState(stateInactive); michael@0: return; michael@0: } michael@0: debug('Waiting for EVENT_P2P_GROUP_STARTED.'); michael@0: self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING); michael@0: _sm.resume(); michael@0: }); michael@0: }); michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: switch (aEvent.id) { michael@0: case EVENT_P2P_GROUP_STARTED: michael@0: _sm.pause(); michael@0: handleGroupStarted(aEvent.info, function (success) { michael@0: _sm.resume(); michael@0: }); michael@0: break; michael@0: michael@0: case EVENT_P2P_GO_NEG_FAILURE: michael@0: debug('Negotiation failure. Go back to inactive state'); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: case EVENT_TIMEOUT_CONNECTING: michael@0: debug('Connecting timeout! Go back to inactive state'); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: case EVENT_P2P_GROUP_FORMATION_SUCCESS: michael@0: case EVENT_P2P_GO_NEG_SUCCESS: michael@0: break; michael@0: michael@0: case EVENT_P2P_GROUP_FORMATION_FAILURE: michael@0: debug('Group formation failure'); michael@0: _sm.gotoState(stateInactive); michael@0: break; michael@0: michael@0: case EVENT_P2P_GROUP_REMOVED: michael@0: debug('Received P2P-GROUP-REMOVED due to previous failed ' + michael@0: 'handleGroupdStarted()'); michael@0: _removedGroupInfo = { michael@0: role: aEvent.info.role, michael@0: ifname: aEvent.info.ifname michael@0: }; michael@0: _sm.gotoState(stateDisconnecting); michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // End of switch. michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: exit: function() { michael@0: this.timeoutTimer.cancel(); michael@0: } michael@0: }); michael@0: michael@0: var stateConnected = _sm.makeState("CONNECTED", { michael@0: groupOwner: null, michael@0: michael@0: enter: function() { michael@0: this.groupOwner = { michael@0: macAddress: _groupInfo.goAddress, michael@0: ipAddress: _groupInfo.networkInterface.gateway, michael@0: passphrase: _groupInfo.passphrase, michael@0: ssid: _groupInfo.ssid, michael@0: freq: _groupInfo.freq, michael@0: isLocal: _groupInfo.isGroupOwner michael@0: }; michael@0: michael@0: if (!_groupInfo.isGroupOwner) { michael@0: _observer.onConnected(this.groupOwner, _savedConfig); michael@0: } else { michael@0: // If I am a group owner, notify onConnected until EVENT_AP_STA_CONNECTED michael@0: // is received. michael@0: } michael@0: michael@0: _removedGroupInfo = null; michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: switch (aEvent.id) { michael@0: case EVENT_AP_STA_CONNECTED: michael@0: if (_groupInfo.isGroupOwner) { michael@0: _observer.onConnected(this.groupOwner, _savedConfig); michael@0: } michael@0: break; michael@0: michael@0: case EVENT_P2P_GROUP_REMOVED: michael@0: _removedGroupInfo = { michael@0: role: aEvent.info.role, michael@0: ifname: aEvent.info.ifname michael@0: }; michael@0: _sm.gotoState(stateDisconnecting); michael@0: break; michael@0: michael@0: case EVENT_AP_STA_DISCONNECTED: michael@0: debug('Client disconnected: ' + aEvent.info.address); michael@0: michael@0: // Now we suppose it's the only client. Remove my group. michael@0: _sm.pause(); michael@0: aP2pCommand.p2pGroupRemove(_groupInfo.ifname, function (success) { michael@0: debug('Requested to remove p2p group. Wait for EVENT_P2P_GROUP_REMOVED.'); michael@0: _sm.resume(); michael@0: }); michael@0: break; michael@0: michael@0: case EVENT_P2P_CMD_DISCONNECT: michael@0: // Since we only support single connection, we can ignore michael@0: // the given peer address. michael@0: _sm.pause(); michael@0: aP2pCommand.p2pGroupRemove(_groupInfo.ifname, function(success) { michael@0: aEvent.info.onDoDisconnect(true); michael@0: _sm.resume(); michael@0: }); michael@0: michael@0: debug('Sent disconnect command. Wait for EVENT_P2P_GROUP_REMOVED.'); michael@0: break; michael@0: michael@0: case EVENT_P2P_PROV_DISC_PBC_REQ: michael@0: case EVENT_P2P_PROV_DISC_SHOW_PIN: michael@0: case EVENT_P2P_PROV_DISC_ENTER_PIN: michael@0: debug('Someone is trying to connect to me: ' + JSON.stringify(aEvent.info)); michael@0: michael@0: _savedConfig = { michael@0: name: aEvent.info.name, michael@0: address: aEvent.info.address, michael@0: wpsMethod: aEvent.info.wpsMethod, michael@0: pin: aEvent.info.pin michael@0: }; michael@0: michael@0: _sm.gotoState(stateWaitingForJoiningConfirmation); michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // end of switch michael@0: return true; michael@0: } michael@0: }); michael@0: michael@0: var stateWaitingForJoiningConfirmation = _sm.makeState("WAITING_FOR_JOINING_CONFIRMATION", { michael@0: timeoutTimer: null, michael@0: michael@0: enter: function() { michael@0: gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig); michael@0: this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION); michael@0: }, michael@0: michael@0: handleEvent: function (aEvent) { michael@0: switch (aEvent.id) { michael@0: case EVENT_P2P_SET_PAIRING_CONFIRMATION: michael@0: if (!aEvent.info.accepted) { michael@0: debug('User rejected invitation!'); michael@0: _sm.gotoState(stateConnected); michael@0: break; michael@0: } michael@0: michael@0: let onWpsCommandSent = function(success) { michael@0: _observer.onConnecting(_savedConfig); michael@0: _sm.gotoState(stateConnected); michael@0: }; michael@0: michael@0: _sm.pause(); michael@0: if (WPS_METHOD_PBC === _savedConfig.wpsMethod) { michael@0: aP2pCommand.wpsPbc(_groupInfo.ifname, onWpsCommandSent); michael@0: } else { michael@0: let detail = { pin: _savedConfig.pin, iface: _groupInfo.ifname }; michael@0: aP2pCommand.wpsPin(detail, onWpsCommandSent); michael@0: } michael@0: break; michael@0: michael@0: case EVENT_TIMEOUT_PAIRING_CONFIRMATION: michael@0: debug('WAITING_FOR_JOINING_CONFIRMATION timeout!'); michael@0: _sm.gotoState(stateConnected); michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // End of switch. michael@0: return true; michael@0: }, michael@0: michael@0: exit: function() { michael@0: this.timeoutTimer.cancel(); michael@0: this.timeoutTimer = null; michael@0: } michael@0: }); michael@0: michael@0: var stateDisconnecting = _sm.makeState("DISCONNECTING", { michael@0: enter: function() { michael@0: _sm.pause(); michael@0: handleGroupRemoved(_removedGroupInfo, function (success) { michael@0: if (!success) { michael@0: debug('Failed to handle group removed event. What can I do?'); michael@0: } michael@0: _sm.gotoState(stateInactive); michael@0: }); michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: return false; // We will not receive any event in this state. michael@0: } michael@0: }); michael@0: michael@0: var stateDisabling = _sm.makeState("DISABLING", { michael@0: enter: function() { michael@0: _sm.pause(); michael@0: aNetUtil.stopDhcpServer(function (success) { // Stopping DHCP server is harmless. michael@0: debug('Stop DHCP server result: ' + success); michael@0: aP2pCommand.p2pDisable(function(success) { michael@0: debug('P2P function disabled'); michael@0: aP2pCommand.closeSupplicantConnection(function (status) { michael@0: debug('Supplicant connection closed'); michael@0: aNetUtil.disableInterface(P2P_INTERFACE_NAME, function (success){ michael@0: debug('Disabled interface: ' + P2P_INTERFACE_NAME); michael@0: _onDisabled(true); michael@0: _sm.gotoState(stateDisabled); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: return false; // We will not receive any event in this state. michael@0: } michael@0: }); michael@0: michael@0: //---------------------------------------------------------- michael@0: // Helper functions. michael@0: //---------------------------------------------------------- michael@0: michael@0: // Handle 'P2P_GROUP_STARTED' event. Note that this function michael@0: // will also do the state transitioning and error handling. michael@0: // michael@0: // @param aInfo Information carried by "P2P_GROUP_STARTED" event: michael@0: // .role: P2P_ROLE_GO or P2P_ROLE_CLIENT michael@0: // .ssid: michael@0: // .freq: michael@0: // .passphrase: Used to connect to GO for legacy device. michael@0: // .goAddress: michael@0: // .ifname: e.g. p2p-p2p0 michael@0: // michael@0: // @param aCallback Callback function. michael@0: function handleGroupStarted(aInfo, aCallback) { michael@0: debug('handleGroupStarted: ' + JSON.stringify(aInfo)); michael@0: michael@0: function onSuccess() michael@0: { michael@0: _sm.gotoState(stateConnected); michael@0: aCallback(true); michael@0: } michael@0: michael@0: function onFailure() michael@0: { michael@0: debug('Failed to handleGroupdStarted(). Remove the group...'); michael@0: aP2pCommand.p2pGroupRemove(aInfo.ifname, function (success) { michael@0: aCallback(false); michael@0: michael@0: if (success) { michael@0: return; // Stay in current state and wait for EVENT_P2P_GROUP_REMOVED. michael@0: } michael@0: michael@0: debug('p2pGroupRemove command error!'); michael@0: _sm.gotoState(stateInactive); michael@0: }); michael@0: } michael@0: michael@0: // Save this group information. michael@0: _groupInfo = aInfo; michael@0: _groupInfo.isGroupOwner = (P2P_ROLE_GO === aInfo.role); michael@0: michael@0: if (_groupInfo.isGroupOwner) { michael@0: debug('Group owner. Start DHCP server'); michael@0: let dhcpServerConfig = { ifname: aInfo.ifname, michael@0: startIp: GO_DHCP_SERVER_IP_RANGE.startIp, michael@0: endIp: GO_DHCP_SERVER_IP_RANGE.endIp, michael@0: serverIp: GO_NETWORK_INTERFACE.ip, michael@0: maskLength: GO_NETWORK_INTERFACE.maskLength }; michael@0: michael@0: aNetUtil.startDhcpServer(dhcpServerConfig, function (success) { michael@0: if (!success) { michael@0: debug('Failed to start DHCP server'); michael@0: onFailure(); michael@0: return; michael@0: } michael@0: michael@0: // Update p2p network interface. michael@0: _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED; michael@0: _p2pNetworkInterface.ips = [GO_NETWORK_INTERFACE.ip]; michael@0: _p2pNetworkInterface.prefixLengths = [GO_NETWORK_INTERFACE.maskLength]; michael@0: _p2pNetworkInterface.gateways = [GO_NETWORK_INTERFACE.ip]; michael@0: handleP2pNetworkInterfaceStateChanged(); michael@0: michael@0: _groupInfo.networkInterface = _p2pNetworkInterface; michael@0: michael@0: debug('Everything is done. Happy p2p GO~'); michael@0: onSuccess(); michael@0: }); michael@0: michael@0: return; michael@0: } michael@0: michael@0: // We are the client. michael@0: michael@0: debug("Client. Request IP from DHCP server on interface: " + _groupInfo.ifname); michael@0: michael@0: aNetUtil.runDhcp(aInfo.ifname, function(dhcpData) { michael@0: if(!dhcpData || !dhcpData.info) { michael@0: debug('Failed to run DHCP client'); michael@0: onFailure(); michael@0: return; michael@0: } michael@0: michael@0: // Save network interface. michael@0: debug("DHCP request success: " + JSON.stringify(dhcpData.info)); michael@0: michael@0: // Update p2p network interface. michael@0: let maskLength = michael@0: netHelpers.getMaskLength(netHelpers.stringToIP(dhcpData.info.mask_str)); michael@0: if (!maskLength) { michael@0: maskLength = 32; // max prefix for IPv4. michael@0: } michael@0: _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED; michael@0: _p2pNetworkInterface.ips = [dhcpData.info.ipaddr_str]; michael@0: _p2pNetworkInterface.prefixLengths = [maskLength]; michael@0: if (typeof dhcpData.info.dns1_str == "string" && michael@0: dhcpData.info.dns1_str.length) { michael@0: _p2pNetworkInterface.dnses.push(dhcpData.info.dns1_str); michael@0: } michael@0: if (typeof dhcpData.info.dns2_str == "string" && michael@0: dhcpData.info.dns2_str.length) { michael@0: _p2pNetworkInterface.dnses.push(dhcpData.info.dns2_str); michael@0: } michael@0: _p2pNetworkInterface.gateways = [dhcpData.info.gateway_str]; michael@0: handleP2pNetworkInterfaceStateChanged(); michael@0: michael@0: _groupInfo.networkInterface = _p2pNetworkInterface; michael@0: michael@0: debug('Happy p2p client~'); michael@0: onSuccess(); michael@0: }); michael@0: } michael@0: michael@0: function resetP2pNetworkInterface() { michael@0: _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED; michael@0: _p2pNetworkInterface.ips = []; michael@0: _p2pNetworkInterface.prefixLengths = []; michael@0: _p2pNetworkInterface.dnses = []; michael@0: _p2pNetworkInterface.gateways = []; michael@0: } michael@0: michael@0: function registerP2pNetworkInteface() { michael@0: if (!_p2pNetworkInterface.registered) { michael@0: resetP2pNetworkInterface(); michael@0: gNetworkManager.registerNetworkInterface(_p2pNetworkInterface); michael@0: _p2pNetworkInterface.registered = true; michael@0: } michael@0: } michael@0: michael@0: function unregisterP2pNetworkInteface() { michael@0: if (_p2pNetworkInterface.registered) { michael@0: resetP2pNetworkInterface(); michael@0: gNetworkManager.unregisterNetworkInterface(_p2pNetworkInterface); michael@0: _p2pNetworkInterface.registered = false; michael@0: } michael@0: } michael@0: michael@0: function handleP2pNetworkInterfaceStateChanged() { michael@0: Services.obs.notifyObservers(_p2pNetworkInterface, michael@0: kNetworkInterfaceStateChangedTopic, michael@0: null); michael@0: } michael@0: michael@0: // Handle 'P2P_GROUP_STARTED' event. michael@0: // michael@0: // @param aInfo information carried by "P2P_GROUP_REMOVED" event: michael@0: // .ifname michael@0: // .role: "GO" or "client". michael@0: // michael@0: // @param aCallback Callback function. michael@0: function handleGroupRemoved(aInfo, aCallback) { michael@0: if (!_groupInfo) { michael@0: debug('No group info. Why?'); michael@0: aCallback(true); michael@0: return; michael@0: } michael@0: if (_groupInfo.ifname !== aInfo.ifname || michael@0: _groupInfo.role !== aInfo.role) { michael@0: debug('Unmatched group info: ' + JSON.stringify(_groupInfo) + michael@0: ' v.s. ' + JSON.stringify(aInfo)); michael@0: } michael@0: michael@0: // Update p2p network interface. michael@0: _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED; michael@0: handleP2pNetworkInterfaceStateChanged(); michael@0: michael@0: if (P2P_ROLE_GO === aInfo.role) { michael@0: aNetUtil.stopDhcpServer(function(success) { michael@0: debug('Stop DHCP server result: ' + success); michael@0: aCallback(true); michael@0: }); michael@0: } else { michael@0: aNetUtil.stopDhcp(aInfo.ifname, function() { michael@0: aCallback(true); michael@0: }); michael@0: } michael@0: } michael@0: michael@0: // Non state-specific event handler. michael@0: function handleEventCommon(aEvent) { michael@0: switch (aEvent.id) { michael@0: case EVENT_P2P_DEVICE_FOUND: michael@0: _observer.onPeerFound(aEvent.info); michael@0: break; michael@0: michael@0: case EVENT_P2P_DEVICE_LOST: michael@0: _observer.onPeerLost(aEvent.info); michael@0: break; michael@0: michael@0: case EVENT_P2P_CMD_DISABLE: michael@0: _onDisabled = aEvent.info.onDisabled; michael@0: _sm.gotoState(stateDisabling); michael@0: break; michael@0: michael@0: case EVENT_P2P_CMD_ENABLE_SCAN: michael@0: if (_scanBlocked) { michael@0: _scanPostponded = true; michael@0: aEvent.info.callback(true); michael@0: break; michael@0: } michael@0: aP2pCommand.p2pEnableScan(P2P_SCAN_TIMEOUT_SEC, aEvent.info.callback); michael@0: break; michael@0: michael@0: case EVENT_P2P_CMD_DISABLE_SCAN: michael@0: aP2pCommand.p2pDisableScan(aEvent.info.callback); michael@0: break; michael@0: michael@0: case EVENT_P2P_FIND_STOPPED: michael@0: break; michael@0: michael@0: case EVENT_P2P_CMD_BLOCK_SCAN: michael@0: _scanBlocked = true; michael@0: aP2pCommand.p2pDisableScan(function(success) {}); michael@0: break; michael@0: michael@0: case EVENT_P2P_CMD_UNBLOCK_SCAN: michael@0: _scanBlocked = false; michael@0: if (_scanPostponded) { michael@0: aP2pCommand.p2pEnableScan(P2P_SCAN_TIMEOUT_SEC, function(success) {}); michael@0: } michael@0: break; michael@0: michael@0: case EVENT_P2P_CMD_CONNECT: michael@0: case EVENT_P2P_CMD_DISCONNECT: michael@0: debug("The current state couldn't handle connect/disconnect request. Ignore it."); michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } // End of switch. michael@0: return true; michael@0: } michael@0: michael@0: function isInP2pManagedState(aState) { michael@0: let p2pManagedStates = [stateWaitingForConfirmation, michael@0: stateWaitingForNegReq, michael@0: stateProvisionDiscovery, michael@0: stateWaitingForInvitationConfirmation, michael@0: stateGroupAdding, michael@0: stateReinvoking, michael@0: stateConnecting, michael@0: stateConnected, michael@0: stateDisconnecting]; michael@0: michael@0: for (let i = 0; i < p2pManagedStates.length; i++) { michael@0: if (aState === p2pManagedStates[i]) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: function initTimeoutTimer(aTimeoutMs, aTimeoutEvent) { michael@0: let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: function onTimerFired() { michael@0: _sm.sendEvent({ id: aTimeoutEvent }); michael@0: timer = null; michael@0: } michael@0: timer.initWithCallback(onTimerFired.bind(this), aTimeoutMs, michael@0: Ci.nsITimer.TYPE_ONE_SHOT); michael@0: return timer; michael@0: } michael@0: michael@0: // Converts local WPS method to peer WPS method. michael@0: function toPeerWpsMethod(aLocalWpsMethod) { michael@0: switch (aLocalWpsMethod) { michael@0: case WPS_METHOD_DISPLAY: michael@0: return WPS_METHOD_KEYPAD; michael@0: case WPS_METHOD_KEYPAD: michael@0: return WPS_METHOD_DISPLAY; michael@0: case WPS_METHOD_PBC: michael@0: return WPS_METHOD_PBC; michael@0: default: michael@0: return WPS_METHOD_PBC; // Use "push button" as the default method. michael@0: } michael@0: } michael@0: michael@0: return p2pSm; michael@0: } michael@0: michael@0: this.WifiP2pManager.INTERFACE_NAME = P2P_INTERFACE_NAME;