Bug 1647489 - Ensure WDBA notification data is not lost r=mhowell,agashlin,nalexander

Use caching to ensure that data collected when a ping is not sent eventually gets sent in a later ping. This is necessary only when a notification has actually been shown.

Differential Revision: https://phabricator.services.mozilla.com/D80711
This commit is contained in:
Kirk Steuber 2020-07-01 18:46:30 +00:00
Родитель 38c6d7dc8c
Коммит a9a1270406
2 изменённых файлов: 262 добавлений и 8 удалений

Просмотреть файл

@ -13,12 +13,14 @@
#include "common.h"
#include "EventLog.h"
#include "Policy.h"
#include "json/json.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/HelperMacros.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/WinHeaderOnlyUtils.h"
#define TELEMETRY_BASE_URL "https://incoming.telemetry.mozilla.org/submit"
@ -36,6 +38,13 @@
// supposed to run every 24 hours.
#define MINIMUM_PING_PERIOD_SEC ((23 * 60 * 60) + (45 * 60))
// The cache only stores data when a notification is shown, so one cache slot
// per possible notification is the maximum we should ever need.
#define MAX_NOTIFICATION_DATA_CACHE_SIZE 2
#define NOTIFICATION_TYPE_CACHE_PREFIX L"PingCacheNotificationType"
#define NOTIFICATION_SHOWN_CACHE_PREFIX L"PingCacheNotificationShown"
#define NOTIFICATION_ACTION_CACHE_PREFIX L"PingCacheNotificationAction"
#if !defined(RRF_SUBKEY_WOW6464KEY)
# define RRF_SUBKEY_WOW6464KEY 0x00010000
#endif // !defined(RRF_SUBKEY_WOW6464KEY)
@ -345,6 +354,249 @@ static TelemetryFieldResult GetAndUpdatePreviousDefaultBrowser(
return oldCurrentDefault;
}
// If notifications actions occurred, we want to make sure a ping gets sent for
// them. If we aren't sending a ping right now, we want to cache the ping values
// for the next time the ping is sent.
// The values passed will only be cached if actions were actually taken
// (i.e. not when notificationShown == "not-shown")
HRESULT MaybeCache(const std::string& notificationType,
const std::string& notificationShown,
const std::string& notificationAction) {
std::string notShown =
GetStringForNotificationShown(NotificationShown::NotShown);
if (notificationShown == notShown) {
return S_OK;
}
// Find the first free cache index so we can write to the end of the cache.
// (The cache is a FIFO queue)
unsigned int cacheIndex = 0;
while (true) {
std::wstring valueName = NOTIFICATION_TYPE_CACHE_PREFIX;
valueName += std::to_wstring(cacheIndex);
LSTATUS ls =
RegGetValueW(HKEY_CURRENT_USER, AGENT_REGKEY_NAME, valueName.c_str(),
RRF_RT_REG_SZ, nullptr, nullptr, nullptr);
if (ls == ERROR_FILE_NOT_FOUND) {
break;
}
if (ls != ERROR_SUCCESS) {
LOG_ERROR_MESSAGE(L"Error probing cache: %#X", ls);
return HRESULT_FROM_WIN32(ls);
}
++cacheIndex;
if (cacheIndex >= MAX_NOTIFICATION_DATA_CACHE_SIZE) {
LOG_ERROR_MESSAGE(L"Cache full. Cannot store another value.");
return E_FAIL;
}
}
// Generate the value names for that index
std::wstring typeValueName = NOTIFICATION_TYPE_CACHE_PREFIX;
typeValueName += std::to_wstring(cacheIndex);
std::wstring shownValueName = NOTIFICATION_SHOWN_CACHE_PREFIX;
shownValueName += std::to_wstring(cacheIndex);
std::wstring actionValueName = NOTIFICATION_ACTION_CACHE_PREFIX;
actionValueName += std::to_wstring(cacheIndex);
// Store the data at that index.
mozilla::WindowsErrorResult<mozilla::Ok> result = RegistrySetValueString(
IsPrefixed::Unprefixed, typeValueName.c_str(), notificationType.c_str());
if (result.isErr()) {
HRESULT hr = result.unwrapErr().AsHResult();
LOG_ERROR_MESSAGE(L"Failed to write to cache: %#X", hr);
return hr;
}
result =
RegistrySetValueString(IsPrefixed::Unprefixed, shownValueName.c_str(),
notificationShown.c_str());
if (result.isErr()) {
HRESULT hr = result.unwrapErr().AsHResult();
LOG_ERROR_MESSAGE(L"Failed to write to cache: %#X", hr);
return hr;
}
result =
RegistrySetValueString(IsPrefixed::Unprefixed, actionValueName.c_str(),
notificationAction.c_str());
if (result.isErr()) {
HRESULT hr = result.unwrapErr().AsHResult();
LOG_ERROR_MESSAGE(L"Failed to write to cache: %#X", hr);
return hr;
}
return S_OK;
}
// Pops the first item off the cache and shifts the remaining ones down by one
// to fill the space left behind.
// If there is an error reading the cached values, they will be discarded and
// the next cache will be read.
// If there are no entries in the cache, the first outparam will be set to True
// to indicate that no values were read.
HRESULT PopCache(bool& cacheEmpty, std::string& notificationType,
std::string& notificationShown,
std::string& notificationAction) {
// This function body will be in a loop so that we read more than once on
// cache read problems. But we are putting a limit on the number of possible
// iterations to prevent us from ever getting stuck in this loop. Under
// expected operation, we should never finish this loop without returning.
for (unsigned int i = 0; i < MAX_NOTIFICATION_DATA_CACHE_SIZE; ++i) {
// Cache is a FIFO queue. We always read from the beginning
unsigned int readIndex = 0;
// Generate the value names for that index
std::wstring typeValueName = NOTIFICATION_TYPE_CACHE_PREFIX;
typeValueName += std::to_wstring(readIndex);
std::wstring shownValueName = NOTIFICATION_SHOWN_CACHE_PREFIX;
shownValueName += std::to_wstring(readIndex);
std::wstring actionValueName = NOTIFICATION_ACTION_CACHE_PREFIX;
actionValueName += std::to_wstring(readIndex);
// Read from the cache
MaybeStringResult typeResult =
RegistryGetValueString(IsPrefixed::Unprefixed, typeValueName.c_str());
MaybeStringResult shownResult =
RegistryGetValueString(IsPrefixed::Unprefixed, shownValueName.c_str());
MaybeStringResult actionResult =
RegistryGetValueString(IsPrefixed::Unprefixed, actionValueName.c_str());
bool cacheReadSuccess = false;
if (typeResult.isOk() && shownResult.isOk() && actionResult.isOk()) {
mozilla::Maybe<std::string> maybeType = typeResult.unwrap();
mozilla::Maybe<std::string> maybeShown = shownResult.unwrap();
mozilla::Maybe<std::string> maybeAction = actionResult.unwrap();
if (maybeType.isNothing() && maybeShown.isNothing() &&
maybeAction.isNothing()) {
// This is the most common case - nothing is in the cache. Return early.
cacheEmpty = true;
return S_OK;
}
cacheReadSuccess =
maybeType.isSome() && maybeShown.isSome() && maybeAction.isSome();
if (cacheReadSuccess) {
notificationType = maybeType.value();
notificationShown = maybeShown.value();
notificationAction = maybeAction.value();
} else {
LOG_ERROR_MESSAGE(
L"Some notification data cache data is missing. "
L"Cache entry dropped");
}
} else {
LOG_ERROR_MESSAGE(
L"Error reading cache data. Entry dropped: %#X, %#X, %#X",
typeResult.unwrapErr().AsHResult(),
shownResult.unwrapErr().AsHResult(),
actionResult.unwrapErr().AsHResult());
}
// Shift the cache entries
for (unsigned int shiftTo = 0;
shiftTo + 1 < MAX_NOTIFICATION_DATA_CACHE_SIZE; ++shiftTo) {
const unsigned int shiftFrom = shiftTo + 1;
// Generate the value names for those indicies
std::wstring shiftToTypeName = NOTIFICATION_TYPE_CACHE_PREFIX;
shiftToTypeName += std::to_wstring(shiftTo);
std::wstring shiftToShownName = NOTIFICATION_SHOWN_CACHE_PREFIX;
shiftToShownName += std::to_wstring(shiftTo);
std::wstring shiftToActionName = NOTIFICATION_ACTION_CACHE_PREFIX;
shiftToActionName += std::to_wstring(shiftTo);
std::wstring shiftFromTypeName = NOTIFICATION_TYPE_CACHE_PREFIX;
shiftFromTypeName += std::to_wstring(shiftFrom);
std::wstring shiftFromShownName = NOTIFICATION_SHOWN_CACHE_PREFIX;
shiftFromShownName += std::to_wstring(shiftFrom);
std::wstring shiftFromActionName = NOTIFICATION_ACTION_CACHE_PREFIX;
shiftFromActionName += std::to_wstring(shiftFrom);
// Shift stored values down by an index. If there is nothing in the value
// we are shifting, delete the values we would have overwritten, since
// there is nothing to overwrite with.
MaybeStringResult result = RegistryGetValueString(
IsPrefixed::Unprefixed, shiftFromTypeName.c_str());
if (result.isOk()) {
mozilla::Maybe<std::string> maybeValue = result.unwrap();
if (maybeValue.isSome()) {
std::string value = maybeValue.value();
mozilla::Unused << RegistrySetValueString(
IsPrefixed::Unprefixed, shiftToTypeName.c_str(), value.c_str());
} else {
mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed,
shiftToTypeName.c_str());
}
}
result = RegistryGetValueString(IsPrefixed::Unprefixed,
shiftFromShownName.c_str());
if (result.isOk()) {
mozilla::Maybe<std::string> maybeValue = result.unwrap();
if (maybeValue.isSome()) {
std::string value = maybeValue.value();
mozilla::Unused << RegistrySetValueString(
IsPrefixed::Unprefixed, shiftToShownName.c_str(), value.c_str());
} else {
mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed,
shiftToShownName.c_str());
}
}
result = RegistryGetValueString(IsPrefixed::Unprefixed,
shiftFromActionName.c_str());
if (result.isOk()) {
mozilla::Maybe<std::string> maybeValue = result.unwrap();
if (maybeValue.isSome()) {
std::string value = maybeValue.value();
mozilla::Unused << RegistrySetValueString(
IsPrefixed::Unprefixed, shiftToActionName.c_str(), value.c_str());
} else {
mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed,
shiftToActionName.c_str());
}
}
// Delete the values we just shifted.
mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed,
shiftFromTypeName.c_str());
mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed,
shiftFromShownName.c_str());
mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed,
shiftFromActionName.c_str());
}
// If we got good data, return it. Otherwise, repeat to try to get the next
// cache entry.
if (cacheReadSuccess) {
cacheEmpty = false;
return S_OK;
}
}
return E_FAIL;
}
// This function retrieves values cached by MaybeCache. If any values were
// loaded from the cache, the values passed in to this function are passed to
// MaybeCache so that they are not lost. If there are no values in the cache,
// the values passed will not be changed.
// Values retrieved from the cache will also be removed from it.
HRESULT MaybeSwapForCached(std::string& notificationType,
std::string& notificationShown,
std::string& notificationAction) {
bool cacheEmpty;
std::string cachedType, cachedShown, cachedAction;
HRESULT hr = PopCache(cacheEmpty, cachedType, cachedShown, cachedAction);
if (FAILED(hr)) {
LOG_ERROR_MESSAGE(L"Failed to read cache: %#X", hr);
return hr;
}
if (cacheEmpty) {
return S_OK;
}
MaybeCache(notificationType, notificationShown, notificationAction);
notificationType = cachedType;
notificationShown = cachedShown;
notificationAction = cachedAction;
return S_OK;
}
HRESULT SendDefaultBrowserPing(
const DefaultBrowserInfo& browserInfo,
const NotificationActivities& activitiesPerformed) {
@ -372,8 +624,8 @@ HRESULT SendDefaultBrowserPing(
// Do not send the ping if we are not an official telemetry-enabled build;
// don't even generate the ping in fact, because if we write the file out
// then some other build might find it later and decide to submit it.
if (!IsOfficialTelemetry()) {
return S_OK;
if (!IsOfficialTelemetry() || IsTelemetryDisabled()) {
return MaybeCache(notificationType, notificationShown, notificationAction);
}
// Pings are limited to one per day (across all installations), so check if we
@ -392,7 +644,13 @@ HRESULT SendDefaultBrowserPing(
}
bool pingAlreadySent = pingAlreadySentResult.unwrap();
if (pingAlreadySent) {
return S_OK;
return MaybeCache(notificationType, notificationShown, notificationAction);
}
HRESULT hr = MaybeSwapForCached(notificationType, notificationShown,
notificationAction);
if (FAILED(hr)) {
return hr;
}
// Don't update the registry's default browser data until we are sure we

Просмотреть файл

@ -17,7 +17,6 @@
#include "DefaultBrowser.h"
#include "EventLog.h"
#include "Notification.h"
#include "Policy.h"
#include "Registry.h"
#include "ScheduledTask.h"
#include "Telemetry.h"
@ -334,10 +333,7 @@ int wmain(int argc, wchar_t** argv) {
NotificationActivities activitiesPerformed =
MaybeShowNotification(browserInfo, argv[2]);
if (!IsTelemetryDisabled()) {
return SendDefaultBrowserPing(browserInfo, activitiesPerformed);
}
return S_OK;
return SendDefaultBrowserPing(browserInfo, activitiesPerformed);
} else {
return E_INVALIDARG;
}