summaryrefslogtreecommitdiffstats
path: root/netwerk/wifi
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /netwerk/wifi
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--netwerk/wifi/WifiScanner.h33
-rw-r--r--netwerk/wifi/dbus/DbusWifiScanner.cpp355
-rw-r--r--netwerk/wifi/dbus/DbusWifiScanner.h60
-rw-r--r--netwerk/wifi/freebsd/FreeBsdWifiScanner.cpp148
-rw-r--r--netwerk/wifi/freebsd/FreeBsdWifiScanner.h31
-rw-r--r--netwerk/wifi/gtest/TestWifiMonitor.cpp368
-rw-r--r--netwerk/wifi/gtest/moz.build13
-rw-r--r--netwerk/wifi/mac/MacWifiScanner.h28
-rw-r--r--netwerk/wifi/mac/MacWifiScanner.mm106
-rw-r--r--netwerk/wifi/mac/Wifi.h119
-rw-r--r--netwerk/wifi/moz.build63
-rw-r--r--netwerk/wifi/nsIWifiAccessPoint.idl41
-rw-r--r--netwerk/wifi/nsIWifiListener.idl29
-rw-r--r--netwerk/wifi/nsIWifiMonitor.idl27
-rw-r--r--netwerk/wifi/nsWifiAccessPoint.cpp67
-rw-r--r--netwerk/wifi/nsWifiAccessPoint.h78
-rw-r--r--netwerk/wifi/nsWifiMonitor.cpp400
-rw-r--r--netwerk/wifi/nsWifiMonitor.h123
-rw-r--r--netwerk/wifi/solaris/SolarisWifiScanner.cpp121
-rw-r--r--netwerk/wifi/solaris/SolarisWifiScanner.h31
-rw-r--r--netwerk/wifi/win/WinWifiScanner.cpp170
-rw-r--r--netwerk/wifi/win/WinWifiScanner.h36
-rw-r--r--netwerk/wifi/win/WlanLibrary.cpp111
-rw-r--r--netwerk/wifi/win/WlanLibrary.h54
24 files changed, 2612 insertions, 0 deletions
diff --git a/netwerk/wifi/WifiScanner.h b/netwerk/wifi/WifiScanner.h
new file mode 100644
index 0000000000..3b2c6eec1f
--- /dev/null
+++ b/netwerk/wifi/WifiScanner.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#pragma once
+
+#include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+
+class nsIWifiAccessPoint;
+
+namespace mozilla {
+
+class WifiScanner {
+ public:
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ virtual nsresult GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints) = 0;
+
+ virtual ~WifiScanner() = default;
+};
+
+} // namespace mozilla
diff --git a/netwerk/wifi/dbus/DbusWifiScanner.cpp b/netwerk/wifi/dbus/DbusWifiScanner.cpp
new file mode 100644
index 0000000000..f382bd60e0
--- /dev/null
+++ b/netwerk/wifi/dbus/DbusWifiScanner.cpp
@@ -0,0 +1,355 @@
+/* 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 "DbusWifiScanner.h"
+#include "mozilla/DBusHelpers.h"
+#include "nsWifiAccessPoint.h"
+
+namespace mozilla {
+
+WifiScannerImpl::WifiScannerImpl() {
+ mConnection =
+ already_AddRefed<DBusConnection>(dbus_bus_get(DBUS_BUS_SYSTEM, nullptr));
+
+ if (mConnection) {
+ dbus_connection_setup_with_g_main(mConnection, nullptr);
+ dbus_connection_set_exit_on_disconnect(mConnection, false);
+ }
+
+ MOZ_COUNT_CTOR(WifiScannerImpl);
+}
+
+WifiScannerImpl::~WifiScannerImpl() { MOZ_COUNT_DTOR(WifiScannerImpl); }
+
+nsresult WifiScannerImpl::GetAccessPointsFromWLAN(
+ AccessPointArray& accessPoints) {
+ if (!mConnection) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return SendGetDevices(accessPoints);
+}
+
+// http://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
+// Refer to function dbus_connection_send_with_reply_and_block.
+static const uint32_t DBUS_DEFAULT_TIMEOUT = -1;
+
+nsresult WifiScannerImpl::SendGetDevices(AccessPointArray& accessPoints) {
+ RefPtr<DBusMessage> msg =
+ already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager",
+ "org.freedesktop.NetworkManager", "GetDevices"));
+ if (!msg) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ RefPtr<DBusMessage> reply =
+ already_AddRefed<DBusMessage>(dbus_connection_send_with_reply_and_block(
+ mConnection, msg, DBUS_DEFAULT_TIMEOUT, &err));
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ return NS_ERROR_FAILURE;
+ }
+
+ return IdentifyDevices(reply, accessPoints);
+}
+
+nsresult WifiScannerImpl::SendGetDeviceType(const char* aPath,
+ AccessPointArray& accessPoints) {
+ RefPtr<DBusMessage> msg = already_AddRefed<DBusMessage>(
+ dbus_message_new_method_call("org.freedesktop.NetworkManager", aPath,
+ "org.freedesktop.DBus.Properties", "Get"));
+ if (!msg) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusMessageIter argsIter;
+ dbus_message_iter_init_append(msg, &argsIter);
+
+ const char* paramInterface = "org.freedesktop.NetworkManager.Device";
+ if (!dbus_message_iter_append_basic(&argsIter, DBUS_TYPE_STRING,
+ &paramInterface)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const char* paramDeviceType = "DeviceType";
+ if (!dbus_message_iter_append_basic(&argsIter, DBUS_TYPE_STRING,
+ &paramDeviceType)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ RefPtr<DBusMessage> reply =
+ already_AddRefed<DBusMessage>(dbus_connection_send_with_reply_and_block(
+ mConnection, msg, DBUS_DEFAULT_TIMEOUT, &err));
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ return NS_ERROR_FAILURE;
+ }
+
+ return IdentifyDeviceType(reply, aPath, accessPoints);
+}
+
+nsresult WifiScannerImpl::SendGetAccessPoints(const char* aPath,
+ AccessPointArray& accessPoints) {
+ RefPtr<DBusMessage> msg =
+ already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ "org.freedesktop.NetworkManager", aPath,
+ "org.freedesktop.NetworkManager.Device.Wireless", "GetAccessPoints"));
+ if (!msg) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ RefPtr<DBusMessage> reply =
+ already_AddRefed<DBusMessage>(dbus_connection_send_with_reply_and_block(
+ mConnection, msg, DBUS_DEFAULT_TIMEOUT, &err));
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ // In the GetAccessPoints case, if there are no access points, error is set.
+ // We don't want to error out here.
+ return NS_OK;
+ }
+
+ return IdentifyAccessPoints(reply, accessPoints);
+}
+
+nsresult WifiScannerImpl::SendGetAPProperties(const char* aPath,
+ AccessPointArray& accessPoints) {
+ RefPtr<DBusMessage> msg =
+ already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ "org.freedesktop.NetworkManager", aPath,
+ "org.freedesktop.DBus.Properties", "GetAll"));
+ if (!msg) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusMessageIter argsIter;
+ dbus_message_iter_init_append(msg, &argsIter);
+
+ const char* param = "org.freedesktop.NetworkManager.AccessPoint";
+ if (!dbus_message_iter_append_basic(&argsIter, DBUS_TYPE_STRING, &param)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ RefPtr<DBusMessage> reply =
+ already_AddRefed<DBusMessage>(dbus_connection_send_with_reply_and_block(
+ mConnection, msg, DBUS_DEFAULT_TIMEOUT, &err));
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ return NS_ERROR_FAILURE;
+ }
+
+ return IdentifyAPProperties(reply, accessPoints);
+}
+
+nsresult WifiScannerImpl::IdentifyDevices(DBusMessage* aMsg,
+ AccessPointArray& accessPoints) {
+ DBusMessageIter iter;
+ nsresult rv = GetDBusIterator(aMsg, &iter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char* devicePath;
+ do {
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) {
+ return NS_ERROR_FAILURE;
+ }
+
+ dbus_message_iter_get_basic(&iter, &devicePath);
+ if (!devicePath) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = SendGetDeviceType(devicePath, accessPoints);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (dbus_message_iter_next(&iter));
+
+ return NS_OK;
+}
+
+nsresult WifiScannerImpl::IdentifyDeviceType(DBusMessage* aMsg,
+ const char* aDevicePath,
+ AccessPointArray& accessPoints) {
+ DBusMessageIter args;
+ if (!dbus_message_iter_init(aMsg, &args)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_VARIANT) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusMessageIter variantIter;
+ dbus_message_iter_recurse(&args, &variantIter);
+ if (dbus_message_iter_get_arg_type(&variantIter) != DBUS_TYPE_UINT32) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t deviceType;
+ dbus_message_iter_get_basic(&variantIter, &deviceType);
+
+ // http://projects.gnome.org/NetworkManager/developers/api/07/spec-07.html
+ // Refer to NM_DEVICE_TYPE_WIFI under NM_DEVICE_TYPE.
+ const uint32_t NM_DEVICE_TYPE_WIFI = 2;
+ nsresult rv = NS_OK;
+ if (deviceType == NM_DEVICE_TYPE_WIFI) {
+ rv = SendGetAccessPoints(aDevicePath, accessPoints);
+ }
+
+ return rv;
+}
+
+nsresult WifiScannerImpl::IdentifyAccessPoints(DBusMessage* aMsg,
+ AccessPointArray& accessPoints) {
+ DBusMessageIter iter;
+ nsresult rv = GetDBusIterator(aMsg, &iter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char* path;
+ do {
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) {
+ return NS_ERROR_FAILURE;
+ }
+ dbus_message_iter_get_basic(&iter, &path);
+ if (!path) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = SendGetAPProperties(path, accessPoints);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (dbus_message_iter_next(&iter));
+
+ return NS_OK;
+}
+
+nsresult WifiScannerImpl::IdentifyAPProperties(DBusMessage* aMsg,
+ AccessPointArray& accessPoints) {
+ DBusMessageIter arr;
+ nsresult rv = GetDBusIterator(aMsg, &arr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsWifiAccessPoint> ap = new nsWifiAccessPoint();
+ do {
+ DBusMessageIter dict;
+ dbus_message_iter_recurse(&arr, &dict);
+
+ do {
+ const char* key;
+ dbus_message_iter_get_basic(&dict, &key);
+ if (!key) {
+ return NS_ERROR_FAILURE;
+ }
+ dbus_message_iter_next(&dict);
+
+ DBusMessageIter variant;
+ dbus_message_iter_recurse(&dict, &variant);
+
+ if (!strncmp(key, "Ssid", strlen("Ssid"))) {
+ nsresult rv = StoreSsid(&variant, ap);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+
+ if (!strncmp(key, "HwAddress", strlen("HwAddress"))) {
+ nsresult rv = SetMac(&variant, ap);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+
+ if (!strncmp(key, "Strength", strlen("Strength"))) {
+ if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_BYTE) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint8_t strength; // in %
+ dbus_message_iter_get_basic(&variant, &strength);
+ int signal_strength = (strength / 2) - 100; // strength to dB
+ ap->setSignal(signal_strength);
+ }
+ } while (dbus_message_iter_next(&dict));
+ } while (dbus_message_iter_next(&arr));
+
+ accessPoints.AppendElement(ap);
+ return NS_OK;
+}
+
+nsresult WifiScannerImpl::StoreSsid(DBusMessageIter* aVariant,
+ nsWifiAccessPoint* aAp) {
+ if (dbus_message_iter_get_arg_type(aVariant) != DBUS_TYPE_ARRAY) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusMessageIter variantMember;
+ dbus_message_iter_recurse(aVariant, &variantMember);
+
+ const uint32_t MAX_SSID_LEN = 32;
+ char ssid[MAX_SSID_LEN];
+ memset(ssid, '\0', ArrayLength(ssid));
+ uint32_t i = 0;
+ do {
+ if (dbus_message_iter_get_arg_type(&variantMember) != DBUS_TYPE_BYTE) {
+ return NS_ERROR_FAILURE;
+ }
+
+ dbus_message_iter_get_basic(&variantMember, &ssid[i]);
+ i++;
+ } while (dbus_message_iter_next(&variantMember) && i < MAX_SSID_LEN);
+
+ aAp->setSSID(ssid, i);
+ return NS_OK;
+}
+
+nsresult WifiScannerImpl::SetMac(DBusMessageIter* aVariant,
+ nsWifiAccessPoint* aAp) {
+ if (dbus_message_iter_get_arg_type(aVariant) != DBUS_TYPE_STRING) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // hwAddress format is XX:XX:XX:XX:XX:XX. Need to convert to XXXXXX format.
+ char* hwAddress;
+ dbus_message_iter_get_basic(aVariant, &hwAddress);
+ if (!hwAddress) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const uint32_t MAC_LEN = 6;
+ uint8_t macAddress[MAC_LEN];
+ char* savedPtr = nullptr;
+ char* token = strtok_r(hwAddress, ":", &savedPtr);
+ for (unsigned char& macAddres : macAddress) {
+ if (!token) {
+ return NS_ERROR_FAILURE;
+ }
+ macAddres = strtoul(token, nullptr, 16);
+ token = strtok_r(nullptr, ":", &savedPtr);
+ }
+ aAp->setMac(macAddress);
+ return NS_OK;
+}
+
+nsresult WifiScannerImpl::GetDBusIterator(DBusMessage* aMsg,
+ DBusMessageIter* aIterArray) {
+ DBusMessageIter iter;
+ if (!dbus_message_iter_init(aMsg, &iter)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ return NS_ERROR_FAILURE;
+ }
+
+ dbus_message_iter_recurse(&iter, aIterArray);
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/netwerk/wifi/dbus/DbusWifiScanner.h b/netwerk/wifi/dbus/DbusWifiScanner.h
new file mode 100644
index 0000000000..044615d390
--- /dev/null
+++ b/netwerk/wifi/dbus/DbusWifiScanner.h
@@ -0,0 +1,60 @@
+/* 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/. */
+
+#ifndef NSWIFIAPSCANNERDBUS_H_
+#define NSWIFIAPSCANNERDBUS_H_
+
+#define DBUS_API_SUBJECT_TO_CHANGE
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "WifiScanner.h"
+
+class nsIWifiAccessPoint;
+class nsWifiAccessPoint;
+
+namespace mozilla {
+
+using AccessPointArray = nsTArray<RefPtr<nsIWifiAccessPoint>>;
+
+class WifiScannerImpl final : public WifiScanner {
+ public:
+ explicit WifiScannerImpl();
+ ~WifiScannerImpl();
+
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ nsresult GetAccessPointsFromWLAN(AccessPointArray& accessPoints);
+
+ private:
+ nsresult SendGetDevices(AccessPointArray& accessPoints);
+ nsresult SendGetDeviceType(const char* aPath, AccessPointArray& accessPoints);
+ nsresult SendGetAccessPoints(const char* aPath,
+ AccessPointArray& accessPoints);
+ nsresult SendGetAPProperties(const char* aPath,
+ AccessPointArray& accessPoints);
+ nsresult IdentifyDevices(DBusMessage* aMsg, AccessPointArray& accessPoints);
+ nsresult IdentifyDeviceType(DBusMessage* aMsg, const char* aDevicePath,
+ AccessPointArray& accessPoints);
+ nsresult IdentifyAccessPoints(DBusMessage* aMsg,
+ AccessPointArray& accessPoints);
+ nsresult IdentifyAPProperties(DBusMessage* aMsg,
+ AccessPointArray& accessPoints);
+ nsresult StoreSsid(DBusMessageIter* aVariant, nsWifiAccessPoint* aAp);
+ nsresult SetMac(DBusMessageIter* aVariant, nsWifiAccessPoint* aAp);
+ nsresult GetDBusIterator(DBusMessage* aMsg, DBusMessageIter* aIterArray);
+
+ RefPtr<DBusConnection> mConnection;
+};
+
+} // namespace mozilla
+
+#endif // NSWIFIAPSCANNERDBUS_H_
diff --git a/netwerk/wifi/freebsd/FreeBsdWifiScanner.cpp b/netwerk/wifi/freebsd/FreeBsdWifiScanner.cpp
new file mode 100644
index 0000000000..c2a27d5ee2
--- /dev/null
+++ b/netwerk/wifi/freebsd/FreeBsdWifiScanner.cpp
@@ -0,0 +1,148 @@
+/* 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/. */
+
+// Developed by J.R. Oldroyd <fbsd@opal.com>, December 2012.
+
+// For FreeBSD we use the getifaddrs(3) to obtain the list of interfaces
+// and then check for those with an 802.11 media type and able to return
+// a list of stations. This is similar to ifconfig(8).
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_media.h>
+#ifdef __DragonFly__
+# include <netproto/802_11/ieee80211_ioctl.h>
+#else
+# include <net80211/ieee80211_ioctl.h>
+#endif
+
+#include <ifaddrs.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "FreeBsdWifiScanner.h"
+#include "nsWifiAccessPoint.h"
+#include "nsWifiMonitor.h"
+
+using namespace mozilla;
+
+#define LOG(args) MOZ_LOG(gWifiMonitorLog, mozilla::LogLevel::Debug, args)
+
+WifiScannerImpl::WifiScannerImpl(){};
+WifiScannerImpl::~WifiScannerImpl(){};
+
+nsresult WifiScannerImpl::GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints) {
+ // get list of interfaces
+ struct ifaddrs* ifal;
+ if (getifaddrs(&ifal) < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ accessPoints.Clear();
+
+ // loop through the interfaces
+ nsresult rv = NS_ERROR_FAILURE;
+ struct ifaddrs* ifa;
+ for (ifa = ifal; ifa; ifa = ifa->ifa_next) {
+ // limit to one interface per address
+ if (ifa->ifa_addr->sa_family != AF_LINK) {
+ continue;
+ }
+
+ // store interface name in socket structure
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, ifa->ifa_name, sizeof(ifr.ifr_name));
+ ifr.ifr_addr.sa_family = AF_LOCAL;
+
+ // open socket to interface
+ int s = socket(ifr.ifr_addr.sa_family, SOCK_DGRAM, 0);
+ if (s < 0) {
+ continue;
+ }
+
+ // clear interface media structure
+ struct ifmediareq ifmr;
+ memset(&ifmr, 0, sizeof(ifmr));
+ strncpy(ifmr.ifm_name, ifa->ifa_name, sizeof(ifmr.ifm_name));
+
+ // get interface media information
+ if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) {
+ close(s);
+ continue;
+ }
+
+ // check interface is a WiFi interface
+ if (IFM_TYPE(ifmr.ifm_active) != IFM_IEEE80211) {
+ close(s);
+ continue;
+ }
+
+ // perform WiFi scan
+ struct ieee80211req i802r;
+ char iscanbuf[32 * 1024];
+ memset(&i802r, 0, sizeof(i802r));
+ strncpy(i802r.i_name, ifa->ifa_name, sizeof(i802r.i_name));
+ i802r.i_type = IEEE80211_IOC_SCAN_RESULTS;
+ i802r.i_data = iscanbuf;
+ i802r.i_len = sizeof(iscanbuf);
+ if (ioctl(s, SIOCG80211, &i802r) < 0) {
+ close(s);
+ continue;
+ }
+
+ // close socket
+ close(s);
+
+ // loop through WiFi networks and build geoloc-lookup structure
+ char* vsr = (char*)i802r.i_data;
+ unsigned len = i802r.i_len;
+ while (len >= sizeof(struct ieee80211req_scan_result)) {
+ struct ieee80211req_scan_result* isr =
+ (struct ieee80211req_scan_result*)vsr;
+
+ // determine size of this entry
+ char* id;
+ int idlen;
+ if (isr->isr_meshid_len) {
+ id = vsr + isr->isr_ie_off + isr->isr_ssid_len;
+ idlen = isr->isr_meshid_len;
+ } else {
+ id = vsr + isr->isr_ie_off;
+ idlen = isr->isr_ssid_len;
+ }
+
+ // copy network data
+ char ssid[IEEE80211_NWID_LEN + 1];
+ strncpy(ssid, id, idlen);
+ ssid[idlen] = '\0';
+ nsWifiAccessPoint* ap = new nsWifiAccessPoint();
+ ap->setSSID(ssid, strlen(ssid));
+ ap->setMac(isr->isr_bssid);
+ ap->setSignal(isr->isr_rssi);
+ accessPoints.AppendElement(ap);
+ rv = NS_OK;
+
+ // log the data
+ LOG(
+ ("FreeBSD access point: "
+ "SSID: %s, MAC: %02x-%02x-%02x-%02x-%02x-%02x, "
+ "Strength: %d, Channel: %dMHz\n",
+ ssid, isr->isr_bssid[0], isr->isr_bssid[1], isr->isr_bssid[2],
+ isr->isr_bssid[3], isr->isr_bssid[4], isr->isr_bssid[5],
+ isr->isr_rssi, isr->isr_freq));
+
+ // increment pointers
+ len -= isr->isr_len;
+ vsr += isr->isr_len;
+ }
+ }
+
+ freeifaddrs(ifal);
+
+ return rv;
+}
diff --git a/netwerk/wifi/freebsd/FreeBsdWifiScanner.h b/netwerk/wifi/freebsd/FreeBsdWifiScanner.h
new file mode 100644
index 0000000000..1bd5bf0eb5
--- /dev/null
+++ b/netwerk/wifi/freebsd/FreeBsdWifiScanner.h
@@ -0,0 +1,31 @@
+/* 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/. */
+
+#pragma once
+
+#include "WifiScanner.h"
+
+class nsIWifiAccessPoint;
+
+namespace mozilla {
+
+class WifiScannerImpl final : public WifiScanner {
+ public:
+ WifiScannerImpl();
+ ~WifiScannerImpl();
+
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ nsresult GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints);
+};
+
+} // namespace mozilla
diff --git a/netwerk/wifi/gtest/TestWifiMonitor.cpp b/netwerk/wifi/gtest/TestWifiMonitor.cpp
new file mode 100644
index 0000000000..719658c4d4
--- /dev/null
+++ b/netwerk/wifi/gtest/TestWifiMonitor.cpp
@@ -0,0 +1,368 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "nsIWifiListener.h"
+#include "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+#include "WifiScanner.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "nsINetworkLinkService.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+
+#if defined(XP_WIN) && defined(_M_IX86)
+# include <objbase.h> // STDMETHODCALLTYPE
+#endif
+
+// Tests that wifi scanning happens on the right network change events,
+// and that wifi-scan polling is operable on mobile networks.
+
+using ::testing::AtLeast;
+using ::testing::Exactly;
+
+static mozilla::LazyLogModule gLog("TestWifiMonitor");
+#define LOGI(x) MOZ_LOG(gLog, mozilla::LogLevel::Info, x)
+#define LOGD(x) MOZ_LOG(gLog, mozilla::LogLevel::Debug, x)
+
+namespace mozilla {
+
+// Timeout if no updates are received for 1s.
+static const uint32_t WIFI_SCAN_TEST_RESULT_TIMEOUT_MS = 1000;
+
+// ID counter, used to make sure each call to GetAccessPointsFromWLAN
+// returns "new" access points.
+static int gCurrentId = 0;
+
+static uint32_t gNumScanResults = 0;
+
+template <typename T>
+bool IsNonZero(const T&) {
+ return true;
+}
+
+template <>
+bool IsNonZero<int>(const int& aVal) {
+ return aVal != 0;
+}
+
+struct LinkTypeMobility {
+ const char* mLinkType;
+ bool mIsMobile;
+};
+
+class MockWifiScanner : public WifiScanner {
+ public:
+ MOCK_METHOD(nsresult, GetAccessPointsFromWLAN,
+ (nsTArray<RefPtr<nsIWifiAccessPoint>> & aAccessPoints),
+ (override));
+};
+
+class MockWifiListener : public nsIWifiListener {
+ virtual ~MockWifiListener() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+#if defined(XP_WIN) && defined(_M_IX86)
+ MOCK_METHOD(nsresult, OnChange,
+ (const nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints),
+ (override, Calltype(STDMETHODCALLTYPE)));
+ MOCK_METHOD(nsresult, OnError, (nsresult error),
+ (override, Calltype(STDMETHODCALLTYPE)));
+#else
+ MOCK_METHOD(nsresult, OnChange,
+ (const nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints),
+ (override));
+ MOCK_METHOD(nsresult, OnError, (nsresult error), (override));
+#endif
+};
+
+NS_IMPL_ISUPPORTS(MockWifiListener, nsIWifiListener)
+
+class TestWifiMonitor : public ::testing::Test {
+ public:
+ TestWifiMonitor() {
+ mObs = mozilla::services::GetObserverService();
+ MOZ_ASSERT(mObs);
+
+ nsresult rv;
+ nsCOMPtr<nsINetworkLinkService> nls =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ EXPECT_TRUE(nls);
+ rv = nls->GetLinkType(&mOrigLinkType);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ rv = nls->GetIsLinkUp(&mOrigIsLinkUp);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ rv = nls->GetLinkStatusKnown(&mOrigLinkStatusKnown);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ // Reduce wifi-polling interval. 0 turns polling off.
+ mOldScanInterval = Preferences::GetInt(WIFI_SCAN_INTERVAL_MS_PREF);
+ Preferences::SetInt(WIFI_SCAN_INTERVAL_MS_PREF, 10);
+ }
+
+ ~TestWifiMonitor() {
+ Preferences::SetInt(WIFI_SCAN_INTERVAL_MS_PREF, mOldScanInterval);
+
+ // Restore network link type
+ const char* linkType = nullptr;
+ switch (mOrigLinkType) {
+ case nsINetworkLinkService::LINK_TYPE_UNKNOWN:
+ linkType = NS_NETWORK_LINK_TYPE_UNKNOWN;
+ break;
+ case nsINetworkLinkService::LINK_TYPE_ETHERNET:
+ linkType = NS_NETWORK_LINK_TYPE_ETHERNET;
+ break;
+ case nsINetworkLinkService::LINK_TYPE_USB:
+ linkType = NS_NETWORK_LINK_TYPE_USB;
+ break;
+ case nsINetworkLinkService::LINK_TYPE_WIFI:
+ linkType = NS_NETWORK_LINK_TYPE_WIFI;
+ break;
+ case nsINetworkLinkService::LINK_TYPE_MOBILE:
+ linkType = NS_NETWORK_LINK_TYPE_MOBILE;
+ break;
+ case nsINetworkLinkService::LINK_TYPE_WIMAX:
+ linkType = NS_NETWORK_LINK_TYPE_WIMAX;
+ break;
+ }
+ EXPECT_TRUE(linkType);
+ mObs->NotifyObservers(nullptr, NS_NETWORK_LINK_TYPE_TOPIC,
+ NS_ConvertUTF8toUTF16(linkType).get());
+
+ const char* linkStatus = nullptr;
+ if (mOrigLinkStatusKnown) {
+ if (mOrigIsLinkUp) {
+ linkStatus = NS_NETWORK_LINK_DATA_UP;
+ } else {
+ linkStatus = NS_NETWORK_LINK_DATA_DOWN;
+ }
+ } else {
+ linkStatus = NS_NETWORK_LINK_DATA_UNKNOWN;
+ }
+ EXPECT_TRUE(linkStatus);
+ mObs->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC,
+ NS_ConvertUTF8toUTF16(linkStatus).get());
+ }
+
+ void SetUp() override {
+ mWifiMonitor = MakeRefPtr<nsWifiMonitor>(MakeUnique<MockWifiScanner>());
+ EXPECT_TRUE(!mWifiMonitor->IsPolling());
+
+ mWifiListener = new MockWifiListener();
+ LOGI(("monitor: %p | scanner: %p | listener: %p", mWifiMonitor.get(),
+ mWifiMonitor->mWifiScanner.get(), mWifiListener.get()));
+ }
+
+ void TearDown() override {
+ ::testing::Mock::VerifyAndClearExpectations(
+ mWifiMonitor->mWifiScanner.get());
+ ::testing::Mock::VerifyAndClearExpectations(mWifiListener.get());
+
+ // Manually disconnect observers so that the monitor can be destroyed.
+ // This would normally be done on xpcom-shutdown but that is sent after
+ // the tests run, which is too late to avoid a gtest memory-leak error.
+ mWifiMonitor->Close();
+
+ mWifiMonitor = nullptr;
+ mWifiListener = nullptr;
+ gCurrentId = 0;
+ }
+
+ protected:
+ // Fn must be the type of a parameterless function that returns a boolean
+ // indicating success.
+ template <typename Card1, typename Card2, typename Card3, typename Fn>
+ void CheckAsync(Card1&& nScans, Card2&& nChanges, Card3&& nErrors, Fn fn) {
+ // Only add WillRepeatedly handler if scans is more than 0, to avoid a
+ // VERY LOUD gtest warning.
+ if (IsNonZero(nScans)) {
+ EXPECT_CALL(
+ *static_cast<MockWifiScanner*>(mWifiMonitor->mWifiScanner.get()),
+ GetAccessPointsFromWLAN)
+ .Times(nScans)
+ .WillRepeatedly(
+ [](nsTArray<RefPtr<nsIWifiAccessPoint>>& aAccessPoints) {
+ EXPECT_TRUE(!NS_IsMainThread());
+ EXPECT_TRUE(aAccessPoints.IsEmpty());
+ nsWifiAccessPoint* ap = new nsWifiAccessPoint();
+ // Signal will be unique so we won't match the prior access
+ // point list.
+ ap->mSignal = gCurrentId++;
+ aAccessPoints.AppendElement(RefPtr(ap));
+ return NS_OK;
+ });
+ } else {
+ EXPECT_CALL(
+ *static_cast<MockWifiScanner*>(mWifiMonitor->mWifiScanner.get()),
+ GetAccessPointsFromWLAN)
+ .Times(nScans);
+ }
+
+ if (IsNonZero(nChanges)) {
+ EXPECT_CALL(*mWifiListener, OnChange)
+ .Times(nChanges)
+ .WillRepeatedly(
+ [](const nsTArray<RefPtr<nsIWifiAccessPoint>>& aAccessPoints) {
+ EXPECT_TRUE(NS_IsMainThread());
+ EXPECT_EQ(aAccessPoints.Length(), 1u);
+ ++gNumScanResults;
+ return NS_OK;
+ });
+ } else {
+ EXPECT_CALL(*mWifiListener, OnChange).Times(nChanges);
+ }
+
+ if (IsNonZero(nErrors)) {
+ EXPECT_CALL(*mWifiListener, OnError)
+ .Times(nErrors)
+ .WillRepeatedly([](nsresult aError) {
+ EXPECT_TRUE(NS_IsMainThread());
+ return NS_OK;
+ });
+ } else {
+ EXPECT_CALL(*mWifiListener, OnError).Times(nErrors);
+ }
+
+ EXPECT_TRUE(fn());
+ }
+
+ void CheckStartWatching(bool aShouldPoll) {
+ LOGI(("CheckStartWatching | aShouldPoll: %s",
+ aShouldPoll ? "true" : "false"));
+ CheckAsync(aShouldPoll ? AtLeast(1) : Exactly(1) /* nScans */,
+ aShouldPoll ? AtLeast(1) : Exactly(1) /* nChanges */,
+ 0 /* nErrors */, [&] {
+ return NS_SUCCEEDED(mWifiMonitor->StartWatching(
+ mWifiListener, aShouldPoll)) &&
+ WaitForScanResult(1);
+ });
+ }
+
+ void CheckStopWatching() {
+ LOGI(("CheckStopWatching"));
+ CheckAsync(0 /* nScans */, 0 /* nChanges */, 0 /* nErrors */, [&] {
+ return NS_SUCCEEDED(mWifiMonitor->StopWatching(mWifiListener));
+ });
+ }
+
+ void CheckNotifyAndNoScanResults(const char* aTopic, const char16_t* aData) {
+ LOGI(("CheckNotifyAndNoScanResults: (%s, %s)", aTopic,
+ NS_ConvertUTF16toUTF8(aData).get()));
+ CheckAsync(0 /* nScans */, 0 /* nChanges */, 0 /* nErrors */, [&] {
+ return NS_SUCCEEDED(mObs->NotifyObservers(nullptr, aTopic, aData));
+ });
+ }
+
+ void CheckNotifyAndWaitForScanResult(const char* aTopic,
+ const char16_t* aData) {
+ LOGI(("CheckNotifyAndWaitForScanResult: (%s, %s)", aTopic,
+ NS_ConvertUTF16toUTF8(aData).get()));
+ CheckAsync(
+ AtLeast(1) /* nScans */, AtLeast(1) /* nChanges */, 0 /* nErrors */,
+ [&] {
+ return NS_SUCCEEDED(mObs->NotifyObservers(nullptr, aTopic, aData)) &&
+ WaitForScanResult(1);
+ });
+ }
+
+ void CheckNotifyAndWaitForPollingScans(const char* aTopic,
+ const char16_t* aData) {
+ LOGI(("CheckNotifyAndWaitForPollingScans: (%s, %s)", aTopic,
+ NS_ConvertUTF16toUTF8(aData).get()));
+ CheckAsync(
+ AtLeast(2) /* nScans */, AtLeast(2) /* nChanges */, 0 /* nErrors */,
+ [&] {
+ // Wait for more than one scan.
+ return NS_SUCCEEDED(mObs->NotifyObservers(nullptr, aTopic, aData)) &&
+ WaitForScanResult(2);
+ });
+ }
+
+ void CheckMessages(bool aShouldPoll) {
+ // NS_NETWORK_LINK_TOPIC messages should cause a new scan.
+ const char* kLinkTopicDatas[] = {
+ NS_NETWORK_LINK_DATA_UP, NS_NETWORK_LINK_DATA_DOWN,
+ NS_NETWORK_LINK_DATA_CHANGED, NS_NETWORK_LINK_DATA_UNKNOWN};
+
+ auto checkFn = aShouldPoll
+ ? &TestWifiMonitor::CheckNotifyAndWaitForPollingScans
+ : &TestWifiMonitor::CheckNotifyAndWaitForScanResult;
+
+ for (const auto& data : kLinkTopicDatas) {
+ CheckStartWatching(aShouldPoll);
+ EXPECT_EQ(mWifiMonitor->IsPolling(), aShouldPoll);
+ (this->*checkFn)(NS_NETWORK_LINK_TOPIC,
+ NS_ConvertUTF8toUTF16(data).get());
+ EXPECT_EQ(mWifiMonitor->IsPolling(), aShouldPoll);
+ CheckStopWatching();
+ EXPECT_TRUE(!mWifiMonitor->IsPolling());
+ }
+
+ // NS_NETWORK_LINK_TYPE_TOPIC should cause wifi scan polling iff the topic
+ // says we have switched to a mobile network (LINK_TYPE_MOBILE or
+ // LINK_TYPE_WIMAX) or we are polling the wifi-scanner (aShouldPoll).
+ const LinkTypeMobility kLinkTypeTopicDatas[] = {
+ {NS_NETWORK_LINK_TYPE_UNKNOWN, false /* mIsMobile */},
+ {NS_NETWORK_LINK_TYPE_ETHERNET, false},
+ {NS_NETWORK_LINK_TYPE_USB, false},
+ {NS_NETWORK_LINK_TYPE_WIFI, false},
+ {NS_NETWORK_LINK_TYPE_WIMAX, true},
+ {NS_NETWORK_LINK_TYPE_MOBILE, true}};
+
+ for (const auto& data : kLinkTypeTopicDatas) {
+ bool shouldPoll = (aShouldPoll || data.mIsMobile);
+ checkFn = shouldPoll ? &TestWifiMonitor::CheckNotifyAndWaitForPollingScans
+ : &TestWifiMonitor::CheckNotifyAndNoScanResults;
+
+ CheckStartWatching(aShouldPoll);
+ (this->*checkFn)(NS_NETWORK_LINK_TYPE_TOPIC,
+ NS_ConvertUTF8toUTF16(data.mLinkType).get());
+ EXPECT_EQ(mWifiMonitor->IsPolling(), shouldPoll);
+ CheckStopWatching();
+ EXPECT_TRUE(!mWifiMonitor->IsPolling());
+ }
+ }
+
+ bool WaitForScanResult(uint32_t nScansToWaitFor) {
+ uint32_t oldScanResults = gNumScanResults;
+ auto startTime = TimeStamp::Now();
+
+ return mozilla::SpinEventLoopUntil(
+ "TestWifiMonitor::WaitForScanResult"_ns, [&]() {
+ LOGD(
+ ("gNumScanResults: %d | oldScanResults: %d | "
+ "nScansToWaitFor: %d | timeMs: %d",
+ (int)gNumScanResults, (int)oldScanResults, (int)nScansToWaitFor,
+ (int)(TimeStamp::Now() - startTime).ToMilliseconds()));
+ return gNumScanResults >= (oldScanResults + nScansToWaitFor) ||
+ (TimeStamp::Now() - startTime).ToMilliseconds() >
+ WIFI_SCAN_TEST_RESULT_TIMEOUT_MS * nScansToWaitFor;
+ });
+ }
+
+ RefPtr<nsWifiMonitor> mWifiMonitor;
+ nsCOMPtr<nsIObserverService> mObs;
+
+ RefPtr<MockWifiListener> mWifiListener;
+
+ int mOldScanInterval;
+ uint32_t mOrigLinkType = 0;
+ bool mOrigIsLinkUp = false;
+ bool mOrigLinkStatusKnown = false;
+};
+
+TEST_F(TestWifiMonitor, WifiScanNoPolling) { CheckMessages(false); }
+
+TEST_F(TestWifiMonitor, WifiScanPolling) { CheckMessages(true); }
+
+} // namespace mozilla
diff --git a/netwerk/wifi/gtest/moz.build b/netwerk/wifi/gtest/moz.build
new file mode 100644
index 0000000000..d47976926f
--- /dev/null
+++ b/netwerk/wifi/gtest/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+LOCAL_INCLUDES += ["/netwerk/wifi"]
+
+UNIFIED_SOURCES += [
+ "TestWifiMonitor.cpp",
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/netwerk/wifi/mac/MacWifiScanner.h b/netwerk/wifi/mac/MacWifiScanner.h
new file mode 100644
index 0000000000..43961da8fa
--- /dev/null
+++ b/netwerk/wifi/mac/MacWifiScanner.h
@@ -0,0 +1,28 @@
+/* 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/. */
+
+#pragma once
+
+#include "WifiScanner.h"
+
+class nsWifiAccessPoint;
+
+namespace mozilla {
+
+class WifiScannerImpl final : public WifiScanner {
+ public:
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ nsresult GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints);
+};
+
+} // namespace mozilla
diff --git a/netwerk/wifi/mac/MacWifiScanner.mm b/netwerk/wifi/mac/MacWifiScanner.mm
new file mode 100644
index 0000000000..eea0a81627
--- /dev/null
+++ b/netwerk/wifi/mac/MacWifiScanner.mm
@@ -0,0 +1,106 @@
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+#import <CoreWLAN/CoreWLAN.h>
+
+#include <dlfcn.h>
+#include <unistd.h>
+
+#include <objc/objc.h>
+#include <objc/objc-runtime.h>
+
+#include "nsObjCExceptions.h"
+#include "nsCOMArray.h"
+#include "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+#include "MacWifiScanner.h"
+
+namespace mozilla {
+
+nsresult WifiScannerImpl::GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ accessPoints.Clear();
+
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+ @try {
+ NSBundle* bundle = [[[NSBundle alloc]
+ initWithPath:@"/System/Library/Frameworks/CoreWLAN.framework"] autorelease];
+ if (!bundle) {
+ [pool release];
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Class CWI_class = [bundle classNamed:@"CWInterface"];
+ if (!CWI_class) {
+ [pool release];
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ id scanResult = [[CWI_class interface] scanForNetworksWithSSID:nil error:nil];
+ if (!scanResult) {
+ [pool release];
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NSArray* scan = [NSMutableArray arrayWithArray:scanResult];
+ NSEnumerator* enumerator = [scan objectEnumerator];
+
+ while (id anObject = [enumerator nextObject]) {
+ auto* ap = new nsWifiAccessPoint();
+ if (!ap) {
+ [pool release];
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // [CWInterface bssidData] is deprecated on OS X 10.7 and up. Which is
+ // is a pain, so we'll use it for as long as it's available.
+ unsigned char macData[6] = {0};
+ if ([anObject respondsToSelector:@selector(bssidData)]) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wobjc-method-access"
+ NSData* data = [anObject bssidData];
+#pragma clang diagnostic pop
+ if (data) {
+ memcpy(macData, [data bytes], 6);
+ }
+ } else {
+ // [CWInterface bssid] returns a string formatted "00:00:00:00:00:00".
+ NSString* macString = [anObject bssid];
+ if (macString && ([macString length] == 17)) {
+ for (NSUInteger i = 0; i < 6; ++i) {
+ NSString* part = [macString substringWithRange:NSMakeRange(i * 3, 2)];
+ NSScanner* scanner = [NSScanner scannerWithString:part];
+ unsigned int data = 0;
+ if (![scanner scanHexInt:&data]) {
+ data = 0;
+ }
+ macData[i] = (unsigned char)data;
+ }
+ }
+ }
+
+ int signal = (int)((NSInteger)[anObject rssiValue]);
+ ap->setMac(macData);
+ ap->setSignal(signal);
+ ap->setSSID([[anObject ssid] UTF8String], 32);
+
+ accessPoints.AppendElement(ap);
+ }
+ } @catch (NSException*) {
+ [pool release];
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ [pool release];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_NOT_AVAILABLE);
+}
+
+} // namespace mozilla
diff --git a/netwerk/wifi/mac/Wifi.h b/netwerk/wifi/mac/Wifi.h
new file mode 100644
index 0000000000..3a177f9643
--- /dev/null
+++ b/netwerk/wifi/mac/Wifi.h
@@ -0,0 +1,119 @@
+// Copyright 2008, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// The contents of this file are taken from Apple80211.h from the iStumbler
+// project (http://www.istumbler.net). This project is released under the BSD
+// license with the following restrictions.
+//
+// Copyright (c) 02006, Alf Watt (alf@istumbler.net). All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// * Neither the name of iStumbler nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This is the reverse engineered header for the Apple80211 private framework.
+// The framework can be found at
+// /System/Library/PrivateFrameworks/Apple80211.framework.
+
+#ifndef GEARS_GEOLOCATION_OSX_WIFI_H__
+#define GEARS_GEOLOCATION_OSX_WIFI_H__
+
+#include <CoreFoundation/CoreFoundation.h>
+
+extern "C" {
+
+typedef SInt32 WIErr;
+
+// A WirelessContext should be created using WirelessAttach
+// before any other Wireless functions are called. WirelessDetach
+// is used to dispose of a WirelessContext.
+typedef struct __WirelessContext* WirelessContextPtr;
+
+// WirelessAttach
+//
+// This should be called before all other wireless functions.
+typedef WIErr (*WirelessAttachFunction)(WirelessContextPtr* outContext,
+ const UInt32);
+
+// WirelessDetach
+//
+// This should be called after all other wireless functions.
+typedef WIErr (*WirelessDetachFunction)(WirelessContextPtr inContext);
+
+typedef UInt16 WINetworkInfoFlags;
+
+struct WirelessNetworkInfo {
+ UInt16 channel; // Channel for the network.
+ SInt16 noise; // Noise for the network. 0 for Adhoc.
+ SInt16 signal; // Signal strength of the network. 0 for Adhoc.
+ UInt8 macAddress[6]; // MAC address of the wireless access point.
+ UInt16 beaconInterval; // Beacon interval in milliseconds
+ WINetworkInfoFlags flags; // Flags for the network
+ UInt16 nameLen;
+ SInt8 name[32];
+};
+
+// WirelessScanSplit
+//
+// WirelessScanSplit scans for available wireless networks. It will allocate 2
+// CFArrays to store a list of managed and adhoc networks. The arrays hold
+// CFData objects which contain WirelessNetworkInfo structures.
+//
+// Note: An adhoc network created on the computer the scan is running on will
+// not be found. WirelessGetInfo can be used to find info about a local adhoc
+// network.
+//
+// If stripDups != 0 only one bases tation for each SSID will be returned.
+typedef WIErr (*WirelessScanSplitFunction)(WirelessContextPtr inContext,
+ CFArrayRef* apList,
+ CFArrayRef* adhocList,
+ const UInt32 stripDups);
+
+} // extern "C"
+
+#endif // GEARS_GEOLOCATION_OSX_WIFI_H__
diff --git a/netwerk/wifi/moz.build b/netwerk/wifi/moz.build
new file mode 100644
index 0000000000..83e0546a50
--- /dev/null
+++ b/netwerk/wifi/moz.build
@@ -0,0 +1,63 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ "nsIWifiAccessPoint.idl",
+ "nsIWifiListener.idl",
+ "nsIWifiMonitor.idl",
+]
+
+XPIDL_MODULE = "necko_wifi"
+
+UNIFIED_SOURCES += [
+ "nsWifiAccessPoint.cpp",
+ "nsWifiMonitor.cpp",
+]
+
+if CONFIG["OS_ARCH"] == "Darwin":
+ SOURCES += [
+ "mac/MacWifiScanner.mm",
+ ]
+ LOCAL_INCLUDES += [
+ "mac",
+ ]
+elif CONFIG["OS_ARCH"] in ("DragonFly", "FreeBSD"):
+ UNIFIED_SOURCES += [
+ "freebsd/FreeBsdWifiScanner.cpp",
+ ]
+ LOCAL_INCLUDES += [
+ "freebsd",
+ ]
+elif CONFIG["OS_ARCH"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "win/WinWifiScanner.cpp",
+ "win/WlanLibrary.cpp",
+ ]
+ LOCAL_INCLUDES += [
+ "win",
+ ]
+elif CONFIG["OS_ARCH"] == "SunOS":
+ CXXFLAGS += CONFIG["GLIB_CFLAGS"]
+ UNIFIED_SOURCES += [
+ "solaris/SolarisWifiScanner.cpp",
+ ]
+ LOCAL_INCLUDES += [
+ "solaris",
+ ]
+elif CONFIG["NECKO_WIFI_DBUS"]:
+ UNIFIED_SOURCES += [
+ "dbus/DbusWifiScanner.cpp",
+ ]
+ LOCAL_INCLUDES += [
+ "dbus",
+ ]
+ CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"]
+
+TEST_DIRS += ["gtest"]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/netwerk/wifi/nsIWifiAccessPoint.idl b/netwerk/wifi/nsIWifiAccessPoint.idl
new file mode 100644
index 0000000000..c98b42edc4
--- /dev/null
+++ b/netwerk/wifi/nsIWifiAccessPoint.idl
@@ -0,0 +1,41 @@
+/* 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 "nsISupports.idl"
+
+[scriptable, uuid(E28E614F-8F86-44FF-BCF5-5F18225834A0)]
+interface nsIWifiAccessPoint : nsISupports
+{
+
+ /*
+ * The mac address of the WiFi node. The format of this string is:
+ * XX-XX-XX-XX-XX-XX
+ */
+
+ readonly attribute ACString mac;
+
+ /*
+ * Public name of a wireless network. The charset of this string is ASCII.
+ * This string will be null if not available.
+ *
+ * Note that this is a conversion of the SSID which makes it "displayable".
+ * for any comparisons, you want to use the Raw SSID.
+ */
+
+ readonly attribute AString ssid;
+
+ /*
+ * Public name of a wireless network. These are the bytes that are read off
+ * of the network, may contain nulls, and generally shouldn't be displayed to
+ * the user.
+ *
+ */
+
+ readonly attribute ACString rawSSID;
+
+ /*
+ * Current signal strength measured in dBm.
+ */
+ readonly attribute long signal;
+};
diff --git a/netwerk/wifi/nsIWifiListener.idl b/netwerk/wifi/nsIWifiListener.idl
new file mode 100644
index 0000000000..49024f405f
--- /dev/null
+++ b/netwerk/wifi/nsIWifiListener.idl
@@ -0,0 +1,29 @@
+/* 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 "nsISupports.idl"
+
+interface nsIWifiAccessPoint;
+
+[scriptable, uuid(BCD4BEDE-F4A5-4A62-9071-D7A60174E376)]
+interface nsIWifiListener : nsISupports
+{
+ /*
+ * Called when the list of access points changes.
+ *
+ * @param accessPoints An array of nsIWifiAccessPoint representing all
+ * access points in view.
+ */
+
+ void onChange(in Array<nsIWifiAccessPoint> accessPoints);
+
+ /*
+ * Called when there is a problem with listening to wifi
+ *
+ * @param error the error which caused this event. The
+ * error values will be nsresult codes.
+ */
+
+ void onError(in nsresult error);
+};
diff --git a/netwerk/wifi/nsIWifiMonitor.idl b/netwerk/wifi/nsIWifiMonitor.idl
new file mode 100644
index 0000000000..feda68f77e
--- /dev/null
+++ b/netwerk/wifi/nsIWifiMonitor.idl
@@ -0,0 +1,27 @@
+/* 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 "nsISupports.idl"
+
+interface nsIWifiListener;
+
+[scriptable, uuid(F289701E-D9AF-4685-BC2F-E4226FF7C018)]
+interface nsIWifiMonitor : nsISupports
+{
+ /*
+ * startWatching
+ * aListener will be called once, then each time the list of wifi access
+ * points change. The wifi access point list will be updated when our
+ * network changes, or on a regular interval if we are on a mobile network.
+ * If aForcePolling is true then we will always poll as long as this
+ * listener is watching.
+ */
+ void startWatching(in nsIWifiListener aListener, in boolean aForcePolling);
+
+ /*
+ * stopWatching
+ * cancels all notifications to the |aListener|.
+ */
+ void stopWatching(in nsIWifiListener aListener);
+};
diff --git a/netwerk/wifi/nsWifiAccessPoint.cpp b/netwerk/wifi/nsWifiAccessPoint.cpp
new file mode 100644
index 0000000000..9803553994
--- /dev/null
+++ b/netwerk/wifi/nsWifiAccessPoint.cpp
@@ -0,0 +1,67 @@
+/* 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 "nsWifiAccessPoint.h"
+#include "nsString.h"
+#include "mozilla/Logging.h"
+
+extern mozilla::LazyLogModule gWifiMonitorLog;
+#define LOG(args) MOZ_LOG(gWifiMonitorLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsWifiAccessPoint, nsIWifiAccessPoint)
+
+nsWifiAccessPoint::nsWifiAccessPoint() {
+ // make sure these are null terminated (because we are paranoid)
+ mMac[0] = '\0';
+ mSsid[0] = '\0';
+ mSsidLen = 0;
+ mSignal = -1000;
+}
+
+NS_IMETHODIMP nsWifiAccessPoint::GetMac(nsACString& aMac) {
+ aMac.Assign(mMac);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWifiAccessPoint::GetSsid(nsAString& aSsid) {
+ // just assign and embedded nulls will truncate resulting
+ // in a displayable string.
+ aSsid.AssignASCII(mSsid);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWifiAccessPoint::GetRawSSID(nsACString& aRawSsid) {
+ aRawSsid.Assign(mSsid, mSsidLen); // SSIDs are 32 chars long
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWifiAccessPoint::GetSignal(int32_t* aSignal) {
+ NS_ENSURE_ARG(aSignal);
+ *aSignal = mSignal;
+ return NS_OK;
+}
+
+int nsWifiAccessPoint::Compare(const nsWifiAccessPoint& o) const {
+ int ret = strcmp(mMac, o.mMac);
+ if (ret) {
+ return ret;
+ }
+ if (mSsidLen != o.mSsidLen) {
+ return (mSsidLen < o.mSsidLen) ? -1 : 1;
+ }
+ ret = strncmp(mSsid, o.mSsid, mSsidLen);
+ if (ret) {
+ return ret;
+ }
+ if (mSignal == o.mSignal) {
+ return 0;
+ }
+ return (mSignal < o.mSignal) ? -1 : 1;
+}
+
+bool nsWifiAccessPoint::operator==(const nsWifiAccessPoint& o) const {
+ LOG(("nsWifiAccessPoint comparing %s->%s | %s->%s | %d -> %d\n", mSsid,
+ o.mSsid, mMac, o.mMac, mSignal, o.mSignal));
+ return Compare(o) == 0;
+}
diff --git a/netwerk/wifi/nsWifiAccessPoint.h b/netwerk/wifi/nsWifiAccessPoint.h
new file mode 100644
index 0000000000..3f83b2c0c0
--- /dev/null
+++ b/netwerk/wifi/nsWifiAccessPoint.h
@@ -0,0 +1,78 @@
+/* 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/. */
+
+#ifndef __nsWifiAccessPoint__
+#define __nsWifiAccessPoint__
+
+#include <algorithm>
+#include "nsWifiMonitor.h"
+#include "nsIWifiAccessPoint.h"
+
+#include "nsString.h"
+#include "nsCOMArray.h"
+#include "mozilla/ArrayUtils.h" // ArrayLength
+#include "mozilla/Attributes.h"
+#include "mozilla/Sprintf.h"
+
+class nsWifiAccessPoint final : public nsIWifiAccessPoint {
+ ~nsWifiAccessPoint() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWIFIACCESSPOINT
+
+ nsWifiAccessPoint();
+
+ char mMac[18]{0};
+ int mSignal;
+ char mSsid[33]{0};
+ int mSsidLen;
+
+ void setSignal(int signal) { mSignal = signal; }
+
+ void setMacRaw(const char* aString) {
+ memcpy(mMac, aString, mozilla::ArrayLength(mMac));
+ }
+
+ void setMac(const unsigned char mac_as_int[6]) {
+ // mac_as_int is big-endian. Write in byte chunks.
+ // Format is XX-XX-XX-XX-XX-XX.
+
+ const unsigned char holder[6] = {0};
+ if (!mac_as_int) {
+ mac_as_int = holder;
+ }
+
+ static const char* kMacFormatString = ("%02x-%02x-%02x-%02x-%02x-%02x");
+
+ SprintfLiteral(mMac, kMacFormatString, mac_as_int[0], mac_as_int[1],
+ mac_as_int[2], mac_as_int[3], mac_as_int[4], mac_as_int[5]);
+
+ mMac[17] = 0;
+ }
+
+ void setSSIDRaw(const char* aSSID, size_t len) {
+ mSsidLen = std::min(len, mozilla::ArrayLength(mSsid));
+ memcpy(mSsid, aSSID, mSsidLen);
+ }
+
+ void setSSID(const char* aSSID, unsigned long len) {
+ if (aSSID && (len < sizeof(mSsid))) {
+ strncpy(mSsid, aSSID, len);
+ mSsid[len] = 0;
+ mSsidLen = len;
+ } else {
+ mSsid[0] = 0;
+ mSsidLen = 0;
+ }
+ }
+
+ // 3-value compare for nsWifiAccessPoint
+ int Compare(const nsWifiAccessPoint& o) const;
+
+ bool operator==(const nsWifiAccessPoint& o) const;
+ bool operator!=(const nsWifiAccessPoint& o) const { return !(*this == o); }
+};
+
+#endif
diff --git a/netwerk/wifi/nsWifiMonitor.cpp b/netwerk/wifi/nsWifiMonitor.cpp
new file mode 100644
index 0000000000..dc05cde398
--- /dev/null
+++ b/netwerk/wifi/nsWifiMonitor.cpp
@@ -0,0 +1,400 @@
+/* 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 "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+#include "nsXPCOMCID.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+#include "nsINetworkLinkService.h"
+#include "nsQueryObject.h"
+#include "nsNetCID.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/DelayedRunnable.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Services.h"
+
+#if defined(XP_WIN)
+# include "WinWifiScanner.h"
+#endif
+
+#if defined(XP_MACOSX)
+# include "nsCocoaFeatures.h"
+# include "MacWifiScanner.h"
+#endif
+
+#if defined(__DragonFly__) || defined(__FreeBSD__)
+# include "FreeBsdWifiScanner.h"
+#endif
+
+#if defined(XP_SOLARIS)
+# include "SolarisWifiScanner.h"
+#endif
+
+#if defined(NECKO_WIFI_DBUS)
+# include "DbusWifiScanner.h"
+#endif
+
+using namespace mozilla;
+
+LazyLogModule gWifiMonitorLog("WifiMonitor");
+#define LOG(args) MOZ_LOG(gWifiMonitorLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsWifiMonitor, nsIObserver, nsIWifiMonitor)
+
+// Main thread only.
+static uint64_t sNextPollingIndex = 1;
+
+static uint64_t NextPollingIndex() {
+ MOZ_ASSERT(NS_IsMainThread());
+ ++sNextPollingIndex;
+
+ // Any non-zero value is valid and we don't care about overflow beyond
+ // that we never want the index to be zero.
+ if (sNextPollingIndex == 0) {
+ ++sNextPollingIndex;
+ }
+ return sNextPollingIndex;
+}
+
+// Should we poll wifi or just check it when our network changes?
+// We poll when we are on a network where the wifi environment
+// could reasonably be expected to change much -- so, on mobile.
+static bool ShouldPollForNetworkType(const char16_t* aLinkType) {
+ return NS_ConvertUTF16toUTF8(aLinkType) == NS_NETWORK_LINK_TYPE_WIMAX ||
+ NS_ConvertUTF16toUTF8(aLinkType) == NS_NETWORK_LINK_TYPE_MOBILE;
+}
+
+// Enum value version.
+static bool ShouldPollForNetworkType(uint32_t aLinkType) {
+ return aLinkType == nsINetworkLinkService::LINK_TYPE_WIMAX ||
+ aLinkType == nsINetworkLinkService::LINK_TYPE_MOBILE;
+}
+
+nsWifiMonitor::nsWifiMonitor(UniquePtr<mozilla::WifiScanner>&& aScanner)
+ : mWifiScanner(std::move(aScanner)) {
+ LOG(("Creating nsWifiMonitor"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ obsSvc->AddObserver(this, NS_NETWORK_LINK_TYPE_TOPIC, false);
+ obsSvc->AddObserver(this, "xpcom-shutdown", false);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsINetworkLinkService> nls =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && nls) {
+ uint32_t linkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
+ rv = nls->GetLinkType(&linkType);
+ if (NS_SUCCEEDED(rv)) {
+ mShouldPollForCurrentNetwork = ShouldPollForNetworkType(linkType);
+ if (ShouldPoll()) {
+ mPollingId = NextPollingIndex();
+ DispatchScanToBackgroundThread(mPollingId);
+ }
+ LOG(("nsWifiMonitor network type: %u | shouldPoll: %s", linkType,
+ mShouldPollForCurrentNetwork ? "true" : "false"));
+ }
+ }
+}
+nsWifiMonitor::~nsWifiMonitor() { LOG(("Destroying nsWifiMonitor")); }
+
+void nsWifiMonitor::Close() {
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TYPE_TOPIC);
+ obsSvc->RemoveObserver(this, "xpcom-shutdown");
+ }
+
+ mPollingId = 0;
+ if (mThread) {
+ mThread->Shutdown();
+ }
+}
+
+NS_IMETHODIMP
+nsWifiMonitor::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(topic, "xpcom-shutdown")) {
+ // Make sure any wifi-polling stops.
+ LOG(("nsWifiMonitor received shutdown"));
+ Close();
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ // Network connectivity has either been gained, lost, or changed (e.g.
+ // by changing Wifi network). Issue an immediate one-time scan.
+ // If we were polling, keep polling.
+ LOG(("nsWifiMonitor %p | mPollingId %" PRIu64
+ " | received: " NS_NETWORK_LINK_TOPIC " with status %s",
+ this, static_cast<uint64_t>(mPollingId),
+ NS_ConvertUTF16toUTF8(data).get()));
+ DispatchScanToBackgroundThread(0);
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TYPE_TOPIC)) {
+ // Network type has changed (e.g. from wifi to mobile). When on some
+ // network types, we poll wifi. This event does not indicate that a
+ // new scan would be beneficial right now, so we only issue one if
+ // we need to begin polling.
+ // Use IDs to make sure only one task is polling at a time.
+ LOG(("nsWifiMonitor %p | mPollingId %" PRIu64
+ " | received: " NS_NETWORK_LINK_TYPE_TOPIC " with status %s",
+ this, static_cast<uint64_t>(mPollingId),
+ NS_ConvertUTF16toUTF8(data).get()));
+
+ bool wasPolling = ShouldPoll();
+ MOZ_ASSERT(wasPolling || mPollingId == 0);
+
+ mShouldPollForCurrentNetwork = ShouldPollForNetworkType(data);
+ if (!wasPolling && ShouldPoll()) {
+ // We weren't polling, so start now.
+ mPollingId = NextPollingIndex();
+ DispatchScanToBackgroundThread(mPollingId);
+ } else if (!ShouldPoll()) {
+ // Stop polling if we were.
+ mPollingId = 0;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWifiMonitor::StartWatching(nsIWifiListener* aListener,
+ bool aForcePolling) {
+ LOG(("nsWifiMonitor::StartWatching %p | listener %p | mPollingId %" PRIu64
+ " | aForcePolling %s",
+ this, aListener, static_cast<uint64_t>(mPollingId),
+ aForcePolling ? "true" : "false"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ mListeners.AppendElement(WifiListenerHolder(aListener, aForcePolling));
+
+ // Run a new scan to update the new listener. If we were polling then
+ // stop that polling and start a new polling interval now.
+ MOZ_ASSERT(mPollingId == 0 || ShouldPoll());
+ if (aForcePolling) {
+ ++mNumPollingListeners;
+ }
+ if (ShouldPoll()) {
+ mPollingId = NextPollingIndex();
+ }
+ return DispatchScanToBackgroundThread(mPollingId);
+}
+
+NS_IMETHODIMP nsWifiMonitor::StopWatching(nsIWifiListener* aListener) {
+ LOG(("nsWifiMonitor::StopWatching %p | listener %p | mPollingId %" PRIu64,
+ this, aListener, static_cast<uint64_t>(mPollingId)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ auto idx = mListeners.IndexOf(
+ WifiListenerHolder(aListener), 0,
+ [](const WifiListenerHolder& elt, const WifiListenerHolder& toRemove) {
+ return toRemove.mListener == elt.mListener ? 0 : 1;
+ });
+
+ if (idx == nsTArray<WifiListenerHolder>::NoIndex) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mListeners[idx].mShouldPoll) {
+ --mNumPollingListeners;
+ }
+
+ mListeners.RemoveElementAt(idx);
+
+ if (!ShouldPoll()) {
+ // Stop polling (if we were).
+ LOG(("nsWifiMonitor::StopWatching clearing polling ID"));
+ mPollingId = 0;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsWifiMonitor::DispatchScanToBackgroundThread(uint64_t aPollingId,
+ uint32_t aWaitMs) {
+ RefPtr<Runnable> runnable = NewRunnableMethod<uint64_t>(
+ "WifiScannerThread", this, &nsWifiMonitor::Scan, aPollingId);
+
+ if (!mThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+#ifndef XP_MACOSX
+ nsIThreadManager::ThreadCreationOptions options = {};
+#else
+ // If this ASSERT fails, we've increased our default stack size and
+ // may no longer need to special-case the stack size on macOS.
+ static_assert(kMacOSWifiMonitorStackSize >
+ nsIThreadManager::DEFAULT_STACK_SIZE);
+
+ // Mac needs a stack size larger than the default for CoreWLAN.
+ nsIThreadManager::ThreadCreationOptions options = {
+ .stackSize = kMacOSWifiMonitorStackSize};
+#endif
+
+ nsresult rv = NS_NewNamedThread("Wifi Monitor", getter_AddRefs(mThread),
+ nullptr, options);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aWaitMs) {
+ return mThread->DelayedDispatch(runnable.forget(), aWaitMs);
+ }
+
+ return mThread->Dispatch(runnable.forget());
+}
+
+bool nsWifiMonitor::IsBackgroundThread() {
+ return NS_GetCurrentThread() == mThread;
+}
+
+void nsWifiMonitor::Scan(uint64_t aPollingId) {
+ MOZ_ASSERT(IsBackgroundThread());
+ LOG(("nsWifiMonitor::Scan aPollingId: %" PRIu64 " | mPollingId: %" PRIu64,
+ aPollingId, static_cast<uint64_t>(mPollingId)));
+
+ // If we are using a stale polling ID then stop. If this request to
+ // Scan is not for polling (aPollingId is 0) then always allow it.
+ if (aPollingId && mPollingId != aPollingId) {
+ LOG(("nsWifiMonitor::Scan stopping polling"));
+ return;
+ }
+
+ LOG(("nsWifiMonitor::Scan starting DoScan with id: %" PRIu64, aPollingId));
+ nsresult rv = DoScan();
+ LOG(("nsWifiMonitor::Scan DoScan complete | rv = %d",
+ static_cast<uint32_t>(rv)));
+
+ if (NS_FAILED(rv)) {
+ auto* mainThread = GetMainThreadSerialEventTarget();
+ if (!mainThread) {
+ LOG(("nsWifiMonitor::Scan cannot find main thread"));
+ return;
+ }
+
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "WaitForPassErrorToWifiListeners"_ns, mainThread,
+ NewRunnableMethod<nsresult>("PassErrorToWifiListeners", this,
+ &nsWifiMonitor::PassErrorToWifiListeners,
+ rv));
+ }
+
+ // If we are polling then we re-issue Scan after a delay.
+ // We re-check the polling IDs since mPollingId may have changed.
+ if (aPollingId && aPollingId == mPollingId) {
+ uint32_t periodMs = StaticPrefs::network_wifi_scanning_period();
+ if (periodMs) {
+ LOG(("nsWifiMonitor::Scan requesting future scan with id: %" PRIu64
+ " | periodMs: %u",
+ aPollingId, periodMs));
+ DispatchScanToBackgroundThread(aPollingId, periodMs);
+ } else {
+ // Polling for wifi-scans is disabled.
+ mPollingId = 0;
+ }
+ }
+
+ LOG(("nsWifiMonitor::Scan complete"));
+}
+
+nsresult nsWifiMonitor::DoScan() {
+ MOZ_ASSERT(IsBackgroundThread());
+
+ if (!mWifiScanner) {
+ LOG(("Constructing WifiScanner"));
+ mWifiScanner = MakeUnique<mozilla::WifiScannerImpl>();
+ }
+ MOZ_ASSERT(mWifiScanner);
+
+ LOG(("Scanning Wifi for access points"));
+ nsTArray<RefPtr<nsIWifiAccessPoint>> accessPoints;
+ nsresult rv = mWifiScanner->GetAccessPointsFromWLAN(accessPoints);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ LOG(("Sorting wifi access points"));
+ accessPoints.Sort([](const RefPtr<nsIWifiAccessPoint>& ia,
+ const RefPtr<nsIWifiAccessPoint>& ib) {
+ const auto& a = static_cast<const nsWifiAccessPoint&>(*ia);
+ const auto& b = static_cast<const nsWifiAccessPoint&>(*ib);
+ return a.Compare(b);
+ });
+
+ // Sorted compare to see if access point list has changed.
+ LOG(("Checking for new access points"));
+ bool accessPointsChanged =
+ accessPoints.Length() != mLastAccessPoints.Length();
+ if (!accessPointsChanged) {
+ auto itAp = accessPoints.begin();
+ auto itLastAp = mLastAccessPoints.begin();
+ while (itAp != accessPoints.end()) {
+ const auto& a = static_cast<const nsWifiAccessPoint&>(**itAp);
+ const auto& b = static_cast<const nsWifiAccessPoint&>(**itLastAp);
+ if (a != b) {
+ accessPointsChanged = true;
+ break;
+ }
+ ++itAp;
+ ++itLastAp;
+ }
+ }
+
+ mLastAccessPoints = std::move(accessPoints);
+
+ LOG(("Sending Wifi access points to the main thread"));
+ auto* mainThread = GetMainThreadSerialEventTarget();
+ if (!mainThread) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_DispatchAndSpinEventLoopUntilComplete(
+ "WaitForCallWifiListeners"_ns, mainThread,
+ NewRunnableMethod<const nsTArray<RefPtr<nsIWifiAccessPoint>>&&, bool>(
+ "CallWifiListeners", this, &nsWifiMonitor::CallWifiListeners,
+ mLastAccessPoints.Clone(), accessPointsChanged));
+}
+
+nsresult nsWifiMonitor::CallWifiListeners(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>&& aAccessPoints,
+ bool aAccessPointsChanged) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("Sending wifi access points to the listeners"));
+ for (auto& listener : mListeners) {
+ if (!listener.mHasSentData || aAccessPointsChanged) {
+ listener.mHasSentData = true;
+ listener.mListener->OnChange(aAccessPoints);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsWifiMonitor::PassErrorToWifiListeners(nsresult rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("About to send error to the wifi listeners"));
+ for (const auto& listener : mListeners) {
+ listener.mListener->OnError(rv);
+ }
+ return NS_OK;
+}
diff --git a/netwerk/wifi/nsWifiMonitor.h b/netwerk/wifi/nsWifiMonitor.h
new file mode 100644
index 0000000000..5a37cc75be
--- /dev/null
+++ b/netwerk/wifi/nsWifiMonitor.h
@@ -0,0 +1,123 @@
+/* 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/. */
+
+#ifndef __nsWifiMonitor__
+#define __nsWifiMonitor__
+
+#include "nsIWifiMonitor.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "nsIThread.h"
+#include "nsIRunnable.h"
+#include "nsCOMArray.h"
+#include "nsIWifiListener.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/Logging.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Monitor.h"
+#include "WifiScanner.h"
+
+namespace mozilla {
+class TestWifiMonitor;
+}
+
+extern mozilla::LazyLogModule gWifiMonitorLog;
+
+class nsWifiAccessPoint;
+
+// Period between scans when on mobile network.
+#define WIFI_SCAN_INTERVAL_MS_PREF "network.wifi.scanning_period"
+
+#ifdef XP_MACOSX
+// Use a larger stack size for the wifi monitor thread of macOS, to
+// accommodate Core WLAN making large stack allocations.
+# define kMacOSWifiMonitorStackSize (512 * 1024)
+#endif
+
+struct WifiListenerHolder {
+ RefPtr<nsIWifiListener> mListener;
+ bool mShouldPoll;
+ bool mHasSentData = false;
+
+ explicit WifiListenerHolder(nsIWifiListener* aListener,
+ bool aShouldPoll = false)
+ : mListener(aListener), mShouldPoll(aShouldPoll) {}
+};
+
+class nsWifiMonitor final : public nsIWifiMonitor, public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWIFIMONITOR
+ NS_DECL_NSIOBSERVER
+
+ explicit nsWifiMonitor(
+ mozilla::UniquePtr<mozilla::WifiScanner>&& aScanner = nullptr);
+
+ private:
+ friend class mozilla::TestWifiMonitor;
+
+ ~nsWifiMonitor();
+
+ nsresult DispatchScanToBackgroundThread(uint64_t aPollingId = 0,
+ uint32_t aWaitMs = 0);
+
+ void Scan(uint64_t aPollingId);
+ nsresult DoScan();
+
+ nsresult CallWifiListeners(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>&& aAccessPoints,
+ bool aAccessPointsChanged);
+
+ nsresult PassErrorToWifiListeners(nsresult rv);
+
+ void Close();
+
+ bool IsBackgroundThread();
+
+ bool ShouldPoll() {
+ MOZ_ASSERT(!IsBackgroundThread());
+ return (mShouldPollForCurrentNetwork && !mListeners.IsEmpty()) ||
+ mNumPollingListeners > 0;
+ };
+
+#ifdef ENABLE_TESTS
+ // Test-only function that confirms we "should" be polling. May be wrong
+ // if somehow the polling tasks are not set to run on the background
+ // thread.
+ bool IsPolling() { return mThread && mPollingId; }
+#endif
+
+ // Main thread only.
+ nsCOMPtr<nsIThread> mThread;
+
+ // Main thread only.
+ nsTArray<WifiListenerHolder> mListeners;
+
+ // Background thread only.
+ mozilla::UniquePtr<mozilla::WifiScanner> mWifiScanner;
+
+ // Background thread only. Sorted.
+ nsTArray<RefPtr<nsIWifiAccessPoint>> mLastAccessPoints;
+
+ // Wifi-scanning requests may poll, meaning they will run repeatedly on
+ // a scheduled time period. If this value is 0 then polling is not running,
+ // otherwise, it indicates the "ID" of the polling that is running. if some
+ // other polling (with different ID) is running, it will stop, not iterate.
+ mozilla::Atomic<uint64_t> mPollingId;
+
+ // Number of current listeners that requested that the wifi scan poll
+ // periodically.
+ // Main thread only.
+ uint32_t mNumPollingListeners = 0;
+
+ // True if the current network type is one that requires polling
+ // (i.e. a "mobile" network type).
+ // Main thread only.
+ bool mShouldPollForCurrentNetwork = false;
+};
+
+#endif
diff --git a/netwerk/wifi/solaris/SolarisWifiScanner.cpp b/netwerk/wifi/solaris/SolarisWifiScanner.cpp
new file mode 100644
index 0000000000..6f30a81197
--- /dev/null
+++ b/netwerk/wifi/solaris/SolarisWifiScanner.cpp
@@ -0,0 +1,121 @@
+/* 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 "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+
+#include "nsCRT.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+
+#include "SolarisWifiScanner.h"
+
+#include <glib.h>
+
+#define DLADM_STRSIZE 256
+#define DLADM_SECTIONS 3
+
+using namespace mozilla;
+
+struct val_strength_t {
+ const char* strength_name;
+ int signal_value;
+};
+
+static val_strength_t strength_vals[] = {{"very weak", -112},
+ {"weak", -88},
+ {"good", -68},
+ {"very good", -40},
+ {"excellent", -16}};
+
+static nsWifiAccessPoint* do_parse_str(char* bssid_str, char* essid_str,
+ char* strength) {
+ unsigned char mac_as_int[6] = {0};
+ sscanf(bssid_str, "%x:%x:%x:%x:%x:%x", &mac_as_int[0], &mac_as_int[1],
+ &mac_as_int[2], &mac_as_int[3], &mac_as_int[4], &mac_as_int[5]);
+
+ int signal = 0;
+ uint32_t strength_vals_count = sizeof(strength_vals) / sizeof(val_strength_t);
+ for (uint32_t i = 0; i < strength_vals_count; i++) {
+ if (!nsCRT::strncasecmp(strength, strength_vals[i].strength_name,
+ DLADM_STRSIZE)) {
+ signal = strength_vals[i].signal_value;
+ break;
+ }
+ }
+
+ nsWifiAccessPoint* ap = new nsWifiAccessPoint();
+ if (ap) {
+ ap->setMac(mac_as_int);
+ ap->setSignal(signal);
+ size_t len = essid_str ? strnlen(essid_str, DLADM_STRSIZE) : 0;
+ ap->setSSID(essid_str, len);
+ }
+ return ap;
+}
+
+nsresult WifiScannerImpl::GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints) {
+ GError* err = nullptr;
+ char* sout = nullptr;
+ char* serr = nullptr;
+ int exit_status = 0;
+ char* dladm_args[] = {
+ "/usr/bin/pfexec", "/usr/sbin/dladm", "scan-wifi", "-p", "-o",
+ "BSSID,ESSID,STRENGTH"};
+
+ gboolean rv = g_spawn_sync("/", dladm_args, nullptr, (GSpawnFlags)0, nullptr,
+ nullptr, &sout, &serr, &exit_status, &err);
+ if (rv && !exit_status) {
+ char wlan[DLADM_SECTIONS][DLADM_STRSIZE + 1];
+ uint32_t section = 0;
+ uint32_t sout_scan = 0;
+ uint32_t wlan_put = 0;
+ bool escape = false;
+ nsWifiAccessPoint* ap;
+ char sout_char;
+ do {
+ sout_char = sout[sout_scan++];
+ if (escape) {
+ escape = false;
+ if (sout_char != '\0') {
+ wlan[section][wlan_put++] = sout_char;
+ continue;
+ }
+ }
+
+ if (sout_char == '\\') {
+ escape = true;
+ continue;
+ }
+
+ if (sout_char == ':') {
+ wlan[section][wlan_put] = '\0';
+ section++;
+ wlan_put = 0;
+ continue;
+ }
+
+ if ((sout_char == '\0') || (sout_char == '\n')) {
+ wlan[section][wlan_put] = '\0';
+ if (section == DLADM_SECTIONS - 1) {
+ ap = do_parse_str(wlan[0], wlan[1], wlan[2]);
+ if (ap) {
+ accessPoints.AppendElement(ap);
+ }
+ }
+ section = 0;
+ wlan_put = 0;
+ continue;
+ }
+
+ wlan[section][wlan_put++] = sout_char;
+
+ } while ((wlan_put <= DLADM_STRSIZE) && (section < DLADM_SECTIONS) &&
+ (sout_char != '\0'));
+ }
+
+ g_free(sout);
+ g_free(serr);
+}
diff --git a/netwerk/wifi/solaris/SolarisWifiScanner.h b/netwerk/wifi/solaris/SolarisWifiScanner.h
new file mode 100644
index 0000000000..1bd5bf0eb5
--- /dev/null
+++ b/netwerk/wifi/solaris/SolarisWifiScanner.h
@@ -0,0 +1,31 @@
+/* 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/. */
+
+#pragma once
+
+#include "WifiScanner.h"
+
+class nsIWifiAccessPoint;
+
+namespace mozilla {
+
+class WifiScannerImpl final : public WifiScanner {
+ public:
+ WifiScannerImpl();
+ ~WifiScannerImpl();
+
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ nsresult GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints);
+};
+
+} // namespace mozilla
diff --git a/netwerk/wifi/win/WinWifiScanner.cpp b/netwerk/wifi/win/WinWifiScanner.cpp
new file mode 100644
index 0000000000..b24fb4a2f1
--- /dev/null
+++ b/netwerk/wifi/win/WinWifiScanner.cpp
@@ -0,0 +1,170 @@
+/* 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 "nsWifiAccessPoint.h"
+#include "WinWifiScanner.h"
+
+#define DOT11_BSS_TYPE_UNUSED static_cast<DOT11_BSS_TYPE>(0)
+
+namespace mozilla {
+
+class InterfaceScanCallbackData {
+ public:
+ explicit InterfaceScanCallbackData(uint32_t numInterfaces)
+ : mCurrentlyScanningInterfaces(numInterfaces) {
+ mAllInterfacesDoneScanningEvent =
+ ::CreateEventW(nullptr, // null security
+ TRUE, // manual reset event
+ FALSE, // initially nonsignaled
+ nullptr); // not named
+ MOZ_ASSERT(NULL != mAllInterfacesDoneScanningEvent);
+ }
+
+ ~InterfaceScanCallbackData() {
+ ::CloseHandle(mAllInterfacesDoneScanningEvent);
+ }
+
+ void OnInterfaceScanComplete() {
+ uint32_t val = ::InterlockedDecrement(&mCurrentlyScanningInterfaces);
+ if (!val) {
+ ::SetEvent(mAllInterfacesDoneScanningEvent);
+ }
+ }
+
+ void WaitForAllInterfacesToFinishScanning(uint32_t msToWait) {
+ ::WaitForSingleObject(mAllInterfacesDoneScanningEvent, msToWait);
+ }
+
+ private:
+ volatile uint32_t mCurrentlyScanningInterfaces;
+ HANDLE mAllInterfacesDoneScanningEvent;
+};
+
+static void WINAPI OnScanComplete(PWLAN_NOTIFICATION_DATA data, PVOID context) {
+ if (WLAN_NOTIFICATION_SOURCE_ACM != data->NotificationSource) {
+ return;
+ }
+
+ if (wlan_notification_acm_scan_complete != data->NotificationCode &&
+ wlan_notification_acm_scan_fail != data->NotificationCode) {
+ return;
+ }
+
+ InterfaceScanCallbackData* cbData =
+ reinterpret_cast<InterfaceScanCallbackData*>(context);
+ cbData->OnInterfaceScanComplete();
+}
+
+WifiScannerImpl::WifiScannerImpl() {
+ // NOTE: We assume that, if we were unable to load the WLAN library when
+ // we initially tried, we will not be able to load it in the future.
+ // Technically, on Windows XP SP2, a user could install the redistributable
+ // and make our assumption incorrect. We opt to avoid making a bunch of
+ // spurious LoadLibrary calls in the common case rather than load the
+ // WLAN API in the edge case.
+ mWlanLibrary.reset(WinWLANLibrary::Load());
+ if (!mWlanLibrary) {
+ NS_WARNING("Could not initialize Windows Wi-Fi scanner");
+ }
+}
+
+WifiScannerImpl::~WifiScannerImpl() {}
+
+nsresult WifiScannerImpl::GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints) {
+ accessPoints.Clear();
+
+ // NOTE: We do not try to load the WLAN library if we previously failed
+ // to load it. See the note in WifiScannerImpl constructor
+ if (!mWlanLibrary) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Get the list of interfaces. WlanEnumInterfaces allocates interface_list.
+ WLAN_INTERFACE_INFO_LIST* interface_list = nullptr;
+ if (ERROR_SUCCESS !=
+ (*mWlanLibrary->GetWlanEnumInterfacesPtr())(mWlanLibrary->GetWLANHandle(),
+ nullptr, &interface_list)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // This ensures we call WlanFreeMemory on interface_list
+ ScopedWLANObject scopedInterfaceList(*mWlanLibrary, interface_list);
+
+ if (!interface_list->dwNumberOfItems) {
+ return NS_OK;
+ }
+
+ InterfaceScanCallbackData cbData(interface_list->dwNumberOfItems);
+
+ DWORD wlanNotifySource;
+ if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanRegisterNotificationPtr())(
+ mWlanLibrary->GetWLANHandle(),
+ WLAN_NOTIFICATION_SOURCE_ACM, TRUE,
+ (WLAN_NOTIFICATION_CALLBACK)OnScanComplete, &cbData,
+ NULL, &wlanNotifySource)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Go through the list of interfaces and call `WlanScan` on each
+ for (unsigned int i = 0; i < interface_list->dwNumberOfItems; ++i) {
+ if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanScanPtr())(
+ mWlanLibrary->GetWLANHandle(),
+ &interface_list->InterfaceInfo[i].InterfaceGuid,
+ NULL, NULL, NULL)) {
+ cbData.OnInterfaceScanComplete();
+ }
+ }
+
+ // From the MSDN documentation:
+ // "Wireless network drivers that meet Windows logo requirements are
+ // required to complete a WlanScan function request in 4 seconds"
+ cbData.WaitForAllInterfacesToFinishScanning(5000);
+
+ // Unregister for the notifications. The documentation mentions that,
+ // if a callback is currently running, this will wait for the callback
+ // to complete.
+ (*mWlanLibrary->GetWlanRegisterNotificationPtr())(
+ mWlanLibrary->GetWLANHandle(), WLAN_NOTIFICATION_SOURCE_NONE, TRUE, NULL,
+ NULL, NULL, &wlanNotifySource);
+
+ // Go through the list of interfaces and get the data for each.
+ for (uint32_t i = 0; i < interface_list->dwNumberOfItems; ++i) {
+ WLAN_BSS_LIST* bss_list;
+ if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanGetNetworkBssListPtr())(
+ mWlanLibrary->GetWLANHandle(),
+ &interface_list->InterfaceInfo[i].InterfaceGuid,
+ nullptr, // Use all SSIDs.
+ DOT11_BSS_TYPE_UNUSED,
+ false, // bSecurityEnabled -
+ // unused
+ nullptr, // reserved
+ &bss_list)) {
+ continue;
+ }
+
+ // This ensures we call WlanFreeMemory on bss_list
+ ScopedWLANObject scopedBssList(*mWlanLibrary, bss_list);
+
+ // Store each discovered access point in our outparam
+ for (int j = 0; j < static_cast<int>(bss_list->dwNumberOfItems); ++j) {
+ nsWifiAccessPoint* ap = new nsWifiAccessPoint();
+ if (!ap) {
+ continue;
+ }
+
+ const WLAN_BSS_ENTRY bss_entry = bss_list->wlanBssEntries[j];
+ ap->setMac(bss_entry.dot11Bssid);
+ ap->setSignal(bss_entry.lRssi);
+ ap->setSSID(reinterpret_cast<char const*>(bss_entry.dot11Ssid.ucSSID),
+ bss_entry.dot11Ssid.uSSIDLength);
+
+ accessPoints.AppendElement(ap);
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/netwerk/wifi/win/WinWifiScanner.h b/netwerk/wifi/win/WinWifiScanner.h
new file mode 100644
index 0000000000..ce36c1156d
--- /dev/null
+++ b/netwerk/wifi/win/WinWifiScanner.h
@@ -0,0 +1,36 @@
+/* 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/. */
+
+#pragma once
+
+#include "mozilla/UniquePtr.h"
+#include "WlanLibrary.h"
+#include "WifiScanner.h"
+
+class nsIWifiAccessPoint;
+
+namespace mozilla {
+
+class WifiScannerImpl final : public WifiScanner {
+ public:
+ WifiScannerImpl();
+ ~WifiScannerImpl();
+
+ /**
+ * GetAccessPointsFromWLAN
+ *
+ * Scans the available wireless interfaces for nearby access points and
+ * populates the supplied collection with them
+ *
+ * @param accessPoints The collection to populate with available APs
+ * @return NS_OK on success, failure codes on failure
+ */
+ nsresult GetAccessPointsFromWLAN(
+ nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints);
+
+ private:
+ mozilla::UniquePtr<WinWLANLibrary> mWlanLibrary;
+};
+
+} // namespace mozilla
diff --git a/netwerk/wifi/win/WlanLibrary.cpp b/netwerk/wifi/win/WlanLibrary.cpp
new file mode 100644
index 0000000000..0fd36be660
--- /dev/null
+++ b/netwerk/wifi/win/WlanLibrary.cpp
@@ -0,0 +1,111 @@
+/* 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 "WlanLibrary.h"
+
+// Moz headers (alphabetical)
+
+WinWLANLibrary* WinWLANLibrary::Load() {
+ WinWLANLibrary* ret = new WinWLANLibrary();
+ if (!ret) {
+ return nullptr;
+ }
+
+ if (!ret->Initialize()) {
+ delete ret;
+ return nullptr;
+ }
+
+ return ret;
+}
+
+HANDLE
+WinWLANLibrary::GetWLANHandle() const { return mWlanHandle; }
+
+decltype(::WlanEnumInterfaces)* WinWLANLibrary::GetWlanEnumInterfacesPtr()
+ const {
+ return mWlanEnumInterfacesPtr;
+}
+
+decltype(::WlanGetNetworkBssList)* WinWLANLibrary::GetWlanGetNetworkBssListPtr()
+ const {
+ return mWlanGetNetworkBssListPtr;
+}
+
+decltype(::WlanFreeMemory)* WinWLANLibrary::GetWlanFreeMemoryPtr() const {
+ return mWlanFreeMemoryPtr;
+}
+
+decltype(::WlanCloseHandle)* WinWLANLibrary::GetWlanCloseHandlePtr() const {
+ return mWlanCloseHandlePtr;
+}
+
+decltype(::WlanOpenHandle)* WinWLANLibrary::GetWlanOpenHandlePtr() const {
+ return mWlanOpenHandlePtr;
+}
+
+decltype(::WlanRegisterNotification)*
+WinWLANLibrary::GetWlanRegisterNotificationPtr() const {
+ return mWlanRegisterNotificationPtr;
+}
+
+decltype(::WlanScan)* WinWLANLibrary::GetWlanScanPtr() const {
+ return mWlanScanPtr;
+}
+
+bool WinWLANLibrary::Initialize() {
+ mWlanLibrary = LoadLibraryW(L"Wlanapi.dll");
+ if (!mWlanLibrary) {
+ return false;
+ }
+
+ mWlanOpenHandlePtr = (decltype(::WlanOpenHandle)*)GetProcAddress(
+ mWlanLibrary, "WlanOpenHandle");
+ mWlanEnumInterfacesPtr = (decltype(::WlanEnumInterfaces)*)GetProcAddress(
+ mWlanLibrary, "WlanEnumInterfaces");
+ mWlanRegisterNotificationPtr =
+ (decltype(::WlanRegisterNotification)*)GetProcAddress(
+ mWlanLibrary, "WlanRegisterNotification");
+ mWlanScanPtr =
+ (decltype(::WlanScan)*)GetProcAddress(mWlanLibrary, "WlanScan");
+
+ mWlanFreeMemoryPtr = (decltype(::WlanFreeMemory)*)GetProcAddress(
+ mWlanLibrary, "WlanFreeMemory");
+ mWlanCloseHandlePtr = (decltype(::WlanCloseHandle)*)GetProcAddress(
+ mWlanLibrary, "WlanCloseHandle");
+ mWlanGetNetworkBssListPtr =
+ (decltype(::WlanGetNetworkBssList)*)GetProcAddress(
+ mWlanLibrary, "WlanGetNetworkBssList");
+
+ if (!mWlanOpenHandlePtr || !mWlanEnumInterfacesPtr ||
+ !mWlanRegisterNotificationPtr || !mWlanGetNetworkBssListPtr ||
+ !mWlanScanPtr || !mWlanFreeMemoryPtr || !mWlanCloseHandlePtr) {
+ return false;
+ }
+
+ // Get the handle to the WLAN API.
+ DWORD negotiated_version;
+ // We could be executing on either Windows XP or Windows Vista, so use the
+ // lower version of the client WLAN API. It seems that the negotiated version
+ // is the Vista version irrespective of what we pass!
+ static const int kXpWlanClientVersion = 1;
+ if (ERROR_SUCCESS != (*mWlanOpenHandlePtr)(kXpWlanClientVersion, nullptr,
+ &negotiated_version,
+ &mWlanHandle)) {
+ return false;
+ }
+
+ return true;
+}
+
+WinWLANLibrary::~WinWLANLibrary() {
+ if (mWlanLibrary) {
+ if (mWlanHandle) {
+ (*mWlanCloseHandlePtr)(mWlanLibrary, mWlanHandle);
+ mWlanHandle = nullptr;
+ }
+ ::FreeLibrary(mWlanLibrary);
+ mWlanLibrary = nullptr;
+ }
+}
diff --git a/netwerk/wifi/win/WlanLibrary.h b/netwerk/wifi/win/WlanLibrary.h
new file mode 100644
index 0000000000..8d996f881d
--- /dev/null
+++ b/netwerk/wifi/win/WlanLibrary.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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/. */
+
+#pragma once
+
+// Moz headers (alphabetical)
+
+// System headers (alphabetical)
+#include <windows.h> // HINSTANCE, HANDLE
+#include <wlanapi.h> // Wlan* functions
+
+class WinWLANLibrary {
+ public:
+ static WinWLANLibrary* Load();
+ ~WinWLANLibrary();
+
+ HANDLE GetWLANHandle() const;
+ decltype(::WlanEnumInterfaces)* GetWlanEnumInterfacesPtr() const;
+ decltype(::WlanGetNetworkBssList)* GetWlanGetNetworkBssListPtr() const;
+ decltype(::WlanFreeMemory)* GetWlanFreeMemoryPtr() const;
+ decltype(::WlanCloseHandle)* GetWlanCloseHandlePtr() const;
+ decltype(::WlanOpenHandle)* GetWlanOpenHandlePtr() const;
+ decltype(::WlanRegisterNotification)* GetWlanRegisterNotificationPtr() const;
+ decltype(::WlanScan)* GetWlanScanPtr() const;
+
+ private:
+ WinWLANLibrary() = default;
+ bool Initialize();
+
+ HMODULE mWlanLibrary = nullptr;
+ HANDLE mWlanHandle = nullptr;
+ decltype(::WlanEnumInterfaces)* mWlanEnumInterfacesPtr = nullptr;
+ decltype(::WlanGetNetworkBssList)* mWlanGetNetworkBssListPtr = nullptr;
+ decltype(::WlanFreeMemory)* mWlanFreeMemoryPtr = nullptr;
+ decltype(::WlanCloseHandle)* mWlanCloseHandlePtr = nullptr;
+ decltype(::WlanOpenHandle)* mWlanOpenHandlePtr = nullptr;
+ decltype(::WlanRegisterNotification)* mWlanRegisterNotificationPtr = nullptr;
+ decltype(::WlanScan)* mWlanScanPtr = nullptr;
+};
+
+class ScopedWLANObject {
+ public:
+ ScopedWLANObject(const WinWLANLibrary& library, void* object)
+ : mLibrary(library), mObject(object) {}
+
+ ~ScopedWLANObject() { (*(mLibrary.GetWlanFreeMemoryPtr()))(mObject); }
+
+ private:
+ const WinWLANLibrary& mLibrary;
+ void* mObject;
+};