/* -*- 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 "WifiProxyService.h" #include "nsServiceManagerUtils.h" #include "mozilla/ModuleUtils.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/dom/ToJSValue.h" #include "nsXULAppAPI.h" #include "WifiUtils.h" #ifdef MOZ_TASK_TRACER #include "GeckoTaskTracer.h" using namespace mozilla::tasktracer; #endif #define NS_WIFIPROXYSERVICE_CID \ { 0xc6c9be7e, 0x744f, 0x4222, {0xb2, 0x03, 0xcd, 0x55, 0xdf, 0xc8, 0xbc, 0x12} } using namespace mozilla; using namespace mozilla::dom; namespace mozilla { // The singleton Wifi service, to be used on the main thread. static StaticRefPtr gWifiProxyService; // The singleton supplicant class, that can be used on any thread. static nsAutoPtr gWpaSupplicant; // Runnable used dispatch the WaitForEvent result on the main thread. class WifiEventDispatcher : public nsRunnable { public: WifiEventDispatcher(const nsAString& aEvent, const nsACString& aInterface) : mEvent(aEvent) , mInterface(aInterface) { MOZ_ASSERT(!NS_IsMainThread()); } NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); gWifiProxyService->DispatchWifiEvent(mEvent, mInterface); return NS_OK; } private: nsString mEvent; nsCString mInterface; }; // Runnable used to call WaitForEvent on the event thread. class EventRunnable : public nsRunnable { public: EventRunnable(const nsACString& aInterface) : mInterface(aInterface) { MOZ_ASSERT(NS_IsMainThread()); } NS_IMETHOD Run() { MOZ_ASSERT(!NS_IsMainThread()); nsAutoString event; gWpaSupplicant->WaitForEvent(event, mInterface); if (!event.IsEmpty()) { #ifdef MOZ_TASK_TRACER // Make wifi initialization events to be the source events of TaskTracer, // and originate the rest correlation tasks from here. AutoSourceEvent taskTracerEvent(SourceEventType::Wifi); AddLabel("%s %s", mInterface.get(), NS_ConvertUTF16toUTF8(event).get()); #endif nsCOMPtr runnable = new WifiEventDispatcher(event, mInterface); NS_DispatchToMainThread(runnable); } return NS_OK; } private: nsCString mInterface; }; // Runnable used dispatch the Command result on the main thread. class WifiResultDispatcher : public nsRunnable { public: WifiResultDispatcher(WifiResultOptions& aResult, const nsACString& aInterface) : mResult(aResult) , mInterface(aInterface) { MOZ_ASSERT(!NS_IsMainThread()); } NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); gWifiProxyService->DispatchWifiResult(mResult, mInterface); return NS_OK; } private: WifiResultOptions mResult; nsCString mInterface; }; // Runnable used to call SendCommand on the control thread. class ControlRunnable : public nsRunnable { public: ControlRunnable(CommandOptions aOptions, const nsACString& aInterface) : mOptions(aOptions) , mInterface(aInterface) { MOZ_ASSERT(NS_IsMainThread()); } NS_IMETHOD Run() { WifiResultOptions result; if (gWpaSupplicant->ExecuteCommand(mOptions, result, mInterface)) { nsCOMPtr runnable = new WifiResultDispatcher(result, mInterface); NS_DispatchToMainThread(runnable); } return NS_OK; } private: CommandOptions mOptions; nsCString mInterface; }; NS_IMPL_ISUPPORTS(WifiProxyService, nsIWifiProxyService) WifiProxyService::WifiProxyService() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!gWifiProxyService); } WifiProxyService::~WifiProxyService() { MOZ_ASSERT(!gWifiProxyService); } already_AddRefed WifiProxyService::FactoryCreate() { if (!XRE_IsParentProcess()) { return nullptr; } MOZ_ASSERT(NS_IsMainThread()); if (!gWifiProxyService) { gWifiProxyService = new WifiProxyService(); ClearOnShutdown(&gWifiProxyService); gWpaSupplicant = new WpaSupplicant(); ClearOnShutdown(&gWpaSupplicant); } RefPtr service = gWifiProxyService.get(); return service.forget(); } NS_IMETHODIMP WifiProxyService::Start(nsIWifiEventListener* aListener, const char ** aInterfaces, uint32_t aNumOfInterfaces) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aListener); #if ANDROID_VERSION >= 19 // KK changes the way mux'ing/demux'ing different supplicant interfaces // (e.g. wlan0/p2p0) from multi-sockets to single socket embedded with // prefixed interface name (e.g. IFNAME=wlan0 xxxxxx). Therefore, we use // the first given interface as the global interface for KK. aNumOfInterfaces = 1; #endif nsresult rv; // Since EventRunnable runs in the manner of blocking, we have to // spin a thread for each interface. // (See the WpaSupplicant::WaitForEvent) mEventThreadList.SetLength(aNumOfInterfaces); for (uint32_t i = 0; i < aNumOfInterfaces; i++) { mEventThreadList[i].mInterface = aInterfaces[i]; rv = NS_NewThread(getter_AddRefs(mEventThreadList[i].mThread)); if (NS_FAILED(rv)) { NS_WARNING("Can't create wifi event thread"); Shutdown(); return NS_ERROR_FAILURE; } } rv = NS_NewThread(getter_AddRefs(mControlThread)); if (NS_FAILED(rv)) { NS_WARNING("Can't create wifi control thread"); Shutdown(); return NS_ERROR_FAILURE; } mListener = aListener; return NS_OK; } NS_IMETHODIMP WifiProxyService::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); for (size_t i = 0; i < mEventThreadList.Length(); i++) { if (mEventThreadList[i].mThread) { mEventThreadList[i].mThread->Shutdown(); mEventThreadList[i].mThread = nullptr; } } mEventThreadList.Clear(); if (mControlThread) { mControlThread->Shutdown(); mControlThread = nullptr; } mListener = nullptr; return NS_OK; } NS_IMETHODIMP WifiProxyService::SendCommand(JS::Handle aOptions, const nsACString& aInterface, JSContext* aCx) { MOZ_ASSERT(NS_IsMainThread()); WifiCommandOptions options; if (!options.Init(aCx, aOptions)) { NS_WARNING("Bad dictionary passed to WifiProxyService::SendCommand"); return NS_ERROR_FAILURE; } if (!mControlThread) { return NS_ERROR_FAILURE; } // Dispatch the command to the control thread. CommandOptions commandOptions(options); nsCOMPtr runnable = new ControlRunnable(commandOptions, aInterface); mControlThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL); return NS_OK; } NS_IMETHODIMP WifiProxyService::WaitForEvent(const nsACString& aInterface) { MOZ_ASSERT(NS_IsMainThread()); #if ANDROID_VERSION >= 19 // We will only have one global interface for KK. if (!mEventThreadList.IsEmpty()) { nsCOMPtr runnable = new EventRunnable(aInterface); mEventThreadList[0].mThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL); return NS_OK; } #else // Dispatch to the event thread which has the given interface name for (size_t i = 0; i < mEventThreadList.Length(); i++) { if (mEventThreadList[i].mInterface.Equals(aInterface)) { nsCOMPtr runnable = new EventRunnable(aInterface); mEventThreadList[i].mThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL); return NS_OK; } } #endif return NS_ERROR_FAILURE; } void WifiProxyService::DispatchWifiResult(const WifiResultOptions& aOptions, const nsACString& aInterface) { MOZ_ASSERT(NS_IsMainThread()); mozilla::AutoSafeJSContext cx; JS::Rooted val(cx); if (!ToJSValue(cx, aOptions, &val)) { return; } if (mListener) { // Call the listener with a JS value. mListener->OnCommand(val, aInterface); } } void WifiProxyService::DispatchWifiEvent(const nsAString& aEvent, const nsACString& aInterface) { MOZ_ASSERT(NS_IsMainThread()); #if ANDROID_VERSION < 19 mListener->OnWaitEvent(aEvent, aInterface); #else // The interface might be embedded in the event string such as // "IFNAME=wlan0 CTRL-EVENT-BSS-ADDED 65 3c:94:d5:7c:11:8b". // Parse the interface name from the event string and use p2p0 // as the default interface if "IFNAME" is not found. nsAutoString event; nsAutoString embeddedInterface(NS_LITERAL_STRING("p2p0")); if (StringBeginsWith(aEvent, NS_LITERAL_STRING("IFNAME"))) { int32_t ifnameFrom = aEvent.FindChar('=') + 1; int32_t ifnameTo = aEvent.FindChar(' ') - 1; embeddedInterface = Substring(aEvent, ifnameFrom, ifnameTo - ifnameFrom + 1); event = Substring(aEvent, aEvent.FindChar(' ') + 1); } else { event = aEvent; } mListener->OnWaitEvent(event, NS_ConvertUTF16toUTF8(embeddedInterface)); #endif } NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WifiProxyService, WifiProxyService::FactoryCreate) NS_DEFINE_NAMED_CID(NS_WIFIPROXYSERVICE_CID); static const mozilla::Module::CIDEntry kWifiProxyServiceCIDs[] = { { &kNS_WIFIPROXYSERVICE_CID, false, nullptr, WifiProxyServiceConstructor }, { nullptr } }; static const mozilla::Module::ContractIDEntry kWifiProxyServiceContracts[] = { { "@mozilla.org/wifi/service;1", &kNS_WIFIPROXYSERVICE_CID }, { nullptr } }; static const mozilla::Module kWifiProxyServiceModule = { mozilla::Module::kVersion, kWifiProxyServiceCIDs, kWifiProxyServiceContracts, nullptr }; } // namespace mozilla NSMODULE_DEFN(WifiProxyServiceModule) = &kWifiProxyServiceModule;