/* 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&& aScanner) : mWifiScanner(std::move(aScanner)) { LOG(("Creating nsWifiMonitor")); MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr 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 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 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(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(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(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(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::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 = NewRunnableMethod( "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(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(rv))); if (NS_FAILED(rv)) { rv = NS_DispatchToMainThread(NewRunnableMethod( "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(); } MOZ_ASSERT(mWifiScanner); LOG(("Scanning Wifi for access points")); nsTArray> accessPoints; nsresult rv = mWifiScanner->GetAccessPointsFromWLAN(accessPoints); if (NS_FAILED(rv)) { return rv; } LOG(("Sorting wifi access points")); accessPoints.Sort([](const RefPtr& ia, const RefPtr& ib) { const auto& a = static_cast(*ia); const auto& b = static_cast(*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(**itAp); const auto& b = static_cast(**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>, bool>( "CallWifiListeners", this, &nsWifiMonitor::CallWifiListeners, mLastAccessPoints.Clone(), accessPointsChanged)); } nsresult nsWifiMonitor::CallWifiListeners( const nsTArray>& 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; }