dom/wifi/WifiP2pManager.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 "use strict";
     9 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    12 Cu.import("resource://gre/modules/StateMachine.jsm");
    13 Cu.import("resource://gre/modules/Services.jsm");
    14 Cu.import("resource://gre/modules/systemlibs.js");
    16 XPCOMUtils.defineLazyServiceGetter(this, "gSysMsgr",
    17                                    "@mozilla.org/system-message-internal;1",
    18                                    "nsISystemMessagesInternal");
    20 XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
    21                                    "@mozilla.org/network/manager;1",
    22                                    "nsINetworkManager");
    24 const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed";
    26 this.EXPORTED_SYMBOLS = ["WifiP2pManager"];
    28 const EVENT_IGNORED                      = -1;
    29 const EVENT_UNKNOWN                      = -2;
    31 // Events from supplicant for p2p.
    32 const EVENT_P2P_DEVICE_FOUND             = 0;
    33 const EVENT_P2P_DEVICE_LOST              = 1;
    34 const EVENT_P2P_GROUP_STARTED            = 2;
    35 const EVENT_P2P_GROUP_REMOVED            = 3;
    36 const EVENT_P2P_PROV_DISC_PBC_REQ        = 4;
    37 const EVENT_P2P_PROV_DISC_PBC_RESP       = 5;
    38 const EVENT_P2P_PROV_DISC_SHOW_PIN       = 6;
    39 const EVENT_P2P_PROV_DISC_ENTER_PIN      = 7;
    40 const EVENT_P2P_GO_NEG_REQUEST           = 8;
    41 const EVENT_P2P_GO_NEG_SUCCESS           = 9;
    42 const EVENT_P2P_GO_NEG_FAILURE           = 10;
    43 const EVENT_P2P_GROUP_FORMATION_SUCCESS  = 11;
    44 const EVENT_P2P_GROUP_FORMATION_FAILURE  = 12;
    45 const EVENT_P2P_FIND_STOPPED             = 13;
    46 const EVENT_P2P_INVITATION_RESULT        = 14;
    47 const EVENT_P2P_INVITATION_RECEIVED      = 15;
    48 const EVENT_P2P_PROV_DISC_FAILURE        = 16;
    50 // Events from supplicant but not p2p specific.
    51 const EVENT_AP_STA_DISCONNECTED          = 100;
    52 const EVENT_AP_STA_CONNECTED             = 101;
    54 // Events from DOM.
    55 const EVENT_P2P_SET_PAIRING_CONFIRMATION = 1000;
    56 const EVENT_P2P_CMD_CONNECT              = 1001;
    57 const EVENT_P2P_CMD_DISCONNECT           = 1002;
    58 const EVENT_P2P_CMD_ENABLE               = 1003;
    59 const EVENT_P2P_CMD_DISABLE              = 1004;
    60 const EVENT_P2P_CMD_ENABLE_SCAN          = 1005;
    61 const EVENT_P2P_CMD_DISABLE_SCAN         = 1006;
    62 const EVENT_P2P_CMD_BLOCK_SCAN           = 1007;
    63 const EVENT_P2P_CMD_UNBLOCK_SCAN         = 1008;
    65 // Internal events.
    66 const EVENT_TIMEOUT_PAIRING_CONFIRMATION = 10000;
    67 const EVENT_TIMEOUT_NEG_REQ              = 10001;
    68 const EVENT_TIMEOUT_CONNECTING           = 10002;
    69 const EVENT_P2P_ENABLE_SUCCESS           = 10003;
    70 const EVENT_P2P_ENABLE_FAILED            = 10004;
    71 const EVENT_P2P_DISABLE_SUCCESS          = 10005;
    73 // WPS method string.
    74 const WPS_METHOD_PBC     = "pbc";
    75 const WPS_METHOD_DISPLAY = "display";
    76 const WPS_METHOD_KEYPAD  = "keypad";
    78 // Role string.
    79 const P2P_ROLE_GO     = "GO";
    80 const P2P_ROLE_CLIENT = "client";
    82 // System message for pairing request.
    83 const PAIRING_REQUEST_SYS_MSG = "wifip2p-pairing-request";
    85 // Configuration.
    86 const P2P_INTERFACE_NAME = "p2p0";
    87 const DEFAULT_GO_INTENT = 15;
    88 const DEFAULT_P2P_DEVICE_NAME = "FirefoxPhone";
    89 const P2P_SCAN_TIMEOUT_SEC = 120;
    90 const DEFAULT_P2P_WPS_METHODS = "virtual_push_button physical_display keypad"; // For wpa_supplicant.
    91 const DEFAULT_P2P_DEVICE_TYPE = "10-0050F204-5"; // For wpa_supplicant.
    93 const GO_NETWORK_INTERFACE = {
    94   ip:         "192.168.2.1",
    95   maskLength: 24,
    96   gateway:    "192.168.2.1",
    97   dns1:       "0.0.0.0",
    98   dns2:       "0.0.0.0",
    99   dhcpServer: "192.168.2.1"
   100 };
   102 const GO_DHCP_SERVER_IP_RANGE = {
   103   startIp: "192.168.2.10",
   104   endIp:   "192.168.2.30"
   105 };
   107 let gDebug = false;
   109 // Device Capability bitmap
   110 const DEVICE_CAPAB_SERVICE_DISCOVERY         = 1;
   111 const DEVICE_CAPAB_CLIENT_DISCOVERABILITY    = 1<<1;
   112 const DEVICE_CAPAB_CONCURRENT_OPER           = 1<<2;
   113 const DEVICE_CAPAB_INFRA_MANAGED             = 1<<3;
   114 const DEVICE_CAPAB_DEVICE_LIMIT              = 1<<4;
   115 const DEVICE_CAPAB_INVITATION_PROCEDURE      = 1<<5;
   117 // Group Capability bitmap
   118 const GROUP_CAPAB_GROUP_OWNER                = 1;
   119 const GROUP_CAPAB_PERSISTENT_GROUP           = 1<<1;
   120 const GROUP_CAPAB_GROUP_LIMIT                = 1<<2;
   121 const GROUP_CAPAB_INTRA_BSS_DIST             = 1<<3;
   122 const GROUP_CAPAB_CROSS_CONN                 = 1<<4;
   123 const GROUP_CAPAB_PERSISTENT_RECONN          = 1<<5;
   124 const GROUP_CAPAB_GROUP_FORMATION            = 1<<6;
   126 // Constants defined in wpa_supplicants.
   127 const DEV_PW_REGISTRAR_SPECIFIED = 5;
   128 const DEV_PW_USER_SPECIFIED      = 1;
   129 const DEV_PW_PUSHBUTTON          = 4;
   131 this.WifiP2pManager = function (aP2pCommand, aNetUtil) {
   132   function debug(aMsg) {
   133     if (gDebug) {
   134       dump('-------------- WifiP2pManager: ' + aMsg);
   135     }
   136   }
   138   let manager = {};
   140   let _stateMachine = P2pStateMachine(aP2pCommand, aNetUtil);
   142   // Set debug flag to true or false.
   143   //
   144   // @param aDebug Boolean to indicate enabling or disabling the debug flag.
   145   manager.setDebug = function(aDebug) {
   146     gDebug = aDebug;
   147   };
   149   // Set observer of observing internal state machine events.
   150   //
   151   // @param aObserver Used to notify WifiWorker what's happening
   152   //        in the internal p2p state machine.
   153   manager.setObserver = function(aObserver) {
   154     _stateMachine.setObserver(aObserver);
   155   };
   157   // Handle wpa_supplicant events.
   158   //
   159   // @param aEventString string from wpa_supplicant.
   160   manager.handleEvent = function(aEventString) {
   161     let event = parseEventString(aEventString);
   162     if (EVENT_UNKNOWN === event.id || EVENT_IGNORED === event.id) {
   163       debug('Unknow or ignored event: ' + aEventString);
   164       return false;
   165     }
   166     return _stateMachine.sendEvent(event);
   167   };
   169   // Set the confirmation of pairing request.
   170   //
   171   // @param aResult Object of confirmation result which contains:
   172   //    .accepted: user granted.
   173   //    .pin: pin code which is displaying or input by user.
   174   //    .wpsMethod: string of "pbc" or "display" or "keypad".
   175   manager.setPairingConfirmation = function(aResult) {
   176     let event = {
   177       id: EVENT_P2P_SET_PAIRING_CONFIRMATION,
   178       info: {
   179         accepted: aResult.accepted,
   180         pin: aResult.pin
   181       }
   182     };
   183     _stateMachine.sendEvent(event);
   184   };
   186   // Connect to a known peer.
   187   //
   188   // @param aAddress MAC address of the peer to connect.
   189   // @param aWpsMethod String of "pbc" or "display" or "keypad".
   190   // @param aGoIntent Number from 0 to 15.
   191   // @param aCallback Callback |true| on attempting to connect.
   192   //                          |false| on failed to connect.
   193   manager.connect = function(aAddress, aWpsMethod, aGoIntent, aCallback) {
   194     let event = {
   195       id: EVENT_P2P_CMD_CONNECT,
   196       info: {
   197         wpsMethod: aWpsMethod,
   198         address: aAddress,
   199         goIntent: aGoIntent,
   200         onDoConnect: aCallback
   201       }
   202     };
   203     _stateMachine.sendEvent(event);
   204   };
   206   // Disconnect with a known peer.
   207   //
   208   // @param aAddress The address the user desires to disconect.
   209   // @param aCallback Callback |true| on "attempting" to disconnect.
   210   //                           |false| on failed to disconnect.
   211   manager.disconnect = function(aAddress, aCallback) {
   212     let event = {
   213       id: EVENT_P2P_CMD_DISCONNECT,
   214       info: {
   215         address: aAddress,
   216         onDoDisconnect: aCallback
   217       }
   218     };
   219     _stateMachine.sendEvent(event);
   220   };
   222   // Enable/disable wifi p2p.
   223   //
   224   // @param aEnabled |true| to enable, |false| to disable.
   225   // @param aCallbacks object for callbacks:
   226   //   .onEnabled
   227   //   .onDisabled
   228   //   .onSupplicantConnected
   229   manager.setEnabled = function(aEnabled, aCallbacks) {
   230     let event = {
   231       id: (aEnabled ? EVENT_P2P_CMD_ENABLE : EVENT_P2P_CMD_DISABLE),
   232       info: {
   233         onEnabled: aCallbacks.onEnabled,
   234         onDisabled: aCallbacks.onDisabled,
   235         onSupplicantConnected: aCallbacks.onSupplicantConnected
   236       }
   237     };
   238     _stateMachine.sendEvent(event);
   239   };
   241   // Enable/disable the wifi p2p scan.
   242   //
   243   // @param aEnabled |true| to enable scan, |false| to disable scan.
   244   // @param aCallback Callback |true| on success to enable/disable scan.
   245   //                           |false| on failed to enable/disable scan.
   246   manager.setScanEnabled = function(aEnabled, aCallback) {
   247     let event = {
   248       id: (aEnabled ? EVENT_P2P_CMD_ENABLE_SCAN : EVENT_P2P_CMD_DISABLE_SCAN),
   249       info: { callback: aCallback }
   250     };
   251     _stateMachine.sendEvent(event);
   252   };
   254   // Block wifi p2p scan.
   255   manager.blockScan = function() {
   256     _stateMachine.sendEvent({ id: EVENT_P2P_CMD_BLOCK_SCAN });
   257   };
   259   // Un-block and do the pending scan if any.
   260   manager.unblockScan = function() {
   261     _stateMachine.sendEvent({ id: EVENT_P2P_CMD_UNBLOCK_SCAN });
   262   };
   264   // Set the p2p device name.
   265   manager.setDeviceName = function(newDeivceName, callback) {
   266     aP2pCommand.setDeviceName(newDeivceName, callback);
   267   };
   269   // Parse wps_supplicant event string.
   270   //
   271   // @param aEventString The raw event string from wpa_supplicant.
   272   //
   273   // @return Object:
   274   //   .id: a number to represent an event.
   275   //   .info: the additional information carried by this event string.
   276   function parseEventString(aEventString) {
   277     if (isIgnoredEvent(aEventString)) {
   278       return { id: EVENT_IGNORED };
   279     }
   281     let match = RegExp("p2p_dev_addr=([0-9a-fA-F:]+) " +
   282                        "pri_dev_type=([0-9a-zA-Z-]+) " +
   283                        "name='(.*)' " +
   284                        "config_methods=0x([0-9a-fA-F]+) " +
   285                        "dev_capab=0x([0-9a-fA-F]+) " +
   286                        "group_capab=0x([0-9a-fA-F]+) ").exec(aEventString + ' ');
   288     let tokens = aEventString.split(" ");
   290     let id = EVENT_UNKNOWN;
   292     // general info.
   293     let info = {};
   295     if (match) {
   296       info = {
   297         address:   match[1] ? match[1] : null,
   298         type:      match[2] ? match[2] : null,
   299         name:      match[3] ? match[3] : null,
   300         wpsFlag:   match[4] ? parseInt(match[4], 16) : null,
   301         devFlag:   match[5] ? parseInt(match[5], 16) : null,
   302         groupFlag: match[6] ? parseInt(match[6], 16) : null
   303       };
   304     }
   306     if (0 === aEventString.indexOf("P2P-DEVICE-FOUND")) {
   307       id = EVENT_P2P_DEVICE_FOUND;
   308       info.wpsCapabilities = wpsFlagToCapabilities(info.wpsFlag);
   309       info.isGroupOwner = isPeerGroupOwner(info.groupFlag);
   310     } else if (0 === aEventString.indexOf("P2P-DEVICE-LOST")) {
   311       // e.g. "P2P-DEVICE-LOST p2p_dev_addr=5e:0a:5b:15:1f:80".
   312       id = EVENT_P2P_DEVICE_LOST;
   313       info.address = /p2p_dev_addr=([0-9a-f:]+)/.exec(aEventString)[1];
   314     } else if (0 === aEventString.indexOf("P2P-GROUP-STARTED")) {
   315       // e.g. "P2P-GROUP-STARTED wlan0-p2p-0 GO ssid="DIRECT-3F Testing
   316       //       passphrase="12345678" go_dev_addr=02:40:61:c2:f3:b7 [PERSISTENT]".
   318       id = EVENT_P2P_GROUP_STARTED;
   319       let groupMatch = RegExp('ssid="(.*)" ' +
   320                               'freq=([0-9]*) ' +
   321                               '(passphrase|psk)=([^ ]+) ' +
   322                               'go_dev_addr=([0-9a-f:]+)').exec(aEventString);
   323       info.ssid = groupMatch[1];
   324       info.freq = groupMatch[2];
   325       if ('passphrase' === groupMatch[3]) {
   326         let s = groupMatch[4]; // e.g. "G7jHkkz9".
   327         info.passphrase = s.substring(1, s.length-1); // Trim the double quote.
   328       } else { // psk
   329         info.psk = groupMatch[4];
   330       }
   331       info.goAddress = groupMatch[5];
   332       info.ifname = tokens[1];
   333       info.role = tokens[2];
   334     } else if (0 === aEventString.indexOf("P2P-GROUP-REMOVED")) {
   335       id = EVENT_P2P_GROUP_REMOVED;
   336       // e.g. "P2P-GROUP-REMOVED wlan0-p2p-0 GO".
   337       info.ifname = tokens[1];
   338       info.role = tokens[2];
   339     } else if (0 === aEventString.indexOf("P2P-PROV-DISC-PBC-REQ")) {
   340       id = EVENT_P2P_PROV_DISC_PBC_REQ;
   341       info.wpsMethod = WPS_METHOD_PBC;
   342     } else if (0 === aEventString.indexOf("P2P-PROV-DISC-PBC-RESP")) {
   343       id = EVENT_P2P_PROV_DISC_PBC_RESP;
   344       // The address is different from the general pattern.
   345       info.address = aEventString.split(" ")[1];
   346       info.wpsMethod = WPS_METHOD_PBC;
   347     } else if (0 === aEventString.indexOf("P2P-PROV-DISC-SHOW-PIN")) {
   348       id = EVENT_P2P_PROV_DISC_SHOW_PIN;
   349       // Obtain peer address and pin from tokens.
   350       info.address = tokens[1];
   351       info.pin     = tokens[2];
   352       info.wpsMethod = WPS_METHOD_DISPLAY;
   353     } else if (0 === aEventString.indexOf("P2P-PROV-DISC-ENTER-PIN")) {
   354       id = EVENT_P2P_PROV_DISC_ENTER_PIN;
   355       // Obtain peer address from tokens.
   356       info.address = tokens[1];
   357       info.wpsMethod = WPS_METHOD_KEYPAD;
   358     } else if (0 === aEventString.indexOf("P2P-GO-NEG-REQUEST")) {
   359       id = EVENT_P2P_GO_NEG_REQUEST;
   360       info.address = tokens[1];
   361       switch (parseInt(tokens[2].split("=")[1], 10)) {
   362         case DEV_PW_REGISTRAR_SPECIFIED: // (5) Peer is display.
   363           info.wpsMethod = WPS_METHOD_KEYPAD;
   364           break;
   365         case DEV_PW_USER_SPECIFIED: // (1) Peer is keypad.
   366           info.wpsMethod = WPS_METHOD_DISPLAY;
   367           break;
   368         case DEV_PW_PUSHBUTTON: // (4) Peer is pbc.
   369           info.wpsMethod = WPS_METHOD_PBC;
   370           break;
   371         default:
   372           debug('Unknown wps method from event P2P-GO-NEG-REQUEST');
   373           break;
   374       }
   375     } else if (0 === aEventString.indexOf("P2P-GO-NEG-SUCCESS")) {
   376       id = EVENT_P2P_GO_NEG_SUCCESS;
   377     } else if (0 === aEventString.indexOf("P2P-GO-NEG-FAILURE")) {
   378       id = EVENT_P2P_GO_NEG_FAILURE;
   379     } else if (0 === aEventString.indexOf("P2P-GROUP-FORMATION-FAILURE")) {
   380       id = EVENT_P2P_GROUP_FORMATION_FAILURE;
   381     } else if (0 === aEventString.indexOf("P2P-GROUP-FORMATION-SUCCESS")) {
   382       id = EVENT_P2P_GROUP_FORMATION_SUCCESS;
   383     } else if (0 === aEventString.indexOf("P2P-FIND-STOPPED")) {
   384       id = EVENT_P2P_FIND_STOPPED;
   385     } else if (0 === aEventString.indexOf("P2P-INVITATION-RESULT")) {
   386       id = EVENT_P2P_INVITATION_RESULT;
   387       info.status = /status=([0-9]+)/.exec(aEventString)[1];
   388     } else if (0 === aEventString.indexOf("P2P-INVITATION-RECEIVED")) {
   389       // e.g. "P2P-INVITATION-RECEIVED sa=32:85:a9:da:e6:1f persistent=7".
   390       id = EVENT_P2P_INVITATION_RECEIVED;
   391       info.address = /sa=([0-9a-f:]+)/.exec(aEventString)[1];
   392       info.netId = /persistent=([0-9]+)/.exec(aEventString)[1];
   393     } else if (0 === aEventString.indexOf("P2P-PROV-DISC-FAILURE")) {
   394       id = EVENT_P2P_PROV_DISC_FAILURE;
   395     } else {
   396       // Not P2P event but we do receive it. Try to recognize it.
   397       if (0 === aEventString.indexOf("AP-STA-DISCONNECTED")) {
   398         id = EVENT_AP_STA_DISCONNECTED;
   399         info.address = tokens[1];
   400       } else if (0 === aEventString.indexOf("AP-STA-CONNECTED")) {
   401         id = EVENT_AP_STA_CONNECTED;
   402         info.address = tokens[1];
   403       } else {
   404         // Neither P2P event nor recognized supplicant event.
   405         debug('Unknwon event string: ' + aEventString);
   406       }
   407     }
   409     let event = {id: id, info: info};
   410     debug('Event parsing result: ' + aEventString + ": " + JSON.stringify(event));
   412     return event;
   413   }
   415   function isIgnoredEvent(aEventString) {
   416     const IGNORED_EVENTS = [
   417       "CTRL-EVENT-BSS-ADDED",
   418       "CTRL-EVENT-BSS-REMOVED",
   419       "CTRL-EVENT-SCAN-RESULTS",
   420       "CTRL-EVENT-STATE-CHANGE",
   421       "WPS-AP-AVAILABLE",
   422       "WPS-ENROLLEE-SEEN"
   423     ];
   424     for(let i = 0; i < IGNORED_EVENTS.length; i++) {
   425       if (0 === aEventString.indexOf(IGNORED_EVENTS[i])) {
   426         return true;
   427       }
   428     }
   429     return false;
   430   }
   432   function isPeerGroupOwner(aGroupFlag) {
   433     return (aGroupFlag & GROUP_CAPAB_GROUP_OWNER) !== 0;
   434   }
   436   // Convert flag to a wps capability array.
   437   //
   438   // @param aWpsFlag Number that represents the wps capabilities.
   439   // @return Array of WPS flag.
   440   function wpsFlagToCapabilities(aWpsFlag) {
   441     let wpsCapabilities = [];
   442     if (aWpsFlag & 0x8) {
   443       wpsCapabilities.push(WPS_METHOD_DISPLAY);
   444     }
   445     if (aWpsFlag & 0x80) {
   446       wpsCapabilities.push(WPS_METHOD_PBC);
   447     }
   448     if (aWpsFlag & 0x100) {
   449       wpsCapabilities.push(WPS_METHOD_KEYPAD);
   450     }
   451     return wpsCapabilities;
   452   }
   454   _stateMachine.start();
   455   return manager;
   456 };
   458 function P2pStateMachine(aP2pCommand, aNetUtil) {
   459   function debug(aMsg) {
   460     if (gDebug) {
   461       dump('-------------- WifiP2pStateMachine: ' + aMsg);
   462     }
   463   }
   465   let p2pSm = {};  // The state machine to return.
   467   let _sm = StateMachine('WIFIP2P'); // The general purpose state machine.
   469   // Information we need to keep track across states.
   470   let _observer;
   472   let _onEnabled;
   473   let _onDisabled;
   474   let _onSupplicantConnected;
   475   let _savedConfig = {}; // Configuration used to do P2P_CONNECT.
   476   let _groupInfo = {};   // The information of the group we have formed.
   477   let _removedGroupInfo = {}; // Used to store the group info we are going to remove.
   479   let _scanBlocked = false;
   480   let _scanPostponded = false;
   482   let _localDevice = {
   483     address: "",
   484     deviceName: DEFAULT_P2P_DEVICE_NAME + "_" + libcutils.property_get("ro.build.product"),
   485     wpsCapabilities: [WPS_METHOD_PBC, WPS_METHOD_KEYPAD, WPS_METHOD_DISPLAY]
   486   };
   488   let _p2pNetworkInterface = {
   489     QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
   491     state: Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED,
   492     type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI_P2P,
   493     name: P2P_INTERFACE_NAME,
   494     ips: [],
   495     prefixLengths: [],
   496     dnses: [],
   497     gateways: [],
   498     httpProxyHost: null,
   499     httpProxyPort: null,
   501     // help
   502     registered: false,
   504     getAddresses: function (ips, prefixLengths) {
   505       ips.value = this.ips.slice();
   506       prefixLengths.value = this.prefixLengths.slice();
   508       return this.ips.length;
   509     },
   511     getGateways: function (count) {
   512       if (count) {
   513         count.value = this.gateways.length;
   514       }
   515       return this.gateways.slice();
   516     },
   518     getDnses: function (count) {
   519       if (count) {
   520         count.value = this.dnses.length;
   521       }
   522       return this.dnses.slice();
   523     }
   524   };
   526   //---------------------------------------------------------
   527   // State machine APIs.
   528   //---------------------------------------------------------
   530   // Register the observer which is implemented in WifiP2pWorkerObserver.jsm.
   531   //
   532   // @param aObserver:
   533   //   .onEnabled
   534   //   .onDisbaled
   535   //   .onPeerFound
   536   //   .onPeerLost
   537   //   .onConnecting
   538   //   .onConnected
   539   //   .onDisconnected
   540   //   .onLocalDeviceChanged
   541   p2pSm.setObserver = function(aObserver) {
   542     _observer = aObserver;
   543   };
   545   p2pSm.start = function() {
   546     _sm.start(stateDisabled);
   547   };
   549   p2pSm.sendEvent = function(aEvent) {
   550     let willBeHandled = isInP2pManagedState(_sm.getCurrentState());
   551     _sm.sendEvent(aEvent);
   552     return willBeHandled;
   553   };
   555   // Initialize internal state machine _sm.
   556   _sm.setDefaultEventHandler(handleEventCommon);
   558   //----------------------------------------------------------
   559   // State definition.
   560   //----------------------------------------------------------
   562   // The initial state.
   563   var stateDisabled = _sm.makeState("DISABLED", {
   564     enter: function() {
   565       _onEnabled = null;
   566       _onSupplicantConnected = null;
   567       _savedConfig = null;
   568       _groupInfo = null;
   569       _removedGroupInfo = null;
   570       _scanBlocked = false;
   571       _scanPostponded = false;
   573       unregisterP2pNetworkInteface();
   574     },
   576     handleEvent: function(aEvent) {
   577       switch (aEvent.id) {
   578         case EVENT_P2P_CMD_ENABLE:
   579           _onEnabled = aEvent.info.onEnabled;
   580           _onSupplicantConnected = aEvent.info.onSupplicantConnected;
   581           _sm.gotoState(stateEnabling);
   582           break;
   584         default:
   585           return false;
   586       } // End of switch.
   587       return true;
   588     }
   589   });
   591   // The state where we are trying to enable wifi p2p.
   592   var stateEnabling = _sm.makeState("ENABLING", {
   593     enter: function() {
   595       function onFailure()
   596       {
   597         _onEnabled(false);
   598         _sm.gotoState(stateDisabled);
   599       }
   601       function onSuccess()
   602       {
   603         _onEnabled(true);
   604         _sm.gotoState(stateInactive);
   605       }
   607       _sm.pause();
   609       // Step 1: Connect to p2p0.
   610       aP2pCommand.connectToSupplicant(function (status) {
   611         let detail;
   613         if (0 !== status) {
   614           debug('Failed to connect to p2p0');
   615           onFailure();
   616           return;
   617         }
   619         debug('wpa_supplicant p2p0 connected!');
   620         _onSupplicantConnected();
   622         // Step 2: Get MAC address.
   623         if (!_localDevice.address) {
   624           aP2pCommand.getMacAddress(function (address) {
   625             if (!address) {
   626               debug('Failed to get MAC address....');
   627               onFailure();
   628               return;
   629             }
   630             debug('Got mac address: ' + address);
   631             _localDevice.address = address;
   632             _observer.onLocalDeviceChanged(_localDevice);
   633           });
   634         }
   636         // Step 3: Enable p2p with the device name and wps methods.
   637         detail = { deviceName: _localDevice.deviceName,
   638                    deviceType: libcutils.property_get("ro.moz.wifi.p2p_device_type") || DEFAULT_P2P_DEVICE_TYPE,
   639                    wpsMethods: libcutils.property_get("ro.moz.wifi.p2p_wps_methods") || DEFAULT_P2P_WPS_METHODS };
   641         aP2pCommand.p2pEnable(detail, function (success) {
   642           if (!success) {
   643             debug('Failed to enable p2p');
   644             onFailure();
   645             return;
   646           }
   648           debug('P2P is enabled! Enabling net interface...');
   650           // Step 4: Enable p2p0 net interface. wpa_supplicant may have
   651           //         already done it for us.
   652           aNetUtil.enableInterface(P2P_INTERFACE_NAME, function (success) {
   653             onSuccess();
   654           });
   655         });
   656       });
   657     },
   659     handleEvent: function(aEvent) {
   660       // We won't receive any event since all of them will be blocked.
   661       return true;
   662     }
   663   });
   665   // The state just after enabling wifi direct.
   666   var stateInactive = _sm.makeState("INACTIVE", {
   667     enter: function() {
   668       registerP2pNetworkInteface();
   670       if (_sm.getPreviousState() !== stateEnabling) {
   671         _observer.onDisconnected(_savedConfig);
   672       }
   674       _savedConfig = null; // Used to connect p2p peer.
   675       _groupInfo   = null; // The information of the formed group.
   676     },
   678     handleEvent: function(aEvent) {
   679       switch (aEvent.id) {
   680         // Receiving the following 3 states implies someone is trying to
   681         // connect to me.
   682         case EVENT_P2P_PROV_DISC_PBC_REQ:
   683         case EVENT_P2P_PROV_DISC_SHOW_PIN:
   684         case EVENT_P2P_PROV_DISC_ENTER_PIN:
   685           debug('Someone is trying to connect to me: ' + JSON.stringify(aEvent.info));
   687           _savedConfig = {
   688             name:      aEvent.info.name,
   689             address:   aEvent.info.address,
   690             wpsMethod: aEvent.info.wpsMethod,
   691             goIntent:  DEFAULT_GO_INTENT,
   692             pin:       aEvent.info.pin // EVENT_P2P_PROV_DISC_SHOW_PIN only.
   693           };
   695           _sm.gotoState(stateWaitingForConfirmation);
   696           break;
   698         // Connect to a peer.
   699         case EVENT_P2P_CMD_CONNECT:
   700           debug('Trying to connect to peer: ' + JSON.stringify(aEvent.info));
   702           _savedConfig = {
   703             address:   aEvent.info.address,
   704             wpsMethod: aEvent.info.wpsMethod,
   705             goIntent:  aEvent.info.goIntent
   706           };
   708           _sm.gotoState(stateProvisionDiscovery);
   709           aEvent.info.onDoConnect(true);
   710           break;
   712         case EVENT_P2P_INVITATION_RECEIVED:
   713           _savedConfig = {
   714             address: aEvent.info.address,
   715             wpsMethod: WPS_METHOD_PBC,
   716             goIntent: DEFAULT_GO_INTENT,
   717             netId: aEvent.info.netId
   718           };
   719           _sm.gotoState(stateWaitingForInvitationConfirmation);
   720           break;
   722         case EVENT_P2P_GROUP_STARTED:
   723           // Most likely the peer just reinvoked a peristen group and succeeeded.
   725           _savedConfig = { address: aEvent.info.goAddress };
   727           _sm.pause();
   728           handleGroupStarted(aEvent.info, function (success) {
   729             _sm.resume();
   730           });
   731           break;
   733         case EVENT_AP_STA_DISCONNECTED:
   734           // We will hit this case when we used to be a group owner and
   735           // requested to remove the group we owned.
   736           break;
   738         default:
   739           return false;
   740       } // End of switch.
   741       return true;
   742     },
   743   });
   745   // Waiting for user's confirmation.
   746   var stateWaitingForConfirmation = _sm.makeState("WAITING_FOR_CONFIRMATION", {
   747     timeoutTimer: null,
   749     enter: function() {
   750       gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
   751       this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
   752     },
   754     handleEvent: function(aEvent) {
   755       switch (aEvent.id) {
   756         case EVENT_P2P_SET_PAIRING_CONFIRMATION:
   757           if (!aEvent.info.accepted) {
   758             debug('User rejected this request');
   759             _sm.gotoState(stateInactive); // Reset to inactive state.
   760             break;
   761           }
   763           debug('User accepted this request');
   765           // The only information we may have to grab from user.
   766           _savedConfig.pin = aEvent.info.pin;
   768           // The case that user requested to form a group ealier on.
   769           // Just go to connecting state and do p2p_connect.
   770           if (_sm.getPreviousState() === stateProvisionDiscovery) {
   771             _sm.gotoState(stateConnecting);
   772             break;
   773           }
   775           // Otherwise, wait for EVENT_P2P_GO_NEG_REQUEST.
   776           _sm.gotoState(stateWaitingForNegReq);
   777           break;
   779         case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
   780           debug('Confirmation timeout!');
   781           _sm.gotoState(stateInactive);
   782           break;
   784         case EVENT_P2P_GO_NEG_REQUEST:
   785           _sm.deferEvent(aEvent);
   786           break;
   788         default:
   789           return false;
   790       } // End of switch.
   792       return true;
   793     },
   795     exit: function() {
   796       this.timeoutTimer.cancel();
   797       this.timeoutTimer = null;
   798     }
   799   });
   801   var stateWaitingForNegReq = _sm.makeState("WAITING_FOR_NEG_REQ", {
   802     timeoutTimer: null,
   804     enter: function() {
   805       debug('Wait for EVENT_P2P_GO_NEG_REQUEST');
   806       this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_NEG_REQ);
   807     },
   809     handleEvent: function(aEvent) {
   810       switch (aEvent.id) {
   811         case EVENT_P2P_GO_NEG_REQUEST:
   812           if (aEvent.info.wpsMethod !== _savedConfig.wpsMethod) {
   813             debug('Unmatched wps method: ' + aEvent.info.wpsMethod + ", " + _savedConfig.wpsMetho);
   814           }
   815           _sm.gotoState(stateConnecting);
   816           break;
   818         case EVENT_TIMEOUT_NEG_REQ:
   819           debug("Waiting for NEG-REQ timeout");
   820           _sm.gotoState(stateInactive);
   821           break;
   823         default:
   824           return false;
   825       } // End of switch.
   826       return true;
   827     },
   829     exit: function() {
   830       this.timeoutTimer.cancel();
   831       this.timeoutTimer = null;
   832     }
   833   });
   835   // Waiting for user's confirmation for invitation.
   836   var stateWaitingForInvitationConfirmation = _sm.makeState("WAITING_FOR_INV_CONFIRMATION", {
   837     timeoutTimer: null,
   839     enter: function() {
   840       gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
   841       this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
   842     },
   844     handleEvent: function(aEvent) {
   845       switch (aEvent.id) {
   846         case EVENT_P2P_SET_PAIRING_CONFIRMATION:
   847           if (!aEvent.info.accepted) {
   848             debug('User rejected this request');
   849             _sm.gotoState(stateInactive); // Reset to inactive state.
   850             break;
   851           }
   853           debug('User accepted this request');
   854           _sm.pause();
   855           aP2pCommand.p2pGetGroupCapab(_savedConfig.address, function (gc) {
   856             let isPeeGroupOwner = gc & GROUP_CAPAB_GROUP_OWNER;
   857             _sm.gotoState(isPeeGroupOwner ? stateGroupAdding : stateReinvoking);
   858           });
   860           break;
   862         case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
   863           debug('Confirmation timeout!');
   864           _sm.gotoState(stateInactive);
   865           break;
   867         default:
   868           return false;
   869       } // End of switch.
   871       return true;
   872     },
   874     exit: function() {
   875       this.timeoutTimer.cancel();
   876       this.timeoutTimer = null;
   877     }
   878   });
   880   var stateGroupAdding = _sm.makeState("GROUP_ADDING", {
   881     timeoutTimer: null,
   883     enter: function() {
   884       let self = this;
   886       _observer.onConnecting(_savedConfig);
   888       _sm.pause();
   889       aP2pCommand.p2pGroupAdd(_savedConfig.netId, function (success) {
   890         if (!success) {
   891           _sm.gotoState(stateInactive);
   892           return;
   893         }
   894         // Waiting for EVENT_P2P_GROUP_STARTED.
   895         self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
   896         _sm.resume();
   897       });
   898     },
   900     handleEvent: function(aEvent) {
   901       switch (aEvent.id) {
   902         case EVENT_P2P_GROUP_STARTED:
   903           _sm.pause();
   904           handleGroupStarted(aEvent.info, function (success) {
   905             _sm.resume();
   906           });
   907           break;
   909         case EVENT_P2P_GO_NEG_FAILURE:
   910           debug('Negotiation failure. Go back to inactive state');
   911           _sm.gotoState(stateInactive);
   912           break;
   914         case EVENT_TIMEOUT_CONNECTING:
   915           debug('Connecting timeout! Go back to inactive state');
   916           _sm.gotoState(stateInactive);
   917           break;
   919         case EVENT_P2P_GROUP_FORMATION_SUCCESS:
   920         case EVENT_P2P_GO_NEG_SUCCESS:
   921           break;
   923         case EVENT_P2P_GROUP_FORMATION_FAILURE:
   924           debug('Group formation failure');
   925           _sm.gotoState(stateInactive);
   926           break;
   928         case EVENT_P2P_GROUP_REMOVED:
   929           debug('Received P2P-GROUP-REMOVED due to previous failed handleGroupdStarted()');
   930           _removedGroupInfo = {
   931             role:   aEvent.info.role,
   932             ifname: aEvent.info.ifname
   933           };
   934           _sm.gotoState(stateDisconnecting);
   935           break;
   937         default:
   938           return false;
   939       } // End of switch.
   941       return true;
   942     },
   944     exit: function() {
   945       this.timeoutTimer.cancel();
   946       this.timeoutTimer = null;
   947     }
   948   });
   950   var stateReinvoking = _sm.makeState("REINVOKING", {
   951     timeoutTimer: null,
   953     enter: function() {
   954       let self = this;
   956       _observer.onConnecting(_savedConfig);
   957       _sm.pause();
   958       aP2pCommand.p2pReinvoke(_savedConfig.netId, _savedConfig.address, function(success) {
   959         if (!success) {
   960           _sm.gotoState(stateInactive);
   961           return;
   962         }
   963         // Waiting for EVENT_P2P_GROUP_STARTED.
   964         self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
   965         _sm.resume();
   966       });
   967     },
   969     handleEvent: function(aEvent) {
   970       switch (aEvent.id) {
   971         case EVENT_P2P_GROUP_STARTED:
   972           _sm.pause();
   973           handleGroupStarted(aEvent.info, function(success) {
   974             _sm.resume();
   975           });
   976           break;
   978         case EVENT_P2P_GO_NEG_FAILURE:
   979           debug('Negotiation failure. Go back to inactive state');
   980           _sm.gotoState(stateInactive);
   981           break;
   983         case EVENT_TIMEOUT_CONNECTING:
   984           debug('Connecting timeout! Go back to inactive state');
   985           _sm.gotoState(stateInactive);
   986           break;
   988         case EVENT_P2P_GROUP_FORMATION_SUCCESS:
   989         case EVENT_P2P_GO_NEG_SUCCESS:
   990           break;
   992         case EVENT_P2P_GROUP_FORMATION_FAILURE:
   993           debug('Group formation failure');
   994           _sm.gotoState(stateInactive);
   995           break;
   997         case EVENT_P2P_GROUP_REMOVED:
   998           debug('Received P2P-GROUP-REMOVED due to previous failed handleGroupdStarted()');
   999           _removedGroupInfo = {
  1000             role:   aEvent.info.role,
  1001             ifname: aEvent.info.ifname
  1002           };
  1003           _sm.gotoState(stateDisconnecting);
  1004           break;
  1006         default:
  1007           return false;
  1008       } // End of switch.
  1010       return true;
  1011     },
  1013     exit: function() {
  1014       this.timeoutTimer.cancel();
  1016   });
  1018   var stateProvisionDiscovery = _sm.makeState("PROVISION_DISCOVERY", {
  1019     enter: function() {
  1020       function onDiscoveryCommandSent(success) {
  1021         if (!success) {
  1022           _sm.gotoState(stateInactive);
  1023           debug('Failed to send p2p_prov_disc. Go back to inactive state.');
  1024           return;
  1027         debug('p2p_prov_disc has been sent.');
  1029         _sm.resume();
  1030         // Waiting for EVENT_P2P_PROV_DISC_PBC_RESP or
  1031         //             EVENT_P2P_PROV_DISC_SHOW_PIN or
  1032         //             EVENT_P2P_PROV_DISC_ENTER_PIN.
  1035       _sm.pause();
  1036       aP2pCommand.p2pProvDiscovery(_savedConfig.address,
  1037                                    toPeerWpsMethod(_savedConfig.wpsMethod),
  1038                                    onDiscoveryCommandSent);
  1039     },
  1041     handleEvent: function(aEvent) {
  1042       switch (aEvent.id) {
  1043         case EVENT_P2P_PROV_DISC_PBC_RESP:
  1044           _sm.gotoState(stateConnecting); // No need for local user grant.
  1045           break;
  1046         case EVENT_P2P_PROV_DISC_SHOW_PIN:
  1047         case EVENT_P2P_PROV_DISC_ENTER_PIN:
  1048           if (aEvent.info.wpsMethod !== _savedConfig.wpsMethod) {
  1049             debug('Unmatched wps method: ' + aEvent.info.wpsMethod + ":" + _savedConfig.wpsMethod);
  1051           if (EVENT_P2P_PROV_DISC_SHOW_PIN === aEvent.id) {
  1052             _savedConfig.pin = aEvent.info.pin;
  1054           _sm.gotoState(stateWaitingForConfirmation);
  1055           break;
  1057         case EVENT_P2P_PROV_DISC_FAILURE:
  1058           _sm.gotoState(stateInactive);
  1059           break;
  1061         default:
  1062           return false;
  1063       } // End of switch.
  1064       return true;
  1066   });
  1068   // We are going to connect to the peer.
  1069   // |_savedConfig| is supposed to have been filled properly.
  1070   var stateConnecting = _sm.makeState("CONNECTING", {
  1071     timeoutTimer: null,
  1073     enter: function() {
  1074       let self = this;
  1076       if (null === _savedConfig.goIntent) {
  1077         _savedConfig.goIntent = DEFAULT_GO_INTENT;
  1080       _observer.onConnecting(_savedConfig);
  1082       let wpsMethodWithPin;
  1083       if (WPS_METHOD_KEYPAD === _savedConfig.wpsMethod ||
  1084           WPS_METHOD_DISPLAY === _savedConfig.wpsMethod) {
  1085         // e.g. '12345678 display or '12345678 keypad'.
  1086         wpsMethodWithPin = (_savedConfig.pin + ' ' + _savedConfig.wpsMethod);
  1087       } else {
  1088         // e.g. 'pbc'.
  1089         wpsMethodWithPin = _savedConfig.wpsMethod;
  1092       _sm.pause();
  1094       aP2pCommand.p2pGetGroupCapab(_savedConfig.address, function(gc) {
  1095         debug('group capabilities of ' + _savedConfig.address + ': ' + gc);
  1097         let isPeerGroupOwner = gc & GROUP_CAPAB_GROUP_OWNER;
  1098         let config = { address:           _savedConfig.address,
  1099                        wpsMethodWithPin:  wpsMethodWithPin,
  1100                        goIntent:          _savedConfig.goIntent,
  1101                        joinExistingGroup: isPeerGroupOwner };
  1103         aP2pCommand.p2pConnect(config, function (success) {
  1104           if (!success) {
  1105             debug('Failed to send p2p_connect');
  1106             _sm.gotoState(stateInactive);
  1107             return;
  1109           debug('Waiting for EVENT_P2P_GROUP_STARTED.');
  1110           self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
  1111           _sm.resume();
  1112         });
  1113       });
  1114     },
  1116     handleEvent: function(aEvent) {
  1117       switch (aEvent.id) {
  1118         case EVENT_P2P_GROUP_STARTED:
  1119           _sm.pause();
  1120           handleGroupStarted(aEvent.info, function (success) {
  1121             _sm.resume();
  1122           });
  1123           break;
  1125         case EVENT_P2P_GO_NEG_FAILURE:
  1126           debug('Negotiation failure. Go back to inactive state');
  1127           _sm.gotoState(stateInactive);
  1128           break;
  1130         case EVENT_TIMEOUT_CONNECTING:
  1131           debug('Connecting timeout! Go back to inactive state');
  1132           _sm.gotoState(stateInactive);
  1133           break;
  1135         case EVENT_P2P_GROUP_FORMATION_SUCCESS:
  1136         case EVENT_P2P_GO_NEG_SUCCESS:
  1137           break;
  1139         case EVENT_P2P_GROUP_FORMATION_FAILURE:
  1140           debug('Group formation failure');
  1141           _sm.gotoState(stateInactive);
  1142           break;
  1144         case EVENT_P2P_GROUP_REMOVED:
  1145           debug('Received P2P-GROUP-REMOVED due to previous failed ' +
  1146                 'handleGroupdStarted()');
  1147           _removedGroupInfo = {
  1148             role:   aEvent.info.role,
  1149             ifname: aEvent.info.ifname
  1150           };
  1151           _sm.gotoState(stateDisconnecting);
  1152           break;
  1154         default:
  1155           return false;
  1156       } // End of switch.
  1158       return true;
  1159     },
  1161     exit: function() {
  1162       this.timeoutTimer.cancel();
  1164   });
  1166   var stateConnected = _sm.makeState("CONNECTED", {
  1167     groupOwner: null,
  1169     enter: function() {
  1170       this.groupOwner = {
  1171         macAddress: _groupInfo.goAddress,
  1172         ipAddress:  _groupInfo.networkInterface.gateway,
  1173         passphrase: _groupInfo.passphrase,
  1174         ssid:       _groupInfo.ssid,
  1175         freq:       _groupInfo.freq,
  1176         isLocal:    _groupInfo.isGroupOwner
  1177       };
  1179       if (!_groupInfo.isGroupOwner) {
  1180         _observer.onConnected(this.groupOwner, _savedConfig);
  1181       } else {
  1182         // If I am a group owner, notify onConnected until EVENT_AP_STA_CONNECTED
  1183         // is received.
  1186       _removedGroupInfo = null;
  1187     },
  1189     handleEvent: function(aEvent) {
  1190       switch (aEvent.id) {
  1191         case EVENT_AP_STA_CONNECTED:
  1192           if (_groupInfo.isGroupOwner) {
  1193             _observer.onConnected(this.groupOwner, _savedConfig);
  1195           break;
  1197         case EVENT_P2P_GROUP_REMOVED:
  1198           _removedGroupInfo = {
  1199             role:   aEvent.info.role,
  1200             ifname: aEvent.info.ifname
  1201           };
  1202           _sm.gotoState(stateDisconnecting);
  1203           break;
  1205         case EVENT_AP_STA_DISCONNECTED:
  1206           debug('Client disconnected: ' + aEvent.info.address);
  1208           // Now we suppose it's the only client. Remove my group.
  1209           _sm.pause();
  1210           aP2pCommand.p2pGroupRemove(_groupInfo.ifname, function (success) {
  1211             debug('Requested to remove p2p group. Wait for EVENT_P2P_GROUP_REMOVED.');
  1212             _sm.resume();
  1213           });
  1214           break;
  1216         case EVENT_P2P_CMD_DISCONNECT:
  1217           // Since we only support single connection, we can ignore
  1218           // the given peer address.
  1219           _sm.pause();
  1220           aP2pCommand.p2pGroupRemove(_groupInfo.ifname, function(success) {
  1221             aEvent.info.onDoDisconnect(true);
  1222             _sm.resume();
  1223           });
  1225           debug('Sent disconnect command. Wait for EVENT_P2P_GROUP_REMOVED.');
  1226           break;
  1228         case EVENT_P2P_PROV_DISC_PBC_REQ:
  1229         case EVENT_P2P_PROV_DISC_SHOW_PIN:
  1230         case EVENT_P2P_PROV_DISC_ENTER_PIN:
  1231           debug('Someone is trying to connect to me: ' + JSON.stringify(aEvent.info));
  1233           _savedConfig = {
  1234             name:      aEvent.info.name,
  1235             address:   aEvent.info.address,
  1236             wpsMethod: aEvent.info.wpsMethod,
  1237             pin:       aEvent.info.pin
  1238           };
  1240           _sm.gotoState(stateWaitingForJoiningConfirmation);
  1241           break;
  1243         default:
  1244           return false;
  1245       } // end of switch
  1246       return true;
  1248   });
  1250   var stateWaitingForJoiningConfirmation = _sm.makeState("WAITING_FOR_JOINING_CONFIRMATION", {
  1251     timeoutTimer: null,
  1253     enter: function() {
  1254       gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
  1255       this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
  1256     },
  1258     handleEvent: function (aEvent) {
  1259       switch (aEvent.id) {
  1260         case EVENT_P2P_SET_PAIRING_CONFIRMATION:
  1261           if (!aEvent.info.accepted) {
  1262             debug('User rejected invitation!');
  1263             _sm.gotoState(stateConnected);
  1264             break;
  1267           let onWpsCommandSent = function(success) {
  1268             _observer.onConnecting(_savedConfig);
  1269             _sm.gotoState(stateConnected);
  1270           };
  1272           _sm.pause();
  1273           if (WPS_METHOD_PBC === _savedConfig.wpsMethod) {
  1274             aP2pCommand.wpsPbc(_groupInfo.ifname, onWpsCommandSent);
  1275           } else {
  1276             let detail = { pin: _savedConfig.pin, iface: _groupInfo.ifname };
  1277             aP2pCommand.wpsPin(detail, onWpsCommandSent);
  1279           break;
  1281         case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
  1282           debug('WAITING_FOR_JOINING_CONFIRMATION timeout!');
  1283           _sm.gotoState(stateConnected);
  1284           break;
  1286         default:
  1287           return false;
  1288       } // End of switch.
  1289       return true;
  1290     },
  1292     exit: function() {
  1293       this.timeoutTimer.cancel();
  1294       this.timeoutTimer = null;
  1296   });
  1298   var stateDisconnecting = _sm.makeState("DISCONNECTING", {
  1299     enter: function() {
  1300       _sm.pause();
  1301       handleGroupRemoved(_removedGroupInfo, function (success) {
  1302         if (!success) {
  1303           debug('Failed to handle group removed event. What can I do?');
  1305         _sm.gotoState(stateInactive);
  1306       });
  1307     },
  1309     handleEvent: function(aEvent) {
  1310       return false; // We will not receive any event in this state.
  1312   });
  1314   var stateDisabling = _sm.makeState("DISABLING", {
  1315     enter: function() {
  1316       _sm.pause();
  1317       aNetUtil.stopDhcpServer(function (success) { // Stopping DHCP server is harmless.
  1318         debug('Stop DHCP server result: ' + success);
  1319         aP2pCommand.p2pDisable(function(success) {
  1320           debug('P2P function disabled');
  1321           aP2pCommand.closeSupplicantConnection(function (status) {
  1322             debug('Supplicant connection closed');
  1323             aNetUtil.disableInterface(P2P_INTERFACE_NAME, function (success){
  1324               debug('Disabled interface: ' + P2P_INTERFACE_NAME);
  1325               _onDisabled(true);
  1326               _sm.gotoState(stateDisabled);
  1327             });
  1328           });
  1329         });
  1330       });
  1331     },
  1333     handleEvent: function(aEvent) {
  1334       return false; // We will not receive any event in this state.
  1336   });
  1338   //----------------------------------------------------------
  1339   // Helper functions.
  1340   //----------------------------------------------------------
  1342   // Handle 'P2P_GROUP_STARTED' event. Note that this function
  1343   // will also do the state transitioning and error handling.
  1344   //
  1345   // @param aInfo Information carried by "P2P_GROUP_STARTED" event:
  1346   //   .role: P2P_ROLE_GO or P2P_ROLE_CLIENT
  1347   //   .ssid:
  1348   //   .freq:
  1349   //   .passphrase: Used to connect to GO for legacy device.
  1350   //   .goAddress:
  1351   //   .ifname: e.g. p2p-p2p0
  1352   //
  1353   // @param aCallback Callback function.
  1354   function handleGroupStarted(aInfo, aCallback) {
  1355     debug('handleGroupStarted: ' + JSON.stringify(aInfo));
  1357     function onSuccess()
  1359       _sm.gotoState(stateConnected);
  1360       aCallback(true);
  1363     function onFailure()
  1365       debug('Failed to handleGroupdStarted(). Remove the group...');
  1366       aP2pCommand.p2pGroupRemove(aInfo.ifname, function (success) {
  1367         aCallback(false);
  1369         if (success) {
  1370           return; // Stay in current state and wait for EVENT_P2P_GROUP_REMOVED.
  1373         debug('p2pGroupRemove command error!');
  1374         _sm.gotoState(stateInactive);
  1375       });
  1378     // Save this group information.
  1379     _groupInfo = aInfo;
  1380     _groupInfo.isGroupOwner = (P2P_ROLE_GO === aInfo.role);
  1382     if (_groupInfo.isGroupOwner) {
  1383       debug('Group owner. Start DHCP server');
  1384       let dhcpServerConfig = { ifname: aInfo.ifname,
  1385                                startIp: GO_DHCP_SERVER_IP_RANGE.startIp,
  1386                                endIp: GO_DHCP_SERVER_IP_RANGE.endIp,
  1387                                serverIp: GO_NETWORK_INTERFACE.ip,
  1388                                maskLength: GO_NETWORK_INTERFACE.maskLength };
  1390       aNetUtil.startDhcpServer(dhcpServerConfig, function (success) {
  1391         if (!success) {
  1392           debug('Failed to start DHCP server');
  1393           onFailure();
  1394           return;
  1397         // Update p2p network interface.
  1398         _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
  1399         _p2pNetworkInterface.ips = [GO_NETWORK_INTERFACE.ip];
  1400         _p2pNetworkInterface.prefixLengths = [GO_NETWORK_INTERFACE.maskLength];
  1401         _p2pNetworkInterface.gateways = [GO_NETWORK_INTERFACE.ip];
  1402         handleP2pNetworkInterfaceStateChanged();
  1404         _groupInfo.networkInterface = _p2pNetworkInterface;
  1406         debug('Everything is done. Happy p2p GO~');
  1407         onSuccess();
  1408       });
  1410       return;
  1413     // We are the client.
  1415     debug("Client. Request IP from DHCP server on interface: " + _groupInfo.ifname);
  1417     aNetUtil.runDhcp(aInfo.ifname, function(dhcpData) {
  1418       if(!dhcpData || !dhcpData.info) {
  1419         debug('Failed to run DHCP client');
  1420         onFailure();
  1421         return;
  1424       // Save network interface.
  1425       debug("DHCP request success: " + JSON.stringify(dhcpData.info));
  1427       // Update p2p network interface.
  1428       let maskLength =
  1429         netHelpers.getMaskLength(netHelpers.stringToIP(dhcpData.info.mask_str));
  1430       if (!maskLength) {
  1431         maskLength = 32; // max prefix for IPv4.
  1433       _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
  1434       _p2pNetworkInterface.ips = [dhcpData.info.ipaddr_str];
  1435       _p2pNetworkInterface.prefixLengths = [maskLength];
  1436       if (typeof dhcpData.info.dns1_str == "string" &&
  1437           dhcpData.info.dns1_str.length) {
  1438         _p2pNetworkInterface.dnses.push(dhcpData.info.dns1_str);
  1440       if (typeof dhcpData.info.dns2_str == "string" &&
  1441           dhcpData.info.dns2_str.length) {
  1442         _p2pNetworkInterface.dnses.push(dhcpData.info.dns2_str);
  1444       _p2pNetworkInterface.gateways = [dhcpData.info.gateway_str];
  1445       handleP2pNetworkInterfaceStateChanged();
  1447       _groupInfo.networkInterface = _p2pNetworkInterface;
  1449       debug('Happy p2p client~');
  1450       onSuccess();
  1451     });
  1454   function resetP2pNetworkInterface() {
  1455     _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
  1456     _p2pNetworkInterface.ips = [];
  1457     _p2pNetworkInterface.prefixLengths = [];
  1458     _p2pNetworkInterface.dnses = [];
  1459     _p2pNetworkInterface.gateways = [];
  1462   function registerP2pNetworkInteface() {
  1463     if (!_p2pNetworkInterface.registered) {
  1464       resetP2pNetworkInterface();
  1465       gNetworkManager.registerNetworkInterface(_p2pNetworkInterface);
  1466       _p2pNetworkInterface.registered = true;
  1470   function unregisterP2pNetworkInteface() {
  1471     if (_p2pNetworkInterface.registered) {
  1472       resetP2pNetworkInterface();
  1473       gNetworkManager.unregisterNetworkInterface(_p2pNetworkInterface);
  1474       _p2pNetworkInterface.registered = false;
  1478   function handleP2pNetworkInterfaceStateChanged() {
  1479     Services.obs.notifyObservers(_p2pNetworkInterface,
  1480                                  kNetworkInterfaceStateChangedTopic,
  1481                                  null);
  1484   // Handle 'P2P_GROUP_STARTED' event.
  1485   //
  1486   // @param aInfo information carried by "P2P_GROUP_REMOVED" event:
  1487   //   .ifname
  1488   //   .role: "GO" or "client".
  1489   //
  1490   // @param aCallback Callback function.
  1491   function handleGroupRemoved(aInfo, aCallback) {
  1492     if (!_groupInfo) {
  1493       debug('No group info. Why?');
  1494       aCallback(true);
  1495       return;
  1497     if (_groupInfo.ifname !== aInfo.ifname ||
  1498         _groupInfo.role   !== aInfo.role) {
  1499       debug('Unmatched group info: ' + JSON.stringify(_groupInfo) +
  1500             ' v.s. ' + JSON.stringify(aInfo));
  1503     // Update p2p network interface.
  1504     _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
  1505     handleP2pNetworkInterfaceStateChanged();
  1507     if (P2P_ROLE_GO === aInfo.role) {
  1508       aNetUtil.stopDhcpServer(function(success) {
  1509         debug('Stop DHCP server result: ' + success);
  1510         aCallback(true);
  1511       });
  1512     } else {
  1513       aNetUtil.stopDhcp(aInfo.ifname, function() {
  1514         aCallback(true);
  1515       });
  1519   // Non state-specific event handler.
  1520   function handleEventCommon(aEvent) {
  1521     switch (aEvent.id) {
  1522       case EVENT_P2P_DEVICE_FOUND:
  1523         _observer.onPeerFound(aEvent.info);
  1524         break;
  1526       case EVENT_P2P_DEVICE_LOST:
  1527         _observer.onPeerLost(aEvent.info);
  1528         break;
  1530       case EVENT_P2P_CMD_DISABLE:
  1531         _onDisabled = aEvent.info.onDisabled;
  1532         _sm.gotoState(stateDisabling);
  1533         break;
  1535       case EVENT_P2P_CMD_ENABLE_SCAN:
  1536         if (_scanBlocked) {
  1537           _scanPostponded = true;
  1538           aEvent.info.callback(true);
  1539           break;
  1541         aP2pCommand.p2pEnableScan(P2P_SCAN_TIMEOUT_SEC, aEvent.info.callback);
  1542         break;
  1544       case EVENT_P2P_CMD_DISABLE_SCAN:
  1545         aP2pCommand.p2pDisableScan(aEvent.info.callback);
  1546         break;
  1548       case EVENT_P2P_FIND_STOPPED:
  1549         break;
  1551       case EVENT_P2P_CMD_BLOCK_SCAN:
  1552         _scanBlocked = true;
  1553         aP2pCommand.p2pDisableScan(function(success) {});
  1554         break;
  1556       case EVENT_P2P_CMD_UNBLOCK_SCAN:
  1557         _scanBlocked = false;
  1558         if (_scanPostponded) {
  1559           aP2pCommand.p2pEnableScan(P2P_SCAN_TIMEOUT_SEC, function(success) {});
  1561         break;
  1563       case EVENT_P2P_CMD_CONNECT:
  1564       case EVENT_P2P_CMD_DISCONNECT:
  1565         debug("The current state couldn't handle connect/disconnect request. Ignore it.");
  1566         break;
  1568       default:
  1569         return false;
  1570     } // End of switch.
  1571     return true;
  1574   function isInP2pManagedState(aState) {
  1575     let p2pManagedStates = [stateWaitingForConfirmation,
  1576                             stateWaitingForNegReq,
  1577                             stateProvisionDiscovery,
  1578                             stateWaitingForInvitationConfirmation,
  1579                             stateGroupAdding,
  1580                             stateReinvoking,
  1581                             stateConnecting,
  1582                             stateConnected,
  1583                             stateDisconnecting];
  1585     for (let i = 0; i < p2pManagedStates.length; i++) {
  1586       if (aState === p2pManagedStates[i]) {
  1587         return true;
  1591     return false;
  1594   function initTimeoutTimer(aTimeoutMs, aTimeoutEvent) {
  1595     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1596     function onTimerFired() {
  1597       _sm.sendEvent({ id: aTimeoutEvent });
  1598       timer = null;
  1600     timer.initWithCallback(onTimerFired.bind(this), aTimeoutMs,
  1601                            Ci.nsITimer.TYPE_ONE_SHOT);
  1602     return timer;
  1605   // Converts local WPS method to peer WPS method.
  1606   function toPeerWpsMethod(aLocalWpsMethod) {
  1607     switch (aLocalWpsMethod) {
  1608       case WPS_METHOD_DISPLAY:
  1609         return WPS_METHOD_KEYPAD;
  1610       case WPS_METHOD_KEYPAD:
  1611         return WPS_METHOD_DISPLAY;
  1612       case WPS_METHOD_PBC:
  1613         return WPS_METHOD_PBC;
  1614       default:
  1615         return WPS_METHOD_PBC; // Use "push button" as the default method.
  1619   return p2pSm;
  1622 this.WifiP2pManager.INTERFACE_NAME = P2P_INTERFACE_NAME;

mercurial