diff options
Diffstat (limited to 'netwerk/wifi')
24 files changed, 2431 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..43a6cc49dc --- /dev/null +++ b/netwerk/wifi/dbus/DbusWifiScanner.cpp @@ -0,0 +1,161 @@ +/* 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 <gio/gio.h> +#include "DbusWifiScanner.h" +#include "nsWifiAccessPoint.h" +#include "mozilla/GUniquePtr.h" +#include "mozilla/RefPtr.h" +#include "mozilla/GRefPtr.h" + +namespace mozilla { + +WifiScannerImpl::WifiScannerImpl() { MOZ_COUNT_CTOR(WifiScannerImpl); } + +WifiScannerImpl::~WifiScannerImpl() { MOZ_COUNT_DTOR(WifiScannerImpl); } + +nsresult WifiScannerImpl::GetAccessPointsFromWLAN( + AccessPointArray& aAccessPoints) { + RefPtr<GDBusProxy> proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, + "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", + "org.freedesktop.NetworkManager", nullptr, nullptr)); + if (!proxy) { + return NS_ERROR_FAILURE; + } + + RefPtr<GVariant> result = + dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "Devices")); + if (!result || + !g_variant_is_of_type(result, G_VARIANT_TYPE_OBJECT_PATH_ARRAY)) { + return NS_ERROR_FAILURE; + } + + gsize num = g_variant_n_children(result); + for (gsize i = 0; i < num; i++) { + const char* devicePath = + g_variant_get_string(g_variant_get_child_value(result, i), nullptr); + if (!devicePath || !AddDevice(devicePath, aAccessPoints)) { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +bool WifiScannerImpl::AddDevice(const char* aDevicePath, + AccessPointArray& aAccessPoints) { + RefPtr<GDBusProxy> proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, + "org.freedesktop.NetworkManager", aDevicePath, + "org.freedesktop.NetworkManager.Device", nullptr, nullptr)); + if (!proxy) { + return false; + } + + RefPtr<GVariant> deviceType = + dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "DeviceType")); + if (!deviceType || !g_variant_is_of_type(deviceType, G_VARIANT_TYPE_UINT32)) { + return false; + } + + // 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; + if (g_variant_get_uint32(deviceType) != NM_DEVICE_TYPE_WIFI) { + // Don't probe non-wifi devices + return true; + } + + proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, + "org.freedesktop.NetworkManager", aDevicePath, + "org.freedesktop.NetworkManager.Device.Wireless", nullptr, nullptr)); + if (!proxy) { + return false; + } + + RefPtr<GVariant> points = + dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "AccessPoints")); + if (!points || + !g_variant_is_of_type(points, G_VARIANT_TYPE_OBJECT_PATH_ARRAY)) { + return false; + } + + gsize num = g_variant_n_children(points); + for (gsize i = 0; i < num; i++) { + const char* ap = + g_variant_get_string(g_variant_get_child_value(points, i), nullptr); + if (!ap || !AddAPProperties(ap, aAccessPoints)) { + return false; + } + } + return true; +} + +bool WifiScannerImpl::AddAPProperties(const char* aApPath, + AccessPointArray& aAccessPoints) { + RefPtr<GDBusProxy> proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, + "org.freedesktop.NetworkManager", aApPath, + "org.freedesktop.NetworkManager.AccessPoint", nullptr, nullptr)); + if (!proxy) { + return false; + } + + RefPtr<nsWifiAccessPoint> ap = new nsWifiAccessPoint(); + + RefPtr<GVariant> ssid = + dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "Ssid")); + if (!ssid || !g_variant_is_of_type(ssid, G_VARIANT_TYPE_BYTESTRING)) { + return false; + } + const uint32_t MAX_SSID_LEN = 32; + gsize len = 0; + const char* ssidString = static_cast<const char*>( + g_variant_get_fixed_array(ssid, &len, sizeof(guint8))); + if (ssidString && len && len <= MAX_SSID_LEN) { + ap->setSSID(ssidString, len); + } + + RefPtr<GVariant> hwAddress = + dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "HwAddress")); + if (!hwAddress || !g_variant_is_of_type(hwAddress, G_VARIANT_TYPE_STRING)) { + return false; + } + GUniquePtr<gchar> address(g_variant_dup_string(hwAddress, nullptr)); + SetMac(address.get(), ap); + + RefPtr<GVariant> st = + dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "Strength")); + if (!st || !g_variant_is_of_type(st, G_VARIANT_TYPE_BYTE)) { + return false; + } + + uint8_t strength = g_variant_get_byte(st); // in % + int signal_strength = (strength / 2) - 100; // strength to dB + ap->setSignal(signal_strength); + + aAccessPoints.AppendElement(ap); + return true; +} + +bool WifiScannerImpl::SetMac(char* aHwAddress, nsWifiAccessPoint* aAp) { + // hwAddress format is XX:XX:XX:XX:XX:XX. Need to convert to XXXXXX format. + const uint32_t MAC_LEN = 6; + uint8_t macAddress[MAC_LEN]; + char* savedPtr = nullptr; + char* token = strtok_r(aHwAddress, ":", &savedPtr); + for (unsigned char& macAddres : macAddress) { + if (!token) { + return false; + } + macAddres = strtoul(token, nullptr, 16); + token = strtok_r(nullptr, ":", &savedPtr); + } + aAp->setMac(macAddress); + return true; +} + +} // namespace mozilla diff --git a/netwerk/wifi/dbus/DbusWifiScanner.h b/netwerk/wifi/dbus/DbusWifiScanner.h new file mode 100644 index 0000000000..70fccb0106 --- /dev/null +++ b/netwerk/wifi/dbus/DbusWifiScanner.h @@ -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/. */ + +#ifndef NSWIFIAPSCANNERDBUS_H_ +#define NSWIFIAPSCANNERDBUS_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: + bool AddDevice(const char* aDevicePath, AccessPointArray& aAccessPoints); + bool AddAPProperties(const char* aApPath, AccessPointArray& aAccessPoints); + bool SetMac(char* aHwAddress, nsWifiAccessPoint* aAp); +}; + +} // 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..f12c5c78ef --- /dev/null +++ b/netwerk/wifi/gtest/TestWifiMonitor.cpp @@ -0,0 +1,404 @@ +/* -*- 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::Cardinality; +using ::testing::Exactly; +using ::testing::MockFunction; +using ::testing::Sequence; + +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 update not received from wifi scanner thread. +static const uint32_t kWifiScanTestResultTimeoutMs = 100; +static const uint32_t kTestWifiScanIntervalMs = 10; + +// ID counter, used to make sure each call to GetAccessPointsFromWLAN +// returns "new" access points. +static int gCurrentId = 0; + +static uint32_t gNumScanResults = 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, kTestWifiScanIntervalMs); + } + + ~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()); + } + + protected: + bool WaitForScanResults() { + // Wait for kWifiScanTestResultTimeoutMs to allow async calls to complete. + bool timedout = false; + RefPtr<CancelableRunnable> timer = NS_NewCancelableRunnableFunction( + "WaitForScanResults Timeout", [&] { timedout = true; }); + NS_DelayedDispatchToCurrentThread(do_AddRef(timer), + kWifiScanTestResultTimeoutMs); + + mozilla::SpinEventLoopUntil("TestWifiMonitor::WaitForScanResults"_ns, + [&]() { return timedout; }); + + timer->Cancel(); + return true; + } + + void CreateObjects() { + mWifiMonitor = MakeRefPtr<nsWifiMonitor>(MakeUnique<MockWifiScanner>()); + EXPECT_TRUE(!mWifiMonitor->IsPolling()); + + // Start with ETHERNET network type to avoid always polling at test start. + mObs->NotifyObservers( + nullptr, NS_NETWORK_LINK_TYPE_TOPIC, + NS_ConvertUTF8toUTF16(NS_NETWORK_LINK_TYPE_ETHERNET).get()); + + mWifiListener = new MockWifiListener(); + LOGI(("monitor: %p | scanner: %p | listener: %p", mWifiMonitor.get(), + mWifiMonitor->mWifiScanner.get(), mWifiListener.get())); + } + + void DestroyObjects() { + ::testing::Mock::VerifyAndClearExpectations( + mWifiMonitor->mWifiScanner.get()); + ::testing::Mock::VerifyAndClearExpectations(mWifiListener.get()); + + // Manually disconnect observers so that the monitor can be destroyed. + // In the browser, this would 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; + } + + void StartWatching(bool aRequestPolling) { + LOGD(("StartWatching | aRequestPolling: %s | nScanResults: %u", + aRequestPolling ? "true" : "false", gNumScanResults)); + EXPECT_TRUE(NS_SUCCEEDED( + mWifiMonitor->StartWatching(mWifiListener, aRequestPolling))); + WaitForScanResults(); + } + + void NotifyOfNetworkEvent(const char* aTopic, const char16_t* aData) { + LOGD(("NotifyOfNetworkEvent: (%s, %s) | nScanResults: %u", aTopic, + NS_ConvertUTF16toUTF8(aData).get(), gNumScanResults)); + EXPECT_TRUE(NS_SUCCEEDED(mObs->NotifyObservers(nullptr, aTopic, aData))); + WaitForScanResults(); + } + + void StopWatching() { + LOGD(("StopWatching | nScanResults: %u", gNumScanResults)); + EXPECT_TRUE(NS_SUCCEEDED(mWifiMonitor->StopWatching(mWifiListener))); + WaitForScanResults(); + } + + struct MockCallSequences { + Sequence mGetAccessPointsSeq; + Sequence mOnChangeSeq; + Sequence mOnErrorSeq; + }; + + void AddMockObjectChecks(const Cardinality& aScanCardinality, + MockCallSequences& aSeqs) { + // Only add WillRepeatedly handler if scans is more than 0, to avoid a + // VERY LOUD gtest warning. + if (aScanCardinality.IsSaturatedByCallCount(0)) { + EXPECT_CALL( + *static_cast<MockWifiScanner*>(mWifiMonitor->mWifiScanner.get()), + GetAccessPointsFromWLAN) + .Times(aScanCardinality) + .InSequence(aSeqs.mGetAccessPointsSeq); + + EXPECT_CALL(*mWifiListener, OnChange) + .Times(aScanCardinality) + .InSequence(aSeqs.mOnChangeSeq); + } else { + EXPECT_CALL( + *static_cast<MockWifiScanner*>(mWifiMonitor->mWifiScanner.get()), + GetAccessPointsFromWLAN) + .Times(aScanCardinality) + .InSequence(aSeqs.mGetAccessPointsSeq) + .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; + }); + + EXPECT_CALL(*mWifiListener, OnChange) + .Times(aScanCardinality) + .InSequence(aSeqs.mOnChangeSeq) + .WillRepeatedly( + [](const nsTArray<RefPtr<nsIWifiAccessPoint>>& aAccessPoints) { + EXPECT_TRUE(NS_IsMainThread()); + EXPECT_EQ(aAccessPoints.Length(), 1u); + ++gNumScanResults; + return NS_OK; + }); + } + + EXPECT_CALL(*mWifiListener, OnError).Times(0).InSequence(aSeqs.mOnErrorSeq); + } + + void AddStartWatchingCheck(bool aShouldPoll, MockCallSequences& aSeqs) { + AddMockObjectChecks(aShouldPoll ? AtLeast(1) : Exactly(1), aSeqs); + } + + void AddNetworkEventCheck(const Cardinality& aScanCardinality, + MockCallSequences& aSeqs) { + AddMockObjectChecks(aScanCardinality, aSeqs); + } + + void AddStopWatchingCheck(bool aShouldPoll, MockCallSequences& aSeqs) { + // When polling, we may get stray scan + OnChange calls asynchronously + // before stopping. We may also get scan calls after stopping. + // We check that the calls actually stopped in ConfirmStoppedCheck. + AddMockObjectChecks(aShouldPoll ? AtLeast(0) : Exactly(0), aSeqs); + } + + void AddConfirmStoppedCheck(MockCallSequences& aSeqs) { + AddMockObjectChecks(Exactly(0), aSeqs); + } + + // A Checkpoint is just a mocked function taking an int. It will serve + // as a temporal barrier that requires all expectations before it to be + // satisfied and retired (meaning they won't be used in matches anymore). + class Checkpoint { + public: + void Check(uint32_t aId, MockCallSequences& aSeqs) { + EXPECT_CALL(mFn, Call(aId)) + .InSequence(aSeqs.mGetAccessPointsSeq, aSeqs.mOnChangeSeq, + aSeqs.mOnErrorSeq); + } + + void Reach(uint32_t aId) { mFn.Call(aId); } + + private: + MockFunction<void(uint32_t)> mFn; + }; + + // A single test is StartWatching, NotifyOfNetworkEvent, and StopWatching. + void RunSingleTest(bool aRequestPolling, bool aShouldPoll, + const Cardinality& aScanCardinality, const char* aTopic, + const char16_t* aData) { + LOGI(("RunSingleTest: <%s, %s> | requestPolling: %s | shouldPoll: %s", + aTopic, NS_ConvertUTF16toUTF8(aData).get(), + aRequestPolling ? "true" : "false", aShouldPoll ? "true" : "false")); + MOZ_ASSERT(aShouldPoll || !aRequestPolling); + + CreateObjects(); + + Checkpoint checkpoint; + { + // gmock expectations are asynchronous by default. Sequence objects + // are used here to require that expectations occur in the specified + // (partial) order. + MockCallSequences seqs; + + AddStartWatchingCheck(aShouldPoll, seqs); + checkpoint.Check(1, seqs); + + AddNetworkEventCheck(aScanCardinality, seqs); + checkpoint.Check(2, seqs); + + AddStopWatchingCheck(aShouldPoll, seqs); + checkpoint.Check(3, seqs); + + AddConfirmStoppedCheck(seqs); + } + + // Now run the test on the mock objects. + StartWatching(aRequestPolling); + checkpoint.Reach(1); + EXPECT_EQ(mWifiMonitor->IsPolling(), aRequestPolling); + + NotifyOfNetworkEvent(aTopic, aData); + checkpoint.Reach(2); + EXPECT_EQ(mWifiMonitor->IsPolling(), aShouldPoll); + + StopWatching(); + checkpoint.Reach(3); + EXPECT_TRUE(!mWifiMonitor->IsPolling()); + + // Wait for extraneous calls as a way to confirm it has stopped. + WaitForScanResults(); + + DestroyObjects(); + } + + void CheckMessages(bool aRequestPolling) { + // 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}; + + for (const auto& data : kLinkTopicDatas) { + RunSingleTest(aRequestPolling, aRequestPolling, + aRequestPolling ? AtLeast(2) : Exactly(1), + NS_NETWORK_LINK_TOPIC, NS_ConvertUTF8toUTF16(data).get()); + } + + // 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, true /* 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 = (aRequestPolling || data.mIsMobile); + RunSingleTest(aRequestPolling, shouldPoll, + shouldPoll ? AtLeast(2) : Exactly(0), + NS_NETWORK_LINK_TYPE_TOPIC, + NS_ConvertUTF8toUTF16(data.mLinkType).get()); + } + } + + 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..40f6f5b24d --- /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", # see bug 1833020 +] + +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..9fdccf6c49 --- /dev/null +++ b/netwerk/wifi/mac/MacWifiScanner.mm @@ -0,0 +1,109 @@ +/* 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..75a9384c09 --- /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_GTK3_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..5a7be15d16 --- /dev/null +++ b/netwerk/wifi/nsWifiMonitor.cpp @@ -0,0 +1,393 @@ +/* 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 "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 "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) { + auto linkTypeU8 = NS_ConvertUTF16toUTF8(aLinkType); + return linkTypeU8 == NS_NETWORK_LINK_TYPE_WIMAX || + linkTypeU8 == NS_NETWORK_LINK_TYPE_MOBILE || + linkTypeU8 == NS_NETWORK_LINK_TYPE_UNKNOWN; +} + +// Enum value version. +static bool ShouldPollForNetworkType(uint32_t aLinkType) { + return aLinkType == nsINetworkLinkService::LINK_TYPE_WIMAX || + aLinkType == nsINetworkLinkService::LINK_TYPE_MOBILE || + aLinkType == nsINetworkLinkService::LINK_TYPE_UNKNOWN; +} + +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)) { + rv = NS_DispatchToMainThread(NewRunnableMethod<nsresult>( + "PassErrorToWifiListeners", this, + &nsWifiMonitor::PassErrorToWifiListeners, rv)); + MOZ_ASSERT(NS_SUCCEEDED(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_DispatchToMainThread( + 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; +}; |