diff options
Diffstat (limited to 'dom/system/gonk/mozstumbler')
-rw-r--r-- | dom/system/gonk/mozstumbler/MozStumbler.cpp | 426 | ||||
-rw-r--r-- | dom/system/gonk/mozstumbler/MozStumbler.h | 47 | ||||
-rw-r--r-- | dom/system/gonk/mozstumbler/StumblerLogging.cpp | 13 | ||||
-rw-r--r-- | dom/system/gonk/mozstumbler/StumblerLogging.h | 18 | ||||
-rw-r--r-- | dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp | 151 | ||||
-rw-r--r-- | dom/system/gonk/mozstumbler/UploadStumbleRunnable.h | 46 | ||||
-rw-r--r-- | dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp | 321 | ||||
-rw-r--r-- | dom/system/gonk/mozstumbler/WriteStumbleOnThread.h | 91 |
8 files changed, 1113 insertions, 0 deletions
diff --git a/dom/system/gonk/mozstumbler/MozStumbler.cpp b/dom/system/gonk/mozstumbler/MozStumbler.cpp new file mode 100644 index 000000000..61e09e705 --- /dev/null +++ b/dom/system/gonk/mozstumbler/MozStumbler.cpp @@ -0,0 +1,426 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* 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/. */ + +#include "MozStumbler.h" +#include "nsDataHashtable.h" +#include "nsGeoPosition.h" +#include "nsNetCID.h" +#include "nsPrintfCString.h" +#include "StumblerLogging.h" +#include "WriteStumbleOnThread.h" +#include "../GeolocationUtil.h" + +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIMobileConnectionInfo.h" +#include "nsIMobileConnectionService.h" +#include "nsIMobileCellInfo.h" +#include "nsIMobileNetworkInfo.h" +#include "nsINetworkInterface.h" +#include "nsIRadioInterfaceLayer.h" + +using namespace mozilla; +using namespace mozilla::dom; + + +NS_IMPL_ISUPPORTS(StumblerInfo, nsICellInfoListCallback, nsIWifiScanResultsReady) + +class RequestCellInfoEvent : public Runnable { +public: + RequestCellInfoEvent(StumblerInfo *callback) + : mRequestCallback(callback) + {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + // Get Cell Info + nsCOMPtr<nsIMobileConnectionService> service = + do_GetService(NS_MOBILE_CONNECTION_SERVICE_CONTRACTID); + + if (!service) { + STUMBLER_ERR("Stumbler-can not get nsIMobileConnectionService \n"); + return NS_OK; + } + nsCOMPtr<nsIMobileConnection> connection; + uint32_t numberOfRilServices = 1, cellInfoNum = 0; + + service->GetNumItems(&numberOfRilServices); + for (uint32_t rilNum = 0; rilNum < numberOfRilServices; rilNum++) { + service->GetItemByServiceId(rilNum /* Client Id */, getter_AddRefs(connection)); + if (!connection) { + STUMBLER_ERR("Stumbler-can not get nsIMobileConnection by ServiceId %d \n", rilNum); + } else { + cellInfoNum++; + connection->GetCellInfoList(mRequestCallback); + } + } + mRequestCallback->SetCellInfoResponsesExpected(cellInfoNum); + + // Get Wifi AP Info + nsCOMPtr<nsIInterfaceRequestor> ir = do_GetService("@mozilla.org/telephony/system-worker-manager;1"); + nsCOMPtr<nsIWifi> wifi = do_GetInterface(ir); + if (!wifi) { + mRequestCallback->SetWifiInfoResponseReceived(); + STUMBLER_ERR("Stumbler-can not get nsIWifi interface\n"); + return NS_OK; + } + wifi->GetWifiScanResults(mRequestCallback); + return NS_OK; + } +private: + RefPtr<StumblerInfo> mRequestCallback; +}; + +void +MozStumble(nsGeoPosition* position) +{ + if (WriteStumbleOnThread::IsFileWaitingForUpload()) { + nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + // Knowing that file is waiting to upload, and no collection will take place, + // just trigger the thread with an empty string. + nsCOMPtr<nsIRunnable> event = new WriteStumbleOnThread(EmptyCString()); + target->Dispatch(event, NS_DISPATCH_NORMAL); + return; + } + + nsCOMPtr<nsIDOMGeoPositionCoords> coords; + position->GetCoords(getter_AddRefs(coords)); + if (!coords) { + return; + } + + double latitude, longitude; + coords->GetLatitude(&latitude); + coords->GetLongitude(&longitude); + + const double kMinChangeInMeters = 30; + static int64_t lastTime_ms = 0; + static double sLastLat = 0; + static double sLastLon = 0; + double delta = -1.0; + int64_t timediff = (PR_Now() / PR_USEC_PER_MSEC) - lastTime_ms; + + if (0 != sLastLon || 0 != sLastLat) { + delta = CalculateDeltaInMeter(latitude, longitude, sLastLat, sLastLon); + } + STUMBLER_DBG("Stumbler-Location. [%f , %f] time_diff:%lld, delta : %f\n", + longitude, latitude, timediff, delta); + + // Consecutive GPS locations must be 30 meters and 3 seconds apart + if (lastTime_ms == 0 || ((timediff >= STUMBLE_INTERVAL_MS) && (delta > kMinChangeInMeters))){ + lastTime_ms = (PR_Now() / PR_USEC_PER_MSEC); + sLastLat = latitude; + sLastLon = longitude; + RefPtr<StumblerInfo> requestCallback = new StumblerInfo(position); + RefPtr<RequestCellInfoEvent> runnable = new RequestCellInfoEvent(requestCallback); + NS_DispatchToMainThread(runnable); + } else { + STUMBLER_DBG("Stumbler-GPS locations less than 30 meters and 3 seconds. Ignore!\n"); + } +} + +void +StumblerInfo::SetWifiInfoResponseReceived() +{ + mIsWifiInfoResponseReceived = true; + + if (mIsWifiInfoResponseReceived && mCellInfoResponsesReceived == mCellInfoResponsesExpected) { + STUMBLER_DBG("Call DumpStumblerInfo from SetWifiInfoResponseReceived\n"); + DumpStumblerInfo(); + } +} + +void +StumblerInfo::SetCellInfoResponsesExpected(uint8_t count) +{ + mCellInfoResponsesExpected = count; + STUMBLER_DBG("SetCellInfoNum (%d)\n", count); + + if (mIsWifiInfoResponseReceived && mCellInfoResponsesReceived == mCellInfoResponsesExpected) { + STUMBLER_DBG("Call DumpStumblerInfo from SetCellInfoResponsesExpected\n"); + DumpStumblerInfo(); + } +} + + +#define TEXT_LAT NS_LITERAL_CSTRING("latitude") +#define TEXT_LON NS_LITERAL_CSTRING("longitude") +#define TEXT_ACC NS_LITERAL_CSTRING("accuracy") +#define TEXT_ALT NS_LITERAL_CSTRING("altitude") +#define TEXT_ALTACC NS_LITERAL_CSTRING("altitudeAccuracy") +#define TEXT_HEAD NS_LITERAL_CSTRING("heading") +#define TEXT_SPD NS_LITERAL_CSTRING("speed") + +nsresult +StumblerInfo::LocationInfoToString(nsACString& aLocDesc) +{ + nsCOMPtr<nsIDOMGeoPositionCoords> coords; + mPosition->GetCoords(getter_AddRefs(coords)); + if (!coords) { + return NS_ERROR_FAILURE; + } + + nsDataHashtable<nsCStringHashKey, double> info; + + double val; + coords->GetLatitude(&val); + info.Put(TEXT_LAT, val); + coords->GetLongitude(&val); + info.Put(TEXT_LON, val); + coords->GetAccuracy(&val); + info.Put(TEXT_ACC, val); + coords->GetAltitude(&val); + info.Put(TEXT_ALT, val); + coords->GetAltitudeAccuracy(&val); + info.Put(TEXT_ALTACC, val); + coords->GetHeading(&val); + info.Put(TEXT_HEAD, val); + coords->GetSpeed(&val); + info.Put(TEXT_SPD, val); + + for (auto it = info.Iter(); !it.Done(); it.Next()) { + const nsACString& key = it.Key(); + val = it.UserData(); + if (!IsNaN(val)) { + aLocDesc += nsPrintfCString("\"%s\":%f,", key.BeginReading(), val); + } + } + + aLocDesc += nsPrintfCString("\"timestamp\":%lld,", PR_Now() / PR_USEC_PER_MSEC).get(); + return NS_OK; +} + +#define TEXT_RADIOTYPE NS_LITERAL_CSTRING("radioType") +#define TEXT_MCC NS_LITERAL_CSTRING("mobileCountryCode") +#define TEXT_MNC NS_LITERAL_CSTRING("mobileNetworkCode") +#define TEXT_LAC NS_LITERAL_CSTRING("locationAreaCode") +#define TEXT_CID NS_LITERAL_CSTRING("cellId") +#define TEXT_PSC NS_LITERAL_CSTRING("psc") +#define TEXT_STRENGTH_ASU NS_LITERAL_CSTRING("asu") +#define TEXT_STRENGTH_DBM NS_LITERAL_CSTRING("signalStrength") +#define TEXT_REGISTERED NS_LITERAL_CSTRING("serving") +#define TEXT_TIMEING_ADVANCE NS_LITERAL_CSTRING("timingAdvance") + +template <class T> void +ExtractCommonNonCDMACellInfoItems(nsCOMPtr<T>& cell, nsDataHashtable<nsCStringHashKey, int32_t>& info) +{ + int32_t mcc, mnc, cid, sig; + + cell->GetMcc(&mcc); + cell->GetMnc(&mnc); + cell->GetCid(&cid); + cell->GetSignalStrength(&sig); + + info.Put(TEXT_MCC, mcc); + info.Put(TEXT_MNC, mnc); + info.Put(TEXT_CID, cid); + info.Put(TEXT_STRENGTH_ASU, sig); +} + +void +StumblerInfo::CellNetworkInfoToString(nsACString& aCellDesc) +{ + aCellDesc += "\"cellTowers\": ["; + + for (uint32_t idx = 0; idx < mCellInfo.Length() ; idx++) { + const char* radioType = 0; + int32_t type; + mCellInfo[idx]->GetType(&type); + bool registered; + mCellInfo[idx]->GetRegistered(®istered); + if (idx) { + aCellDesc += ",{"; + } else { + aCellDesc += "{"; + } + + STUMBLER_DBG("type=%d\n", type); + + nsDataHashtable<nsCStringHashKey, int32_t> info; + info.Put(TEXT_REGISTERED, registered); + + if(type == nsICellInfo::CELL_INFO_TYPE_GSM) { + radioType = "gsm"; + nsCOMPtr<nsIGsmCellInfo> gsmCellInfo = do_QueryInterface(mCellInfo[idx]); + ExtractCommonNonCDMACellInfoItems(gsmCellInfo, info); + int32_t lac; + gsmCellInfo->GetLac(&lac); + info.Put(TEXT_LAC, lac); + } else if (type == nsICellInfo::CELL_INFO_TYPE_WCDMA) { + radioType = "wcdma"; + nsCOMPtr<nsIWcdmaCellInfo> wcdmaCellInfo = do_QueryInterface(mCellInfo[idx]); + ExtractCommonNonCDMACellInfoItems(wcdmaCellInfo, info); + int32_t lac, psc; + wcdmaCellInfo->GetLac(&lac); + wcdmaCellInfo->GetPsc(&psc); + info.Put(TEXT_LAC, lac); + info.Put(TEXT_PSC, psc); + } else if (type == nsICellInfo::CELL_INFO_TYPE_CDMA) { + radioType = "cdma"; + nsCOMPtr<nsICdmaCellInfo> cdmaCellInfo = do_QueryInterface(mCellInfo[idx]); + int32_t mnc, lac, cid, sig; + cdmaCellInfo->GetSystemId(&mnc); + cdmaCellInfo->GetNetworkId(&lac); + cdmaCellInfo->GetBaseStationId(&cid); + info.Put(TEXT_MNC, mnc); + info.Put(TEXT_LAC, lac); + info.Put(TEXT_CID, cid); + + cdmaCellInfo->GetEvdoDbm(&sig); + if (sig < 0 || sig == nsICellInfo::UNKNOWN_VALUE) { + cdmaCellInfo->GetCdmaDbm(&sig); + } + if (sig > -1 && sig != nsICellInfo::UNKNOWN_VALUE) { + sig *= -1; + info.Put(TEXT_STRENGTH_DBM, sig); + } + } else if (type == nsICellInfo::CELL_INFO_TYPE_LTE) { + radioType = "lte"; + nsCOMPtr<nsILteCellInfo> lteCellInfo = do_QueryInterface(mCellInfo[idx]); + ExtractCommonNonCDMACellInfoItems(lteCellInfo, info); + int32_t lac, timingAdvance, pcid, rsrp; + lteCellInfo->GetTac(&lac); + lteCellInfo->GetTimingAdvance(&timingAdvance); + lteCellInfo->GetPcid(&pcid); + lteCellInfo->GetRsrp(&rsrp); + info.Put(TEXT_LAC, lac); + info.Put(TEXT_TIMEING_ADVANCE, timingAdvance); + info.Put(TEXT_PSC, pcid); + if (rsrp != nsICellInfo::UNKNOWN_VALUE) { + info.Put(TEXT_STRENGTH_DBM, rsrp * -1); + } + } + + aCellDesc += nsPrintfCString("\"%s\":\"%s\"", TEXT_RADIOTYPE.get(), radioType); + for (auto it = info.Iter(); !it.Done(); it.Next()) { + const nsACString& key = it.Key(); + int32_t value = it.UserData(); + if (value != nsICellInfo::UNKNOWN_VALUE) { + aCellDesc += nsPrintfCString(",\"%s\":%d", key.BeginReading(), value); + } + } + + aCellDesc += "}"; + } + aCellDesc += "]"; +} + +void +StumblerInfo::DumpStumblerInfo() +{ + if (!mIsWifiInfoResponseReceived || mCellInfoResponsesReceived != mCellInfoResponsesExpected) { + STUMBLER_DBG("CellInfoReceived=%d (Expected=%d), WifiInfoResponseReceived=%d\n", + mCellInfoResponsesReceived, mCellInfoResponsesExpected, mIsWifiInfoResponseReceived); + return; + } + mIsWifiInfoResponseReceived = false; + mCellInfoResponsesReceived = 0; + + nsAutoCString desc; + nsresult rv = LocationInfoToString(desc); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("LocationInfoToString failed, skip this dump"); + return; + } + + CellNetworkInfoToString(desc); + desc += mWifiDesc; + + STUMBLER_DBG("dispatch write event to thread\n"); + nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + + nsCOMPtr<nsIRunnable> event = new WriteStumbleOnThread(desc); + target->Dispatch(event, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +StumblerInfo::NotifyGetCellInfoList(uint32_t count, nsICellInfo** aCellInfos) +{ + MOZ_ASSERT(NS_IsMainThread()); + STUMBLER_DBG("There are %d cellinfo in the result\n", count); + + for (uint32_t i = 0; i < count; i++) { + mCellInfo.AppendElement(aCellInfos[i]); + } + mCellInfoResponsesReceived++; + DumpStumblerInfo(); + return NS_OK; +} + +NS_IMETHODIMP StumblerInfo::NotifyGetCellInfoListFailed(const nsAString& error) +{ + MOZ_ASSERT(NS_IsMainThread()); + mCellInfoResponsesReceived++; + STUMBLER_ERR("NotifyGetCellInfoListFailedm CellInfoReadyNum=%d, mCellInfoResponsesExpected=%d, mIsWifiInfoResponseReceived=%d", + mCellInfoResponsesReceived, mCellInfoResponsesExpected, mIsWifiInfoResponseReceived); + DumpStumblerInfo(); + return NS_OK; +} + +NS_IMETHODIMP +StumblerInfo::Onready(uint32_t count, nsIWifiScanResult** results) +{ + MOZ_ASSERT(NS_IsMainThread()); + STUMBLER_DBG("There are %d wifiAPinfo in the result\n",count); + + mWifiDesc += ",\"wifiAccessPoints\": ["; + bool firstItem = true; + for (uint32_t i = 0 ; i < count ; i++) { + nsString ssid; + results[i]->GetSsid(ssid); + if (ssid.IsEmpty()) { + STUMBLER_DBG("no ssid, skip this AP\n"); + continue; + } + + if (ssid.Length() >= 6) { + if (StringEndsWith(ssid, NS_LITERAL_STRING("_nomap"))) { + STUMBLER_DBG("end with _nomap. skip this AP(ssid :%s)\n", ssid.get()); + continue; + } + } + + if (firstItem) { + mWifiDesc += "{"; + firstItem = false; + } else { + mWifiDesc += ",{"; + } + + // mac address + nsString bssid; + results[i]->GetBssid(bssid); + // 00:00:00:00:00:00 --> 000000000000 + bssid.StripChars(":"); + mWifiDesc += "\"macAddress\":\""; + mWifiDesc += NS_ConvertUTF16toUTF8(bssid); + + uint32_t signal; + results[i]->GetSignalStrength(&signal); + mWifiDesc += "\",\"signalStrength\":"; + mWifiDesc.AppendInt(signal); + + mWifiDesc += "}"; + } + mWifiDesc += "]"; + + mIsWifiInfoResponseReceived = true; + DumpStumblerInfo(); + return NS_OK; +} + +NS_IMETHODIMP +StumblerInfo::Onfailure() +{ + MOZ_ASSERT(NS_IsMainThread()); + STUMBLER_ERR("GetWifiScanResults Onfailure\n"); + mIsWifiInfoResponseReceived = true; + DumpStumblerInfo(); + return NS_OK; +} + diff --git a/dom/system/gonk/mozstumbler/MozStumbler.h b/dom/system/gonk/mozstumbler/MozStumbler.h new file mode 100644 index 000000000..41ee4e5e1 --- /dev/null +++ b/dom/system/gonk/mozstumbler/MozStumbler.h @@ -0,0 +1,47 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* 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/. */ + +#ifndef mozilla_system_mozstumbler_h__ +#define mozilla_system_mozstumbler_h__ + +#include "nsIDOMEventTarget.h" +#include "nsICellInfo.h" +#include "nsIWifi.h" + +#define STUMBLE_INTERVAL_MS 3000 + +class nsGeoPosition; + +void MozStumble(nsGeoPosition* position); + +class StumblerInfo final : public nsICellInfoListCallback, + public nsIWifiScanResultsReady +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICELLINFOLISTCALLBACK + NS_DECL_NSIWIFISCANRESULTSREADY + + explicit StumblerInfo(nsGeoPosition* position) + : mPosition(position), mCellInfoResponsesExpected(0), mCellInfoResponsesReceived(0), mIsWifiInfoResponseReceived(0) + {} + void SetWifiInfoResponseReceived(); + void SetCellInfoResponsesExpected(uint8_t count); + +private: + ~StumblerInfo() {} + void DumpStumblerInfo(); + nsresult LocationInfoToString(nsACString& aLocDesc); + void CellNetworkInfoToString(nsACString& aCellDesc); + nsTArray<RefPtr<nsICellInfo>> mCellInfo; + nsCString mWifiDesc; + RefPtr<nsGeoPosition> mPosition; + int mCellInfoResponsesExpected; + int mCellInfoResponsesReceived; + bool mIsWifiInfoResponseReceived; +}; +#endif // mozilla_system_mozstumbler_h__ + diff --git a/dom/system/gonk/mozstumbler/StumblerLogging.cpp b/dom/system/gonk/mozstumbler/StumblerLogging.cpp new file mode 100644 index 000000000..acf23b3b1 --- /dev/null +++ b/dom/system/gonk/mozstumbler/StumblerLogging.cpp @@ -0,0 +1,13 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* 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/. */ + +#include "StumblerLogging.h" + +mozilla::LogModule* GetLog() +{ + static mozilla::LazyLogModule log("mozstumbler"); + return log; +} diff --git a/dom/system/gonk/mozstumbler/StumblerLogging.h b/dom/system/gonk/mozstumbler/StumblerLogging.h new file mode 100644 index 000000000..038f44f8f --- /dev/null +++ b/dom/system/gonk/mozstumbler/StumblerLogging.h @@ -0,0 +1,18 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* 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/. */ + +#ifndef STUMBLERLOGGING_H +#define STUMBLERLOGGING_H + +#include "mozilla/Logging.h" + +mozilla::LogModule* GetLog(); + +#define STUMBLER_DBG(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Debug, ("STUMBLER - %s: " arg, __func__, ##__VA_ARGS__)) +#define STUMBLER_LOG(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Info, ("STUMBLER - %s: " arg, __func__, ##__VA_ARGS__)) +#define STUMBLER_ERR(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Error, ("STUMBLER -%s: " arg, __func__, ##__VA_ARGS__)) + +#endif diff --git a/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp new file mode 100644 index 000000000..d97aa9712 --- /dev/null +++ b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp @@ -0,0 +1,151 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* 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/. */ + +#include "UploadStumbleRunnable.h" +#include "StumblerLogging.h" +#include "mozilla/dom/Event.h" +#include "nsIInputStream.h" +#include "nsIScriptSecurityManager.h" +#include "nsIURLFormatter.h" +#include "nsIXMLHttpRequest.h" +#include "nsNetUtil.h" +#include "nsVariant.h" + +UploadStumbleRunnable::UploadStumbleRunnable(nsIInputStream* aUploadData) +: mUploadInputStream(aUploadData) +{ +} + +NS_IMETHODIMP +UploadStumbleRunnable::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv = Upload(); + if (NS_FAILED(rv)) { + WriteStumbleOnThread::UploadEnded(false); + } + return NS_OK; +} + +nsresult +UploadStumbleRunnable::Upload() +{ + nsresult rv; + RefPtr<nsVariant> variant = new nsVariant(); + + rv = variant->SetAsISupports(mUploadInputStream); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIXMLHttpRequest> xhr = do_CreateInstance(NS_XMLHTTPREQUEST_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIScriptSecurityManager> secman = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> systemPrincipal; + rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = xhr->Init(systemPrincipal, nullptr, nullptr, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURLFormatter> formatter = + do_CreateInstance("@mozilla.org/toolkit/URLFormatterService;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsString url; + rv = formatter->FormatURLPref(NS_LITERAL_STRING("geo.stumbler.url"), url); + NS_ENSURE_SUCCESS(rv, rv); + + rv = xhr->Open(NS_LITERAL_CSTRING("POST"), NS_ConvertUTF16toUTF8(url), false, EmptyString(), EmptyString()); + NS_ENSURE_SUCCESS(rv, rv); + + xhr->SetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), NS_LITERAL_CSTRING("gzip")); + xhr->SetMozBackgroundRequest(true); + // 60s timeout + xhr->SetTimeout(60 * 1000); + + nsCOMPtr<EventTarget> target(do_QueryInterface(xhr)); + RefPtr<nsIDOMEventListener> listener = new UploadEventListener(xhr); + + const char* const sEventStrings[] = { + // nsIXMLHttpRequestEventTarget event types + "abort", + "error", + "load", + "timeout" + }; + + for (uint32_t index = 0; index < MOZ_ARRAY_LENGTH(sEventStrings); index++) { + nsAutoString eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]); + rv = target->AddEventListener(eventType, listener, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = xhr->Send(variant); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UploadEventListener, nsIDOMEventListener) + +UploadEventListener::UploadEventListener(nsIXMLHttpRequest* aXHR) +: mXHR(aXHR) +{ +} + +NS_IMETHODIMP +UploadEventListener::HandleEvent(nsIDOMEvent* aEvent) +{ + nsString type; + if (NS_FAILED(aEvent->GetType(type))) { + STUMBLER_ERR("Failed to get event type"); + WriteStumbleOnThread::UploadEnded(false); + return NS_ERROR_FAILURE; + } + + if (type.EqualsLiteral("load")) { + STUMBLER_DBG("Got load Event\n"); + } else if (type.EqualsLiteral("error") && mXHR) { + STUMBLER_ERR("Upload Error"); + } else { + STUMBLER_DBG("Receive %s Event", NS_ConvertUTF16toUTF8(type).get()); + } + + uint32_t statusCode = 0; + bool doDelete = false; + if (!mXHR) { + return NS_OK; + } + nsresult rv = mXHR->GetStatus(&statusCode); + if (NS_SUCCEEDED(rv)) { + STUMBLER_DBG("statuscode %d \n", statusCode); + } + + if (200 == statusCode || 400 == statusCode) { + doDelete = true; + } + + WriteStumbleOnThread::UploadEnded(doDelete); + nsCOMPtr<EventTarget> target(do_QueryInterface(mXHR)); + + const char* const sEventStrings[] = { + // nsIXMLHttpRequestEventTarget event types + "abort", + "error", + "load", + "timeout" + }; + + for (uint32_t index = 0; index < MOZ_ARRAY_LENGTH(sEventStrings); index++) { + nsAutoString eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]); + rv = target->RemoveEventListener(eventType, this, false); + } + + mXHR = nullptr; + return NS_OK; +} diff --git a/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h new file mode 100644 index 000000000..462665a86 --- /dev/null +++ b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h @@ -0,0 +1,46 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* 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/. */ + + +#ifndef UPLOADSTUMBLERUNNABLE_H +#define UPLOADSTUMBLERUNNABLE_H + +#include "nsIDOMEventListener.h" + +class nsIXMLHttpRequest; +class nsIInputStream; + +/* + This runnable is managed by WriteStumbleOnThread only, see that class + for how this is scheduled. + */ +class UploadStumbleRunnable final : public Runnable +{ +public: + explicit UploadStumbleRunnable(nsIInputStream* aUploadInputStream); + + NS_IMETHOD Run() override; +private: + virtual ~UploadStumbleRunnable() {} + nsCOMPtr<nsIInputStream> mUploadInputStream; + nsresult Upload(); +}; + + +class UploadEventListener : public nsIDOMEventListener +{ +public: + UploadEventListener(nsIXMLHttpRequest* aXHR); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + +protected: + virtual ~UploadEventListener() {} + nsCOMPtr<nsIXMLHttpRequest> mXHR; +}; + +#endif diff --git a/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp new file mode 100644 index 000000000..e58e771c4 --- /dev/null +++ b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp @@ -0,0 +1,321 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* 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/. */ + +#include "WriteStumbleOnThread.h" +#include "StumblerLogging.h" +#include "UploadStumbleRunnable.h" +#include "nsDumpUtils.h" +#include "nsGZFileWriter.h" +#include "nsIFileStreams.h" +#include "nsIInputStream.h" +#include "nsPrintfCString.h" + +#define MAXFILESIZE_KB (15 * 1024) +#define ONEDAY_IN_MSEC (24 * 60 * 60 * 1000) +#define MAX_UPLOAD_ATTEMPTS 20 + +mozilla::Atomic<bool> WriteStumbleOnThread::sIsFileWaitingForUpload(false); +mozilla::Atomic<bool> WriteStumbleOnThread::sIsAlreadyRunning(false); +WriteStumbleOnThread::UploadFreqGuard WriteStumbleOnThread::sUploadFreqGuard = {0}; + +#define FILENAME_INPROGRESS NS_LITERAL_CSTRING("stumbles.json.gz") +#define FILENAME_COMPLETED NS_LITERAL_CSTRING("stumbles.done.json.gz") +#define OUTPUT_DIR NS_LITERAL_CSTRING("mozstumbler") + +class DeleteRunnable : public Runnable +{ + public: + DeleteRunnable() {} + + NS_IMETHOD + Run() override + { + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, + getter_AddRefs(tmpFile), + OUTPUT_DIR, + nsDumpUtils::CREATE); + if (NS_SUCCEEDED(rv)) { + tmpFile->Remove(true); + } + // critically, this sets this flag to false so writing can happen again + WriteStumbleOnThread::sIsAlreadyRunning = false; + WriteStumbleOnThread::sIsFileWaitingForUpload = false; + return NS_OK; + } + + private: + ~DeleteRunnable() {} +}; + +bool +WriteStumbleOnThread::IsFileWaitingForUpload() +{ + return sIsFileWaitingForUpload; +} + +void +WriteStumbleOnThread::UploadEnded(bool deleteUploadFile) +{ + if (!deleteUploadFile) { + sIsAlreadyRunning = false; + return; + } + + nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + nsCOMPtr<nsIRunnable> event = new DeleteRunnable(); + target->Dispatch(event, NS_DISPATCH_NORMAL); +} + +void +WriteStumbleOnThread::WriteJSON(Partition aPart) +{ + MOZ_ASSERT(!NS_IsMainThread()); + + nsCOMPtr<nsIFile> tmpFile; + nsresult rv; + rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile), + OUTPUT_DIR, nsDumpUtils::CREATE); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("Open a file for stumble failed"); + return; + } + + RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter(nsGZFileWriter::Append); + rv = gzWriter->Init(tmpFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("gzWriter init failed"); + return; + } + + /* + The json format is like below. + {items:[ + {item}, + {item}, + {item} + ]} + */ + + // Need to add "]}" after the last item + if (aPart == Partition::End) { + rv = gzWriter->Write("]}"); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("gzWriter Write failed"); + } + + rv = gzWriter->Finish(); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("ostream finish failed"); + } + + nsCOMPtr<nsIFile> targetFile; + nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(targetFile), + OUTPUT_DIR, nsDumpUtils::CREATE); + nsAutoString targetFilename; + rv = targetFile->GetLeafName(targetFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("Get Filename failed"); + return; + } + rv = targetFile->Remove(true); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("Remove File failed"); + return; + } + // Rename tmpfile + rv = tmpFile->MoveTo(/* directory */ nullptr, targetFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("Rename File failed"); + return; + } + return; + } + + // Need to add "{items:[" before the first item + if (aPart == Partition::Begining) { + rv = gzWriter->Write("{\"items\":[{"); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("ostream write begining failed"); + } + } else if (aPart == Partition::Middle) { + rv = gzWriter->Write(",{"); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("ostream write middle failed"); + } + } + rv = gzWriter->Write(mDesc.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("ostream write mDesc failed"); + } + // one item is ended with '}' (e.g. {item}) + rv = gzWriter->Write("}"); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("ostream write end failed"); + } + + rv = gzWriter->Finish(); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("ostream finish failed"); + } + + // check if it is the end of this file + int64_t fileSize = 0; + rv = tmpFile->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("GetFileSize failed"); + return; + } + if (fileSize >= MAXFILESIZE_KB) { + WriteJSON(Partition::End); + return; + } +} + +WriteStumbleOnThread::Partition +WriteStumbleOnThread::GetWritePosition() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile), + OUTPUT_DIR, nsDumpUtils::CREATE); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("Open a file for stumble failed"); + return Partition::Unknown; + } + + int64_t fileSize = 0; + rv = tmpFile->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("GetFileSize failed"); + return Partition::Unknown; + } + + if (fileSize == 0) { + return Partition::Begining; + } else if (fileSize >= MAXFILESIZE_KB) { + return Partition::End; + } else { + return Partition::Middle; + } +} + +NS_IMETHODIMP +WriteStumbleOnThread::Run() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + bool b = sIsAlreadyRunning.exchange(true); + if (b) { + return NS_OK; + } + + UploadFileStatus status = GetUploadFileStatus(); + + if (UploadFileStatus::NoFile != status) { + if (UploadFileStatus::ExistsAndReadyToUpload == status) { + sIsFileWaitingForUpload = true; + Upload(); + return NS_OK; + } + } else { + Partition partition = GetWritePosition(); + if (partition == Partition::Unknown) { + STUMBLER_ERR("GetWritePosition failed, skip once"); + } else { + WriteJSON(partition); + } + } + + sIsFileWaitingForUpload = false; + sIsAlreadyRunning = false; + return NS_OK; +} + + +/* + If the upload file exists, then check if it is one day old. + • if it is a day old -> ExistsAndReadyToUpload + • if it is less than the current day old -> Exists + • otherwise -> NoFile + + The Exists case means that the upload and the stumbling is rate limited + per-day to the size of the one file. + */ +WriteStumbleOnThread::UploadFileStatus +WriteStumbleOnThread::GetUploadFileStatus() +{ + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile), + OUTPUT_DIR, nsDumpUtils::CREATE); + int64_t fileSize; + rv = tmpFile->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("GetFileSize failed"); + return UploadFileStatus::NoFile; + } + if (fileSize <= 0) { + tmpFile->Remove(true); + return UploadFileStatus::NoFile; + } + + PRTime lastModifiedTime; + tmpFile->GetLastModifiedTime(&lastModifiedTime); + if ((PR_Now() / PR_USEC_PER_MSEC) - lastModifiedTime >= ONEDAY_IN_MSEC) { + return UploadFileStatus::ExistsAndReadyToUpload; + } + return UploadFileStatus::Exists; +} + +void +WriteStumbleOnThread::Upload() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + time_t seconds = time(0); + int day = seconds / (60 * 60 * 24); + + if (sUploadFreqGuard.daySinceEpoch < day) { + sUploadFreqGuard.daySinceEpoch = day; + sUploadFreqGuard.attempts = 0; + } + + sUploadFreqGuard.attempts++; + if (sUploadFreqGuard.attempts > MAX_UPLOAD_ATTEMPTS) { + STUMBLER_ERR("Too many upload attempts today"); + sIsAlreadyRunning = false; + return; + } + + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile), + OUTPUT_DIR, nsDumpUtils::CREATE); + int64_t fileSize; + rv = tmpFile->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("GetFileSize failed"); + sIsAlreadyRunning = false; + return; + } + + if (fileSize <= 0) { + sIsAlreadyRunning = false; + return; + } + + // prepare json into nsIInputStream + nsCOMPtr<nsIInputStream> inStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), tmpFile); + if (NS_FAILED(rv)) { + sIsAlreadyRunning = false; + return; + } + + RefPtr<nsIRunnable> uploader = new UploadStumbleRunnable(inStream); + NS_DispatchToMainThread(uploader); +} diff --git a/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h new file mode 100644 index 000000000..104cf9bdd --- /dev/null +++ b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h @@ -0,0 +1,91 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* 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/. */ + +#ifndef WriteStumbleOnThread_H +#define WriteStumbleOnThread_H + +#include "mozilla/Atomics.h" + +class DeleteRunnable; + +/* + This class is the entry point to stumbling, in that it + receives the location+cell+wifi string and writes it + to disk, or instead, it calls UploadStumbleRunnable + to upload the data. + + Writes will happen until the file is a max size, then stop. + Uploads will happen only when the file is one day old. + The purpose of these decisions is to have very simple rate-limiting + on the writes, as well as the uploads. + + There is only one file active; it is either being used for writing, + or for uploading. If the file is ready for uploading, no further + writes will take place until this file has been uploaded. + This can mean writing might not take place for days until the uploaded + file is processed. This is correct by-design. + + A notable limitation is that the upload is triggered by a location event, + this is used as an arbitrary and simple trigger. In future, there are + better events that can be used, such as detecting network activity. + + This thread is guarded so that only one instance is active (see the + mozilla::Atomics used for this). + */ +class WriteStumbleOnThread : public mozilla::Runnable +{ +public: + explicit WriteStumbleOnThread(const nsCString& aDesc) + : mDesc(aDesc) + {} + + NS_IMETHOD Run() override; + + static void UploadEnded(bool deleteUploadFile); + + // Used externally to determine if cell+wifi scans should happen + // (returns false for that case). + static bool IsFileWaitingForUpload(); + +private: + friend class DeleteRunnable; + + enum class Partition { + Begining, + Middle, + End, + Unknown + }; + + enum class UploadFileStatus { + NoFile, Exists, ExistsAndReadyToUpload + }; + + ~WriteStumbleOnThread() {} + + Partition GetWritePosition(); + UploadFileStatus GetUploadFileStatus(); + void WriteJSON(Partition aPart); + void Upload(); + + nsCString mDesc; + + // Only run one instance of this + static mozilla::Atomic<bool> sIsAlreadyRunning; + + static mozilla::Atomic<bool> sIsFileWaitingForUpload; + + // Limit the upload attempts per day. If the device is rebooted + // this resets the allowed attempts, which is acceptable. + struct UploadFreqGuard { + int attempts; + int daySinceEpoch; + }; + static UploadFreqGuard sUploadFreqGuard; + +}; + +#endif |