xref: /aosp_15_r20/external/cronet/components/metrics/structured/lib/key_data_file_delegate_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/metrics/structured/lib/key_data_file_delegate.h"
6 
7 #include <memory>
8 #include <string>
9 #include <utility>
10 
11 #include "base/containers/flat_set.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/files/scoped_temp_dir.h"
15 #include "base/functional/callback_helpers.h"
16 #include "base/memory/raw_ptr.h"
17 #include "base/run_loop.h"
18 #include "base/strings/strcat.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/test/metrics/histogram_tester.h"
21 #include "base/test/task_environment.h"
22 #include "base/values.h"
23 #include "components/metrics/structured/lib/histogram_util.h"
24 #include "components/metrics/structured/lib/proto/key.pb.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26 
27 namespace metrics::structured {
28 
29 namespace {
30 
31 // 32 byte long test key, matching the size of a real key.
32 constexpr char kKey[] = "abcdefghijklmnopqrstuvwxyzabcdef";
33 
34 // These project, event, and metric names are used for testing.
35 // - project: TestProjectOne
36 //   - event: TestEventOne
37 //     - metric: TestMetricOne
38 //     - metric: TestMetricTwo
39 // - project: TestProjectTwo
40 
41 // The name hash of "TestProjectOne".
42 constexpr uint64_t kProjectOneHash = UINT64_C(16881314472396226433);
43 // The name hash of "TestProjectTwo".
44 constexpr uint64_t kProjectTwoHash = UINT64_C(5876808001962504629);
45 
46 // The name hash of "TestMetricOne".
47 constexpr uint64_t kMetricOneHash = UINT64_C(637929385654885975);
48 // The name hash of "TestMetricTwo".
49 constexpr uint64_t kMetricTwoHash = UINT64_C(14083999144141567134);
50 
51 // The hex-encoded frst 8 bytes of SHA256(kKey), ie. the user ID for key kKey.
52 constexpr char kUserId[] = "2070DF23E0D95759";
53 
54 // Test values and their hashes. Hashes are the first 8 bytes of:
55 // HMAC_SHA256(concat(hex(kMetricNHash), kValueN), kKey)
56 constexpr char kValueOne[] = "value one";
57 constexpr char kValueTwo[] = "value two";
58 constexpr char kValueOneHash[] = "805B8790DC69B773";
59 constexpr char kValueTwoHash[] = "87CEF12FB15E0B3A";
60 
61 constexpr int kKeyRotationPeriod = 90;
62 
HashToHex(const uint64_t hash)63 std::string HashToHex(const uint64_t hash) {
64   return base::HexEncode(&hash, sizeof(uint64_t));
65 }
66 
67 }  // namespace
68 
69 class KeyDataFileDelegateTest : public testing::Test {
70  protected:
SetUp()71   void SetUp() override {
72     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
73     // Move the mock date forward from day 0, because KeyDataFileDelegate
74     // assumes that day 0 is a bug.
75     task_environment_.AdvanceClock(base::Days(1000));
76   }
77 
TearDown()78   void TearDown() override { ResetKeyData(); }
79 
ResetKeyData()80   void ResetKeyData() {
81     // Manually deconstruct objects in the right order to avoid dangling raw
82     // pointer.
83     key_data_file_ = nullptr;
84     key_data_.reset();
85   }
86 
ResetState()87   void ResetState() {
88     ResetKeyData();
89     base::DeleteFile(GetPath());
90     ASSERT_FALSE(base::PathExists(GetPath()));
91   }
92 
GetPath()93   base::FilePath GetPath() {
94     return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("keys"));
95   }
96 
MakeKeyDataFileDelegate()97   void MakeKeyDataFileDelegate() {
98     auto key_data_file = std::make_unique<KeyDataFileDelegate>(
99         GetPath(), base::Seconds(0), base::DoNothing());
100     key_data_file_ = key_data_file.get();
101     key_data_ = std::make_unique<KeyData>(std::move(key_data_file));
102     Wait();
103   }
104 
SaveKeyData()105   void SaveKeyData() {
106     key_data_file_->WriteNowForTesting();
107     Wait();
108     ASSERT_TRUE(base::PathExists(GetPath()));
109   }
110 
Today()111   int Today() { return (base::Time::Now() - base::Time::UnixEpoch()).InDays(); }
112 
113   // Read the on-disk file and return the information about the key for
114   // |project_name_hash|. Fails if a key does not exist.
GetKey(const uint64_t project_name_hash)115   KeyProto GetKey(const uint64_t project_name_hash) {
116     std::string proto_str;
117     CHECK(base::ReadFileToString(GetPath(), &proto_str));
118     KeyDataProto proto;
119     CHECK(proto.ParseFromString(proto_str));
120 
121     const auto it = proto.keys().find(project_name_hash);
122     CHECK(it != proto.keys().end());
123     return it->second;
124   }
125 
126   // Write a KeyDataProto to disk with a single key described by the
127   // arguments.
SetupKey(const uint64_t project_name_hash,const std::string & key,const int last_rotation,const int rotation_period)128   void SetupKey(const uint64_t project_name_hash,
129                 const std::string& key,
130                 const int last_rotation,
131                 const int rotation_period) {
132     // It's a test logic error for the key data to exist when calling SetupKey,
133     // because it will desync the in-memory proto from the underlying storage.
134     ASSERT_FALSE(key_data_);
135 
136     KeyDataProto proto;
137     KeyProto& key_proto = (*proto.mutable_keys())[project_name_hash];
138     key_proto.set_key(key);
139     key_proto.set_last_rotation(last_rotation);
140     key_proto.set_rotation_period(rotation_period);
141 
142     ASSERT_TRUE(base::WriteFile(GetPath(), proto.SerializeAsString()));
143   }
144 
Wait()145   void Wait() { task_environment_.RunUntilIdle(); }
146 
ExpectNoErrors()147   void ExpectNoErrors() {
148     histogram_tester_.ExpectTotalCount("UMA.StructuredMetrics.InternalError",
149                                        0);
150   }
151 
ExpectKeyValidation(const int valid,const int created,const int rotated)152   void ExpectKeyValidation(const int valid,
153                            const int created,
154                            const int rotated) {
155     static const std::string histogram =
156         "UMA.StructuredMetrics.KeyValidationState";
157     histogram_tester_.ExpectBucketCount(histogram, KeyValidationState::kValid,
158                                         valid);
159     histogram_tester_.ExpectBucketCount(histogram, KeyValidationState::kCreated,
160                                         created);
161     histogram_tester_.ExpectBucketCount(histogram, KeyValidationState::kRotated,
162                                         rotated);
163   }
164 
165   base::test::TaskEnvironment task_environment_{
166       base::test::TaskEnvironment::MainThreadType::UI,
167       base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED,
168       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
169   base::ScopedTempDir temp_dir_;
170   base::HistogramTester histogram_tester_;
171 
172   std::unique_ptr<KeyData> key_data_;
173   raw_ptr<KeyDataFileDelegate> key_data_file_;
174 };
175 
176 // If there is no key store file present, check that new keys are generated for
177 // each project, and those keys are of the right length and different from each
178 // other.
TEST_F(KeyDataFileDelegateTest,GeneratesKeysForProjects)179 TEST_F(KeyDataFileDelegateTest, GeneratesKeysForProjects) {
180   // Make key data and use two keys, in order to generate them.
181   MakeKeyDataFileDelegate();
182   key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
183   key_data_->Id(kProjectTwoHash, kKeyRotationPeriod);
184   SaveKeyData();
185 
186   const std::string key_one = GetKey(kProjectOneHash).key();
187   const std::string key_two = GetKey(kProjectTwoHash).key();
188 
189   EXPECT_EQ(key_one.size(), 32ul);
190   EXPECT_EQ(key_two.size(), 32ul);
191   EXPECT_NE(key_one, key_two);
192 
193   ExpectNoErrors();
194   ExpectKeyValidation(/*valid=*/0, /*created=*/2, /*rotated=*/0);
195 }
196 
197 // When repeatedly initialized with no key store file present, ensure the keys
198 // generated each time are distinct.
TEST_F(KeyDataFileDelegateTest,GeneratesDistinctKeys)199 TEST_F(KeyDataFileDelegateTest, GeneratesDistinctKeys) {
200   base::flat_set<std::string> keys;
201 
202   for (int i = 1; i <= 10; ++i) {
203     // Reset on-disk and in-memory state, regenerate the key, and save it to
204     // disk.
205     ResetState();
206     MakeKeyDataFileDelegate();
207     key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
208     SaveKeyData();
209 
210     keys.insert(GetKey(kProjectOneHash).key());
211     ExpectKeyValidation(/*valid=*/0, /*created=*/i, /*rotated=*/0);
212   }
213 
214   ExpectNoErrors();
215   EXPECT_EQ(keys.size(), 10ul);
216 }
217 
218 // If there is an existing key store file, check that its keys are not replaced.
TEST_F(KeyDataFileDelegateTest,ReuseExistingKeys)219 TEST_F(KeyDataFileDelegateTest, ReuseExistingKeys) {
220   // Create a file with one key.
221   MakeKeyDataFileDelegate();
222   const uint64_t id_one = key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
223   SaveKeyData();
224   ExpectKeyValidation(/*valid=*/0, /*created=*/1, /*rotated=*/0);
225   const std::string key_one = GetKey(kProjectOneHash).key();
226 
227   // Reset the in-memory state, leave the on-disk state intact.
228   ResetKeyData();
229 
230   // Open the file again and check we use the same key.
231   MakeKeyDataFileDelegate();
232   const uint64_t id_two = key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
233   SaveKeyData();
234   ExpectKeyValidation(/*valid=*/1, /*created=*/1, /*rotated=*/0);
235   const std::string key_two = GetKey(kProjectOneHash).key();
236 
237   EXPECT_EQ(id_one, id_two);
238   EXPECT_EQ(key_one, key_two);
239 }
240 
241 // Check that different events have different hashes for the same metric and
242 // value.
TEST_F(KeyDataFileDelegateTest,DifferentEventsDifferentHashes)243 TEST_F(KeyDataFileDelegateTest, DifferentEventsDifferentHashes) {
244   MakeKeyDataFileDelegate();
245   EXPECT_NE(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "value",
246                                   kKeyRotationPeriod),
247             key_data_->HmacMetric(kProjectTwoHash, kMetricOneHash, "value",
248                                   kKeyRotationPeriod));
249   ExpectNoErrors();
250 }
251 
252 // Check that an event has different hashes for different metrics with the same
253 // value.
TEST_F(KeyDataFileDelegateTest,DifferentMetricsDifferentHashes)254 TEST_F(KeyDataFileDelegateTest, DifferentMetricsDifferentHashes) {
255   MakeKeyDataFileDelegate();
256   EXPECT_NE(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "value",
257                                   kKeyRotationPeriod),
258             key_data_->HmacMetric(kProjectOneHash, kMetricTwoHash, "value",
259                                   kKeyRotationPeriod));
260   ExpectNoErrors();
261 }
262 
263 // Check that an event has different hashes for different values of the same
264 // metric.
TEST_F(KeyDataFileDelegateTest,DifferentValuesDifferentHashes)265 TEST_F(KeyDataFileDelegateTest, DifferentValuesDifferentHashes) {
266   MakeKeyDataFileDelegate();
267   EXPECT_NE(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "first",
268                                   kKeyRotationPeriod),
269             key_data_->HmacMetric(kProjectOneHash, kMetricOneHash, "second",
270                                   kKeyRotationPeriod));
271   ExpectNoErrors();
272 }
273 
274 // Ensure that KeyDataFileDelegate::UserId is the expected value of SHA256(key).
TEST_F(KeyDataFileDelegateTest,CheckUserIDs)275 TEST_F(KeyDataFileDelegateTest, CheckUserIDs) {
276   SetupKey(kProjectOneHash, kKey, Today(), kKeyRotationPeriod);
277 
278   MakeKeyDataFileDelegate();
279   EXPECT_EQ(HashToHex(key_data_->Id(kProjectOneHash, kKeyRotationPeriod)),
280             kUserId);
281   EXPECT_NE(HashToHex(key_data_->Id(kProjectTwoHash, kKeyRotationPeriod)),
282             kUserId);
283   ExpectKeyValidation(/*valid=*/1, /*created=*/1, /*rotated=*/0);
284   ExpectNoErrors();
285 }
286 
287 // Ensure that KeyDataFileDelegate::Hash returns expected values for a known key
288 // and value.
TEST_F(KeyDataFileDelegateTest,CheckHashes)289 TEST_F(KeyDataFileDelegateTest, CheckHashes) {
290   SetupKey(kProjectOneHash, kKey, Today(), kKeyRotationPeriod);
291 
292   MakeKeyDataFileDelegate();
293   EXPECT_EQ(HashToHex(key_data_->HmacMetric(kProjectOneHash, kMetricOneHash,
294                                             kValueOne, kKeyRotationPeriod)),
295             kValueOneHash);
296   EXPECT_EQ(HashToHex(key_data_->HmacMetric(kProjectOneHash, kMetricTwoHash,
297                                             kValueTwo, kKeyRotationPeriod)),
298             kValueTwoHash);
299   ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/0);
300   ExpectNoErrors();
301 }
302 
303 // Check that keys for a event are correctly rotated after a given rotation
304 // period.
TEST_F(KeyDataFileDelegateTest,KeysRotated)305 TEST_F(KeyDataFileDelegateTest, KeysRotated) {
306   const int start_day = Today();
307   SetupKey(kProjectOneHash, kKey, start_day, kKeyRotationPeriod);
308 
309   MakeKeyDataFileDelegate();
310   const uint64_t first_id = key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
311   EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash), start_day);
312   ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/0);
313 
314   {
315     // Advancing by |kKeyRotationPeriod|-1 days, the key should not be rotated.
316     task_environment_.AdvanceClock(base::Days(kKeyRotationPeriod - 1));
317     EXPECT_EQ(key_data_->Id(kProjectOneHash, kKeyRotationPeriod), first_id);
318     EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash), start_day);
319     SaveKeyData();
320 
321     ASSERT_EQ(GetKey(kProjectOneHash).last_rotation(), start_day);
322     ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/0);
323   }
324 
325   {
326     // Advancing by another |key_rotation_period|+1 days, the key should be
327     // rotated and the last rotation day should be incremented by
328     // |key_rotation_period|.
329     task_environment_.AdvanceClock(base::Days(kKeyRotationPeriod + 1));
330     EXPECT_NE(key_data_->Id(kProjectOneHash, kKeyRotationPeriod), first_id);
331     SaveKeyData();
332 
333     int expected_last_key_rotation = start_day + 2 * kKeyRotationPeriod;
334     EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
335               expected_last_key_rotation);
336     EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash),
337               expected_last_key_rotation);
338     ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/1);
339 
340     ASSERT_EQ(GetKey(kProjectOneHash).rotation_period(), kKeyRotationPeriod);
341   }
342 
343   {
344     // Advancing by |2* kKeyRotationPeriod| days, the last rotation day should
345     // now 4 periods of |kKeyRotationPeriod| days ahead.
346     task_environment_.AdvanceClock(base::Days(kKeyRotationPeriod * 2));
347     key_data_->Id(kProjectOneHash, kKeyRotationPeriod);
348     SaveKeyData();
349 
350     int expected_last_key_rotation = start_day + 4 * kKeyRotationPeriod;
351     EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
352               expected_last_key_rotation);
353     EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash),
354               expected_last_key_rotation);
355     ExpectKeyValidation(/*valid=*/2, /*created=*/0, /*rotated=*/2);
356   }
357 }
358 
359 // Check that keys with updated rotations are correctly rotated.
TEST_F(KeyDataFileDelegateTest,KeysWithUpdatedRotations)360 TEST_F(KeyDataFileDelegateTest, KeysWithUpdatedRotations) {
361   int first_key_rotation_period = 60;
362 
363   const int start_day = Today();
364   SetupKey(kProjectOneHash, kKey, start_day, first_key_rotation_period);
365 
366   MakeKeyDataFileDelegate();
367   const uint64_t first_id =
368       key_data_->Id(kProjectOneHash, first_key_rotation_period);
369   EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash), start_day);
370   ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/0);
371 
372   // Advance days by |new_key_rotation_period| + 1. This should fall within the
373   // rotation of the |new_key_rotation_period| but outside
374   // |first_key_rotation_period|.
375   int new_key_rotation_period = 50;
376   task_environment_.AdvanceClock(base::Days(new_key_rotation_period + 1));
377   const uint64_t second_id =
378       key_data_->Id(kProjectOneHash, new_key_rotation_period);
379   EXPECT_NE(first_id, second_id);
380   SaveKeyData();
381 
382   // Key should have been rotated with new_key_rotation_period.
383   int expected_last_key_rotation = start_day + new_key_rotation_period;
384   EXPECT_EQ(GetKey(kProjectOneHash).last_rotation(),
385             expected_last_key_rotation);
386   EXPECT_EQ(key_data_->LastKeyRotation(kProjectOneHash),
387             expected_last_key_rotation);
388   ExpectKeyValidation(/*valid=*/1, /*created=*/0, /*rotated=*/1);
389 }
390 
391 }  // namespace metrics::structured
392