1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef __DEFAULT_BROWSER_AGENT_CACHE_H__ 8 #define __DEFAULT_BROWSER_AGENT_CACHE_H__ 9 10 #include <cstdint> 11 #include <string> 12 #include <windows.h> 13 14 #include "Registry.h" 15 16 using DwordResult = mozilla::WindowsErrorResult<uint32_t>; 17 18 /** 19 * This cache functions as a FIFO queue which writes its data to the Windows 20 * registry. 21 * 22 * Note that the cache is not thread-safe, so it is recommended that the WDBA's 23 * RegistryMutex be acquired before accessing it. 24 * 25 * Some of the terminology used in this module is a easy to mix up, so let's 26 * just be clear about it: 27 * - registry key/sub-key 28 * A registry key is sort of like the registry's equivalent of a 29 * directory. It can contain values, each of which is made up of a name 30 * and corresponding data. We may also refer to a "sub-key", meaning a 31 * registry key nested in a registry key. 32 * - cache key/entry key 33 * A cache key refers to the string that we use to look up a single 34 * element of cache entry data. Example: "CacheEntryVersion" 35 * - entry 36 * This refers to an entire record stored using Cache::Enqueue or retrieved 37 * using Cache::Dequeue. It consists of numerous cache keys and their 38 * corresponding data. 39 * 40 * The first version of this cache was problematic because of how hard it was to 41 * extend. This version attempts to overcome this. It first migrates all data 42 * out of the version 1 cache. This means that the stored ping data will not 43 * be accessible to out-of-date clients, but presumably they will eventually 44 * be updated or the up-to-date client that performed the migration will send 45 * the pings itself. Because the WDBA telemetry has no client ID, all analysis 46 * is stateless, so even if the other clients send some pings before the stored 47 * ones get sent, that's ok. The ordering isn't really important. 48 * 49 * This version of the cache attempts to correct the problem of how hard it was 50 * to extend the old cache. The biggest problem that the old cache had was that 51 * when it dequeued data it had to shift data, but it wouldn't shift keys that 52 * it didn't know about, causing them to become associated with the wrong cache 53 * entries. 54 * 55 * Version 2 of the cache will make 4 improvements to attempt to avoid problems 56 * like this in the future: 57 * 1. Each cache entry will get its own registry key. This will help to keep 58 * cache entries isolated from each other. 59 * 2. Each cache entry will include version data so that we know what cache 60 * keys to expect when we read it. 61 * 3. Rather than having to shift every entry every time we dequeue, we will 62 * implement a circular queue so that we just have to update what index 63 * currently represents the front 64 * 4. We will store the cache capacity in the cache so that we can expand the 65 * cache later, if we want, without breaking previous versions. 66 */ 67 class Cache { 68 public: 69 // cacheRegKey is the registry sub-key that the cache will be stored in. If 70 // null is passed (the default), we will use the default cache name. This is 71 // what ought to be used in production. When testing, we will pass a different 72 // key in so that our testing caches don't conflict with each other or with 73 // a possible production cache on the test machine. 74 explicit Cache(const wchar_t* cacheRegKey = nullptr); 75 ~Cache(); 76 77 // The version of the cache (not to be confused with the version of the cache 78 // entries). This should only be incremented if we need to make breaking 79 // changes that require migration to a new cache location, like we did between 80 // versions 1 and 2. This value will be used as part of the sub-key that the 81 // cache is stored in (ex: "PingCache\version2"). 82 static constexpr const uint32_t kVersion = 2; 83 // This value will be written into each entry. This allows us to know what 84 // cache keys to expect in the event that additional cache keys are added in 85 // later entry versions. 86 static constexpr const uint32_t kEntryVersion = 2; 87 static constexpr const uint32_t kDefaultCapacity = 2; 88 // We want to allow the cache to be expandable, but we don't really want it to 89 // be infinitely expandable. So we'll set an upper bound. 90 static constexpr const uint32_t kMaxCapacity = 100; 91 static constexpr const wchar_t* kDefaultPingCacheRegKey = L"PingCache"; 92 93 // Used to read the version 1 cache entries during data migration. Full cache 94 // key names are formatted like: "<keyPrefix><baseKeyName><cacheIndex>" 95 // For example: "PingCacheNotificationType0" 96 static constexpr const wchar_t* kVersion1KeyPrefix = L"PingCache"; 97 static constexpr const uint32_t kVersion1MaxSize = 2; 98 99 static constexpr const wchar_t* kCapacityRegName = L"Capacity"; 100 static constexpr const wchar_t* kFrontRegName = L"Front"; 101 static constexpr const wchar_t* kSizeRegName = L"Size"; 102 103 // Cache Entry keys 104 static constexpr const wchar_t* kEntryVersionKey = L"CacheEntryVersion"; 105 // Note that the next 3 must also match the base key names from version 1 106 // since we use them to construct those key names. 107 static constexpr const wchar_t* kNotificationTypeKey = L"NotificationType"; 108 static constexpr const wchar_t* kNotificationShownKey = L"NotificationShown"; 109 static constexpr const wchar_t* kNotificationActionKey = 110 L"NotificationAction"; 111 static constexpr const wchar_t* kPrevNotificationActionKey = 112 L"PrevNotificationAction"; 113 114 // The version key wasn't added until version 2, but we add it to the version 115 // 1 entries when migrating them to the cache. 116 static constexpr const uint32_t kInitialVersionEntryVersionKey = 1; 117 static constexpr const uint32_t kInitialVersionNotificationTypeKey = 1; 118 static constexpr const uint32_t kInitialVersionNotificationShownKey = 1; 119 static constexpr const uint32_t kInitialVersionNotificationActionKey = 1; 120 static constexpr const uint32_t kInitialVersionPrevNotificationActionKey = 2; 121 122 // We have two cache entry structs: one for the current version, and one 123 // generic one that can handle any version. There are a couple of reasons 124 // for this: 125 // - We only want to support writing the current version, but we want to 126 // support reading any version. 127 // - It makes things a bit nicer for the caller when Enqueue-ing, since 128 // they don't have to set the version or wrap values that were added 129 // later in a mozilla::Maybe. 130 // - It keeps us from having to worry about writing an invalid cache entry, 131 // such as one that claims to be version 2, but doesn't have 132 // prevNotificationAction. 133 // Note that the entry struct for the current version does not contain a 134 // version member value because we already know that its version is equal to 135 // Cache::kEntryVersion. 136 struct Entry { 137 std::string notificationType; 138 std::string notificationShown; 139 std::string notificationAction; 140 std::string prevNotificationAction; 141 }; 142 struct VersionedEntry { 143 uint32_t entryVersion; 144 std::string notificationType; 145 std::string notificationShown; 146 std::string notificationAction; 147 mozilla::Maybe<std::string> prevNotificationAction; 148 }; 149 150 using MaybeEntry = mozilla::Maybe<VersionedEntry>; 151 using MaybeEntryResult = mozilla::WindowsErrorResult<MaybeEntry>; 152 153 VoidResult Init(); 154 VoidResult Enqueue(const Entry& entry); 155 MaybeEntryResult Dequeue(); 156 157 private: 158 const std::wstring mCacheRegKey; 159 160 // We can't easily copy a VoidResult, so just store the raw HRESULT here. 161 mozilla::Maybe<HRESULT> mInitializeResult; 162 // How large the cache will grow before it starts rejecting new entries. 163 uint32_t mCapacity; 164 // The index of the first present cache entry. 165 uint32_t mFront; 166 // How many entries are present in the cache. 167 uint32_t mSize; 168 169 DwordResult EnsureDwordSetting(const wchar_t* regName, uint32_t defaultValue); 170 VoidResult SetupCache(); 171 VoidResult MaybeMigrateVersion1(); 172 std::wstring MakeEntryRegKeyName(uint32_t index); 173 VoidResult WriteEntryKeys(uint32_t index, const VersionedEntry& entry); 174 VoidResult DeleteEntry(uint32_t index); 175 VoidResult SetFront(uint32_t newFront); 176 VoidResult SetSize(uint32_t newSize); 177 VoidResult VersionedEnqueue(const VersionedEntry& entry); 178 VoidResult DiscardFront(); 179 MaybeDwordResult ReadEntryKeyDword(const std::wstring& regKey, 180 const wchar_t* regName, bool expected); 181 MaybeStringResult ReadEntryKeyString(const std::wstring& regKey, 182 const wchar_t* regName, bool expected); 183 }; 184 185 #endif // __DEFAULT_BROWSER_AGENT_CACHE_H__ 186