tenfourfox/dom/wifi/WifiP2pManager.jsm

1650 lines
51 KiB
JavaScript

/* -*- 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;