summaryrefslogtreecommitdiffstats
path: root/netwerk/wifi/nsWifiMonitor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/wifi/nsWifiMonitor.cpp')
-rw-r--r--netwerk/wifi/nsWifiMonitor.cpp400
1 files changed, 400 insertions, 0 deletions
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;
+}