diff --git a/dom/events/test/test_all_synthetic_events.html b/dom/events/test/test_all_synthetic_events.html index 76a009139c5f..6fc4cfb08143 100644 --- a/dom/events/test/test_all_synthetic_events.html +++ b/dom/events/test/test_all_synthetic_events.html @@ -285,6 +285,10 @@ const kEventConstructors = { return new MozWifiStatusChangeEvent(aName, aProps); }, }, + MozWifiStationInfoEvent: { create: function (aName, aProps) { + return new MozWifiStationInfoEvent(aName, aProps); + }, + }, MutationEvent: { create: function (aName, aProps) { var e = document.createEvent("mutationevent"); e.initMutationEvent(aName, aProps.bubbles, aProps.cancelable, diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 93f6cf6c8e77..e5fd0810c97e 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -713,7 +713,9 @@ var interfaceNamesInGlobalScope = // IMPORTANT: Do not change this list without review from a DOM peer! {name: "MozWifiConnectionInfoEvent", b2g: true}, // IMPORTANT: Do not change this list without review from a DOM peer! - {name: "MozWifiManager", b2g: true, permission: "wifi-manage"}, + {name: "MozWifiStationInfoEvent", b2g: true}, +// IMPORTANT: Do not change this list without review from a DOM peer! + {name: "MozWifiManager", b2g: true, permission: "wifi-manage"}, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "MozWifiNetwork", b2g: true, permission: "wifi-manage"}, // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/MozWifiManager.webidl b/dom/webidl/MozWifiManager.webidl index 431a20cc2cea..fa6eee2773ea 100644 --- a/dom/webidl/MozWifiManager.webidl +++ b/dom/webidl/MozWifiManager.webidl @@ -321,4 +321,10 @@ interface MozWifiManager : EventTarget { */ attribute EventHandler onenabled; attribute EventHandler ondisabled; + + /** + * An event listener that is called with information about the number + * of wifi stations connected to wifi hotspot every 5 seconds. + */ + attribute EventHandler onstationInfoUpdate; }; diff --git a/dom/webidl/MozWifiStationInfoEvent.webidl b/dom/webidl/MozWifiStationInfoEvent.webidl new file mode 100644 index 000000000000..8ee0674f9ca6 --- /dev/null +++ b/dom/webidl/MozWifiStationInfoEvent.webidl @@ -0,0 +1,19 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + */ + +[Constructor(DOMString type, optional MozWifiStationInfoEventInit eventInitDict), HeaderFile="GeneratedEventClasses.h"] +interface MozWifiStationInfoEvent : Event +{ + /** + * The number of wifi stations connected to wifi hotspot. + */ + readonly attribute short station; +}; + +dictionary MozWifiStationInfoEventInit : EventInit +{ + short station = 0; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index ddf47e075e80..96acf1edd011 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -595,6 +595,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': 'MozWifiManager.webidl', 'MozWifiP2pManager.webidl', 'MozWifiP2pStatusChangeEvent.webidl', + 'MozWifiStationInfoEvent.webidl', 'MozWifiStatusChangeEvent.webidl', ] else: diff --git a/dom/wifi/DOMWifiManager.js b/dom/wifi/DOMWifiManager.js index c4edd561a45b..82e7374651e2 100644 --- a/dom/wifi/DOMWifiManager.js +++ b/dom/wifi/DOMWifiManager.js @@ -80,6 +80,7 @@ function DOMWifiManager() { this.defineEventHandlerGetterSetter("onconnectionInfoUpdate"); this.defineEventHandlerGetterSetter("onenabled"); this.defineEventHandlerGetterSetter("ondisabled"); + this.defineEventHandlerGetterSetter("onstationInfoUpdate"); } DOMWifiManager.prototype = { @@ -99,6 +100,7 @@ DOMWifiManager.prototype = { this._enabled = false; this._lastConnectionInfo = null; this._capabilities = null; + this._stationNumber = 0; const messages = ["WifiManager:getNetworks:Return:OK", "WifiManager:getNetworks:Return:NO", "WifiManager:getKnownNetworks:Return:OK", "WifiManager:getKnownNetworks:Return:NO", @@ -116,8 +118,8 @@ DOMWifiManager.prototype = { "WifiManager:onconnect", "WifiManager:ondisconnect", "WifiManager:onwpstimeout", "WifiManager:onwpsfail", "WifiManager:onwpsoverlap", "WifiManager:connectionInfoUpdate", - "WifiManager:onauthenticating", - "WifiManager:onconnectingfailed"]; + "WifiManager:onauthenticating", "WifiManager:onconnectingfailed", + "WifiManager:stationInfoUpdate"]; this.initDOMRequestHelper(aWindow, messages); this._mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender); @@ -392,6 +394,10 @@ DOMWifiManager.prototype = { this._connectionStatus = "authenticating"; this._fireStatusChangeEvent(); break; + case "WifiManager:stationInfoUpdate": + this._stationNumber = msg.station; + this._fireStationInfoUpdate(msg); + break; } }, @@ -419,6 +425,13 @@ DOMWifiManager.prototype = { this.__DOM_IMPL__.dispatchEvent(evt); }, + _fireStationInfoUpdate: function onStationInfoUpdate(info) { + var evt = new this._window.MozWifiStationInfoEvent("stationInfoUpdate", + { station: this._stationNumber} + ); + this.__DOM_IMPL__.dispatchEvent(evt); + }, + getNetworks: function getNetworks() { var request = this.createRequest(); this._sendMessageForRequest("WifiManager:getNetworks", null, request); diff --git a/dom/wifi/WifiCommand.jsm b/dom/wifi/WifiCommand.jsm index 4538592326da..44fbc3e0efe8 100644 --- a/dom/wifi/WifiCommand.jsm +++ b/dom/wifi/WifiCommand.jsm @@ -276,6 +276,33 @@ this.WifiCommand = function(aControlMessage, aInterface, aSdkVersion) { }); }; + command.connectToHostapd = function(callback) { + voidControlMessage("connect_to_hostapd", callback); + }; + + command.closeHostapdConnection = function(callback) { + voidControlMessage("close_hostapd_connection", callback); + }; + + command.hostapdCommand = function (callback, request) { + var msg = { cmd: "hostapd_command", + request: request, + iface: aInterface }; + + aControlMessage(msg, function(data) { + callback(data.status ? null : data.reply); + }); + }; + + command.hostapdGetStations = function (callback) { + var msg = { cmd: "hostapd_get_stations", + iface: aInterface }; + + aControlMessage(msg, function(data) { + callback(data.status); + }); + }; + command.setPowerModeICS = function (mode, callback) { doBooleanCommand("DRIVER POWERMODE " + (mode === "AUTO" ? 0 : 1), "OK", callback); }; diff --git a/dom/wifi/WifiHotspotUtils.cpp b/dom/wifi/WifiHotspotUtils.cpp new file mode 100644 index 000000000000..b9b324309c5d --- /dev/null +++ b/dom/wifi/WifiHotspotUtils.cpp @@ -0,0 +1,176 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WifiHotspotUtils.h" +#include +#include +#include +#include +#include +#include + +#include "prinit.h" +#include "mozilla/Assertions.h" +#include "nsDebug.h" +#include "nsPrintfCString.h" + +static void* sWifiHotspotUtilsLib; +static PRCallOnceType sInitWifiHotspotUtilsLib; +// Socket pair used to exit from a blocking read. +static struct wpa_ctrl* ctrl_conn; +static const char *ctrl_iface_dir = "/data/misc/wifi/hostapd"; +static char *ctrl_ifname = nullptr; + +DEFINE_DLFUNC(wpa_ctrl_open, struct wpa_ctrl*, const char*) +DEFINE_DLFUNC(wpa_ctrl_close, void, struct wpa_ctrl*) +DEFINE_DLFUNC(wpa_ctrl_attach, int32_t, struct wpa_ctrl*) +DEFINE_DLFUNC(wpa_ctrl_detach, int32_t, struct wpa_ctrl*) +DEFINE_DLFUNC(wpa_ctrl_request, int32_t, struct wpa_ctrl*, + const char*, size_t cmd_len, char *reply, + size_t *reply_len, void (*msg_cb)(char *msg, size_t len)) + + +static PRStatus +InitWifiHotspotUtilsLib() +{ + sWifiHotspotUtilsLib = dlopen("/system/lib/libwpa_client.so", RTLD_LAZY); + // We might fail to open the hardware lib. That's OK. + return PR_SUCCESS; +} + +static void* +GetWifiHotspotLibHandle() +{ + PR_CallOnce(&sInitWifiHotspotUtilsLib, InitWifiHotspotUtilsLib); + return sWifiHotspotUtilsLib; +} + +struct wpa_ctrl * +WifiHotspotUtils::openConnection(const char *ifname) +{ + if (!ifname) { + return nullptr; + } + + USE_DLFUNC(wpa_ctrl_open) + ctrl_conn = wpa_ctrl_open(nsPrintfCString("%s/%s", ctrl_iface_dir, ifname).get()); + return ctrl_conn; +} + +int32_t +WifiHotspotUtils::sendCommand(struct wpa_ctrl *ctrl, const char *cmd, + char *reply, size_t *reply_len) +{ + int32_t ret; + + if (!ctrl_conn) { + NS_WARNING(nsPrintfCString("Not connected to hostapd - \"%s\" command dropped.\n", cmd).get()); + return -1; + } + + USE_DLFUNC(wpa_ctrl_request) + ret = wpa_ctrl_request(ctrl, cmd, strlen(cmd), reply, reply_len, nullptr); + if (ret == -2) { + NS_WARNING(nsPrintfCString("'%s' command timed out.\n", cmd).get()); + return -2; + } else if (ret < 0 || strncmp(reply, "FAIL", 4) == 0) { + return -1; + } + if (strncmp(cmd, "PING", 4) == 0) { + reply[*reply_len] = '\0'; + } + return 0; +} + + +// static +void* +WifiHotspotUtils::GetSharedLibrary() +{ + void* wpaClientLib = GetWifiHotspotLibHandle(); + if (!wpaClientLib) { + NS_WARNING("No /system/lib/libwpa_client.so"); + } + return wpaClientLib; +} + +int32_t WifiHotspotUtils::do_wifi_connect_to_hostapd() +{ + struct dirent *dent; + + DIR *dir = opendir(ctrl_iface_dir); + if (dir) { + while ((dent = readdir(dir))) { + if (strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0) { + continue; + } + ctrl_ifname = strdup(dent->d_name); + break; + } + closedir(dir); + } + + ctrl_conn = openConnection(ctrl_ifname); + if (!ctrl_conn) { + NS_WARNING(nsPrintfCString("Unable to open connection to hostapd on \"%s\": %s", + ctrl_ifname, strerror(errno)).get()); + return -1; + } + + USE_DLFUNC(wpa_ctrl_attach) + if (wpa_ctrl_attach(ctrl_conn) != 0) { + USE_DLFUNC(wpa_ctrl_close) + wpa_ctrl_close(ctrl_conn); + ctrl_conn = nullptr; + return -1; + } + + return 0; +} + +int32_t WifiHotspotUtils::do_wifi_close_hostapd_connection() +{ + if (!ctrl_conn) { + NS_WARNING("Invalid ctrl_conn."); + return -1; + } + + USE_DLFUNC(wpa_ctrl_detach) + if (wpa_ctrl_detach(ctrl_conn) < 0) { + NS_WARNING("Failed to detach wpa_ctrl."); + } + + USE_DLFUNC(wpa_ctrl_close) + wpa_ctrl_close(ctrl_conn); + ctrl_conn = nullptr; + return 0; +} + +int32_t WifiHotspotUtils::do_wifi_hostapd_command(const char *command, + char *reply, + size_t *reply_len) +{ + return sendCommand(ctrl_conn, command, reply, reply_len); +} + +int32_t WifiHotspotUtils::do_wifi_hostapd_get_stations() +{ + char addr[32], cmd[64]; + int stations = 0; + size_t addrLen = sizeof(addr); + + if (sendCommand(ctrl_conn, "STA-FIRST", addr, &addrLen)) { + return 0; + } + stations++; + + sprintf(cmd, "STA-NEXT %s", addr); + while (sendCommand(ctrl_conn, cmd, addr, &addrLen) == 0) { + stations++; + sprintf(cmd, "STA-NEXT %s", addr); + } + + return stations; +} diff --git a/dom/wifi/WifiHotspotUtils.h b/dom/wifi/WifiHotspotUtils.h new file mode 100644 index 000000000000..320f93b925c4 --- /dev/null +++ b/dom/wifi/WifiHotspotUtils.h @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Abstraction on top of the network support from libnetutils that we + * use to set up network connections. + */ + +#ifndef WifiHotspotUtils_h +#define WifiHotspotUtils_h + +// Forward declaration. +struct wpa_ctrl; + +class WifiHotspotUtils +{ +public: + static void* GetSharedLibrary(); + + int32_t do_wifi_connect_to_hostapd(); + int32_t do_wifi_close_hostapd_connection(); + int32_t do_wifi_hostapd_command(const char *command, + char *reply, + size_t *reply_len); + int32_t do_wifi_hostapd_get_stations(); + +private: + struct wpa_ctrl * openConnection(const char *ifname); + int32_t sendCommand(struct wpa_ctrl *ctrl, const char *cmd, + char *reply, size_t *reply_len); +}; + +// Defines a function type with the right arguments and return type. +#define DEFINE_DLFUNC(name, ret, args...) typedef ret (*FUNC##name)(args); + +// Set up a dlsymed function ready to use. +#define USE_DLFUNC(name) \ + FUNC##name name = (FUNC##name) dlsym(GetSharedLibrary(), #name); \ + if (!name) { \ + MOZ_ASSUME_UNREACHABLE("Symbol not found in shared library : " #name); \ + } + +#endif // WifiHotspotUtils_h diff --git a/dom/wifi/WifiUtils.cpp b/dom/wifi/WifiUtils.cpp index 84a118706e4e..77cb143c1533 100644 --- a/dom/wifi/WifiUtils.cpp +++ b/dom/wifi/WifiUtils.cpp @@ -381,6 +381,7 @@ WpaSupplicant::WpaSupplicant() mImpl = new KKWpaSupplicantImpl(); } mNetUtils = new NetUtils(); + mWifiHotspotUtils = new WifiHotspotUtils(); }; void WpaSupplicant::WaitForEvent(nsAString& aEvent, const nsCString& aInterface) @@ -414,6 +415,10 @@ bool WpaSupplicant::ExecuteCommand(CommandOptions aOptions, return false; } + if (!mWifiHotspotUtils->GetSharedLibrary()) { + return false; + } + // Always correlate the opaque ids. aResult.mId = aOptions.mId; @@ -518,6 +523,51 @@ bool WpaSupplicant::ExecuteCommand(CommandOptions aOptions, if (inet_ntop(AF_INET, &aResult.mMask, inet_str, sizeof(inet_str))) { aResult.mMask_str = NS_ConvertUTF8toUTF16(inet_str); } + } else if (aOptions.mCmd.EqualsLiteral("hostapd_command")) { + size_t len = BUFFER_SIZE - 1; + char buffer[BUFFER_SIZE]; + NS_ConvertUTF16toUTF8 request(aOptions.mRequest); + aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_command(request.get(), + buffer, + &len); + nsString value; + if (aResult.mStatus == 0) { + if (buffer[len - 1] == '\n') { // remove trailing new lines. + len--; + } + buffer[len] = '\0'; + CheckBuffer(buffer, len, value); + } + aResult.mReply = value; + } else if (aOptions.mCmd.EqualsLiteral("hostapd_get_stations")) { + aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_get_stations(); + } else if (aOptions.mCmd.EqualsLiteral("connect_to_hostapd")) { + aResult.mStatus = mWifiHotspotUtils->do_wifi_connect_to_hostapd(); + } else if (aOptions.mCmd.EqualsLiteral("close_hostapd_connection")) { + aResult.mStatus = mWifiHotspotUtils->do_wifi_close_hostapd_connection(); + } else if (aOptions.mCmd.EqualsLiteral("hostapd_command")) { + size_t len = BUFFER_SIZE - 1; + char buffer[BUFFER_SIZE]; + NS_ConvertUTF16toUTF8 request(aOptions.mRequest); + aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_command(request.get(), + buffer, + &len); + nsString value; + if (aResult.mStatus == 0) { + if (buffer[len - 1] == '\n') { // remove trailing new lines. + len--; + } + buffer[len] = '\0'; + CheckBuffer(buffer, len, value); + } + aResult.mReply = value; + } else if (aOptions.mCmd.EqualsLiteral("hostapd_get_stations")) { + aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_get_stations(); + } else if (aOptions.mCmd.EqualsLiteral("connect_to_hostapd")) { + aResult.mStatus = mWifiHotspotUtils->do_wifi_connect_to_hostapd(); + } else if (aOptions.mCmd.EqualsLiteral("close_hostapd_connection")) { + aResult.mStatus = mWifiHotspotUtils->do_wifi_close_hostapd_connection(); + } else { NS_WARNING("WpaSupplicant::ExecuteCommand : Unknown command"); printf_stderr("WpaSupplicant::ExecuteCommand : Unknown command: %s", diff --git a/dom/wifi/WifiUtils.h b/dom/wifi/WifiUtils.h index 54c96cbe9b74..41e20f1b0a12 100644 --- a/dom/wifi/WifiUtils.h +++ b/dom/wifi/WifiUtils.h @@ -14,6 +14,7 @@ #include "nsAutoPtr.h" #include "mozilla/dom/WifiOptionsBinding.h" #include "mozilla/dom/network/NetUtils.h" +#include "WifiHotspotUtils.h" #include "nsCxPusher.h" // Needed to add a copy constructor to WifiCommandOptions. @@ -131,6 +132,7 @@ public: private: nsAutoPtr mImpl; nsAutoPtr mNetUtils; + nsAutoPtr mWifiHotspotUtils; protected: void CheckBuffer(char* buffer, int32_t length, nsAString& aEvent); diff --git a/dom/wifi/WifiWorker.js b/dom/wifi/WifiWorker.js index 6a2d95105e7e..9ff7cb8075a4 100644 --- a/dom/wifi/WifiWorker.js +++ b/dom/wifi/WifiWorker.js @@ -1059,6 +1059,19 @@ var WifiManager = (function() { } } + var wifiHotspotStatusTimer = null; + function cancelWifiHotspotStatusTimer() { + if (wifiHotspotStatusTimer) { + wifiHotspotStatusTimer.cancel(); + wifiHotspotStatusTimer = null; + } + } + + function createWifiHotspotStatusTimer(onTimeout) { + wifiHotspotStatusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + wifiHotspotStatusTimer.init(onTimeout, 5000, Ci.nsITimer.TYPE_REPEATING_SLACK); + } + // Get wifi interface and load wifi driver when enable Ap mode. manager.setWifiApEnabled = function(enabled, configuration, callback) { if (enabled === manager.isWifiTetheringEnabled(manager.tetheringState)) { @@ -1072,9 +1085,20 @@ var WifiManager = (function() { if (status < 0) { callback(); manager.tetheringState = "UNINITIALIZED"; + if (wifiHotspotStatusTimer) { + cancelWifiHotspotStatusTimer(); + wifiCommand.closeHostapdConnection(function(result) { + }); + } return; } + function getWifiHotspotStatus() { + wifiCommand.hostapdGetStations(function(result) { + notify("stationInfoUpdate", {station: result}); + }); + } + function doStartWifiTethering() { cancelWaitForDriverReadyTimer(); WifiNetworkInterface.name = manager.ifname; @@ -1084,6 +1108,13 @@ var WifiManager = (function() { manager.tetheringState = "UNINITIALIZED"; } else { manager.tetheringState = "COMPLETED"; + wifiCommand.connectToHostapd(function(result) { + if (result) { + return; + } + // Create a timer to track the connection status. + createWifiHotspotStatusTimer(getWifiHotspotStatus); + }); } // Pop out current request. callback(); @@ -2282,6 +2313,10 @@ function WifiWorker() { }); }; + WifiManager.onstationInfoUpdate = function() { + self._fireEvent("stationInfoUpdate", { station: this.station }); + }; + // Read the 'wifi.enabled' setting in order to start with a known // value at boot time. The handle() will be called after reading. // diff --git a/dom/wifi/moz.build b/dom/wifi/moz.build index 00551a9636c1..76934d323358 100644 --- a/dom/wifi/moz.build +++ b/dom/wifi/moz.build @@ -7,6 +7,7 @@ XPIDL_SOURCES += [ 'nsIDOMMozWifiConnectionInfoEvent.idl', 'nsIDOMMozWifiP2pStatusChangeEvent.idl', + 'nsIDOMMozWifiStationInfoEvent.idl', 'nsIDOMMozWifiStatusChangeEvent.idl', 'nsIWifi.idl', 'nsIWifiCertService.idl', @@ -35,6 +36,7 @@ EXTRA_JS_MODULES += [ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': SOURCES = [ 'WifiCertService.cpp', + 'WifiHotspotUtils.cpp', 'WifiProxyService.cpp', 'WifiUtils.cpp', ] diff --git a/dom/wifi/nsIDOMMozWifiStationInfoEvent.idl b/dom/wifi/nsIDOMMozWifiStationInfoEvent.idl new file mode 100644 index 000000000000..bb5686150998 --- /dev/null +++ b/dom/wifi/nsIDOMMozWifiStationInfoEvent.idl @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIDOMEvent.idl" + +[scriptable, builtinclass, uuid(97dc8040-d5c9-11e3-9c1a-0800200c9a66)] +interface nsIDOMMozWifiStationInfoEvent : nsIDOMEvent +{ + /** + * The number of wifi stations connected to wifi hotspot. + */ + readonly attribute short station; + + [noscript] void initMozWifiStationInfoEvent(in DOMString aType, + in boolean aCanBubble, + in boolean aCancelable, + in short station); +}; diff --git a/js/xpconnect/src/event_impl_gen.conf.in b/js/xpconnect/src/event_impl_gen.conf.in index 7c45dc424cc2..e2c6d47bc22a 100644 --- a/js/xpconnect/src/event_impl_gen.conf.in +++ b/js/xpconnect/src/event_impl_gen.conf.in @@ -26,6 +26,7 @@ simple_events = [ 'MozWifiP2pStatusChangeEvent', 'MozWifiStatusChangeEvent', 'MozWifiConnectionInfoEvent', + 'MozWifiStationInfoEvent', #endif #ifdef MOZ_B2G_RIL 'MozCellBroadcastEvent',