summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/defaultagent/Cache.h
blob: a3c825d4f2be28b200f2e691c814337c6dd635c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 __DEFAULT_BROWSER_AGENT_CACHE_H__
#define __DEFAULT_BROWSER_AGENT_CACHE_H__

#include <cstdint>
#include <string>
#include <windows.h>

#include "Registry.h"

using DwordResult = mozilla::WindowsErrorResult<uint32_t>;

/**
 * This cache functions as a FIFO queue which writes its data to the Windows
 * registry.
 *
 * Note that the cache is not thread-safe, so it is recommended that the WDBA's
 * RegistryMutex be acquired before accessing it.
 *
 * Some of the terminology used in this module is a easy to mix up, so let's
 * just be clear about it:
 *  - registry key/sub-key
 *       A registry key is sort of like the registry's equivalent of a
 *       directory. It can contain values, each of which is made up of a name
 *       and corresponding data. We may also refer to a "sub-key", meaning a
 *       registry key nested in a registry key.
 *  - cache key/entry key
 *       A cache key refers to the string that we use to look up a single
 *       element of cache entry data. Example: "CacheEntryVersion"
 *  - entry
 *      This refers to an entire record stored using Cache::Enqueue or retrieved
 *      using Cache::Dequeue. It consists of numerous cache keys and their
 *      corresponding data.
 *
 * The first version of this cache was problematic because of how hard it was to
 * extend. This version attempts to overcome this. It first migrates all data
 * out of the version 1 cache. This means that the stored ping data will not
 * be accessible to out-of-date clients, but presumably they will eventually
 * be updated or the up-to-date client that performed the migration will send
 * the pings itself. Because the WDBA telemetry has no client ID, all analysis
 * is stateless, so even if the other clients send some pings before the stored
 * ones get sent, that's ok. The ordering isn't really important.
 *
 * This version of the cache attempts to correct the problem of how hard it was
 * to extend the old cache. The biggest problem that the old cache had was that
 * when it dequeued data it had to shift data, but it wouldn't shift keys that
 * it didn't know about, causing them to become associated with the wrong cache
 * entries.
 *
 * Version 2 of the cache will make 4 improvements to attempt to avoid problems
 * like this in the future:
 *  1. Each cache entry will get its own registry key. This will help to keep
 *     cache entries isolated from each other.
 *  2. Each cache entry will include version data so that we know what cache
 *     keys to expect when we read it.
 *  3. Rather than having to shift every entry every time we dequeue, we will
 *     implement a circular queue so that we just have to update what index
 *     currently represents the front
 *  4. We will store the cache capacity in the cache so that we can expand the
 *     cache later, if we want, without breaking previous versions.
 */
class Cache {
 public:
  // cacheRegKey is the registry sub-key that the cache will be stored in. If
  // null is passed (the default), we will use the default cache name. This is
  // what ought to be used in production. When testing, we will pass a different
  // key in so that our testing caches don't conflict with each other or with
  // a possible production cache on the test machine.
  explicit Cache(const wchar_t* cacheRegKey = nullptr);
  ~Cache();

  // The version of the cache (not to be confused with the version of the cache
  // entries). This should only be incremented if we need to make breaking
  // changes that require migration to a new cache location, like we did between
  // versions 1 and 2. This value will be used as part of the sub-key that the
  // cache is stored in (ex: "PingCache\version2").
  static constexpr const uint32_t kVersion = 2;
  // This value will be written into each entry. This allows us to know what
  // cache keys to expect in the event that additional cache keys are added in
  // later entry versions.
  static constexpr const uint32_t kEntryVersion = 2;
  static constexpr const uint32_t kDefaultCapacity = 2;
  // We want to allow the cache to be expandable, but we don't really want it to
  // be infinitely expandable. So we'll set an upper bound.
  static constexpr const uint32_t kMaxCapacity = 100;
  static constexpr const wchar_t* kDefaultPingCacheRegKey = L"PingCache";

  // Used to read the version 1 cache entries during data migration. Full cache
  // key names are formatted like: "<keyPrefix><baseKeyName><cacheIndex>"
  // For example: "PingCacheNotificationType0"
  static constexpr const wchar_t* kVersion1KeyPrefix = L"PingCache";
  static constexpr const uint32_t kVersion1MaxSize = 2;

  static constexpr const wchar_t* kCapacityRegName = L"Capacity";
  static constexpr const wchar_t* kFrontRegName = L"Front";
  static constexpr const wchar_t* kSizeRegName = L"Size";

  // Cache Entry keys
  static constexpr const wchar_t* kEntryVersionKey = L"CacheEntryVersion";
  // Note that the next 3 must also match the base key names from version 1
  // since we use them to construct those key names.
  static constexpr const wchar_t* kNotificationTypeKey = L"NotificationType";
  static constexpr const wchar_t* kNotificationShownKey = L"NotificationShown";
  static constexpr const wchar_t* kNotificationActionKey =
      L"NotificationAction";
  static constexpr const wchar_t* kPrevNotificationActionKey =
      L"PrevNotificationAction";

  // The version key wasn't added until version 2, but we add it to the version
  // 1 entries when migrating them to the cache.
  static constexpr const uint32_t kInitialVersionEntryVersionKey = 1;
  static constexpr const uint32_t kInitialVersionNotificationTypeKey = 1;
  static constexpr const uint32_t kInitialVersionNotificationShownKey = 1;
  static constexpr const uint32_t kInitialVersionNotificationActionKey = 1;
  static constexpr const uint32_t kInitialVersionPrevNotificationActionKey = 2;

  // We have two cache entry structs: one for the current version, and one
  // generic one that can handle any version. There are a couple of reasons
  // for this:
  //  - We only want to support writing the current version, but we want to
  //    support reading any version.
  //  - It makes things a bit nicer for the caller when Enqueue-ing, since
  //    they don't have to set the version or wrap values that were added
  //    later in a mozilla::Maybe.
  //  - It keeps us from having to worry about writing an invalid cache entry,
  //    such as one that claims to be version 2, but doesn't have
  //    prevNotificationAction.
  // Note that the entry struct for the current version does not contain a
  // version member value because we already know that its version is equal to
  // Cache::kEntryVersion.
  struct Entry {
    std::string notificationType;
    std::string notificationShown;
    std::string notificationAction;
    std::string prevNotificationAction;
  };
  struct VersionedEntry {
    uint32_t entryVersion;
    std::string notificationType;
    std::string notificationShown;
    std::string notificationAction;
    mozilla::Maybe<std::string> prevNotificationAction;
  };

  using MaybeEntry = mozilla::Maybe<VersionedEntry>;
  using MaybeEntryResult = mozilla::WindowsErrorResult<MaybeEntry>;

  VoidResult Init();
  VoidResult Enqueue(const Entry& entry);
  MaybeEntryResult Dequeue();

 private:
  const std::wstring mCacheRegKey;

  // We can't easily copy a VoidResult, so just store the raw HRESULT here.
  mozilla::Maybe<HRESULT> mInitializeResult;
  // How large the cache will grow before it starts rejecting new entries.
  uint32_t mCapacity;
  // The index of the first present cache entry.
  uint32_t mFront;
  // How many entries are present in the cache.
  uint32_t mSize;

  DwordResult EnsureDwordSetting(const wchar_t* regName, uint32_t defaultValue);
  VoidResult SetupCache();
  VoidResult MaybeMigrateVersion1();
  std::wstring MakeEntryRegKeyName(uint32_t index);
  VoidResult WriteEntryKeys(uint32_t index, const VersionedEntry& entry);
  VoidResult DeleteEntry(uint32_t index);
  VoidResult SetFront(uint32_t newFront);
  VoidResult SetSize(uint32_t newSize);
  VoidResult VersionedEnqueue(const VersionedEntry& entry);
  VoidResult DiscardFront();
  MaybeDwordResult ReadEntryKeyDword(const std::wstring& regKey,
                                     const wchar_t* regName, bool expected);
  MaybeStringResult ReadEntryKeyString(const std::wstring& regKey,
                                       const wchar_t* regName, bool expected);
};

#endif  // __DEFAULT_BROWSER_AGENT_CACHE_H__