xref: /aosp_15_r20/external/cronet/components/metrics/structured/lib/key_data.h (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 #ifndef COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_
6 #define COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_
7 
8 #include <memory>
9 #include <optional>
10 #include <string>
11 #include <string_view>
12 
13 #include "base/memory/scoped_refptr.h"
14 #include "base/memory/weak_ptr.h"
15 #include "base/sequence_checker.h"
16 #include "base/task/sequenced_task_runner.h"
17 #include "base/time/time.h"
18 #include "components/metrics/structured/lib/persistent_proto.h"
19 #include "components/metrics/structured/lib/proto/key.pb.h"
20 
21 namespace metrics::structured {
22 
23 // KeyData is the central class for managing keys and generating hashes for
24 // structured metrics.
25 //
26 // The class maintains one key and its rotation data for every project defined
27 // in /tools/metrics/structured/sync/structured.xml. This can be used to
28 // generate:
29 //  - an ID for the project with KeyData::Id.
30 //  - a hash of a given value for an event with KeyData::HmacMetric.
31 //
32 // Every project has a uint64_t project_name_hash that is generated by taking
33 // the first 8 bytes of MD5 hash of the project name. Keys for the project are
34 // retrieved using this project_name_hash. For more details, refer to
35 // //tools/metrics/structured/ccodegen.py.
36 //
37 // KeyData performs key rotation. Every project is associated with a rotation
38 // period, which is 90 days unless specified in structured.xml. Keys are rotated
39 // with a resolution of one day. They are guaranteed not to be used for
40 // HmacMetric or UserProjectId for longer than their rotation period, except in
41 // cases of local clock changes.
42 //
43 // When first created, every project's key rotation date is selected uniformly
44 // so that there is an even distribution of rotations across users. This means
45 // that, for most users, the first rotation period will be shorter than the
46 // standard full rotation period for that project.
47 class KeyData {
48  public:
49   // Delegate to read and upsert keys.
50   class StorageDelegate {
51    public:
52     virtual ~StorageDelegate() = default;
53 
54     // Returns if the delegate is ready to read or upsert keys.
55     virtual bool IsReady() const = 0;
56 
57     // Returns the key associated with |project_name_hash|.
58     //
59     // If the key does not exist yet, then returns nullptr. Note that this will
60     // return the expired key if it needs to be rotated.
61     virtual const KeyProto* GetKey(uint64_t project_name_hash) const = 0;
62 
63     // Upserts the key for |project_name_hash| with duration
64     // |key_rotation_period| and last updated time |last_key_rotation|.
65     //
66     // |last_key_rotation| is the TimeDelta from base::Time::UnixEpoch in
67     // which the key was last rotated.
68     virtual void UpsertKey(uint64_t project_name_hash,
69                            base::TimeDelta last_key_rotation,
70                            base::TimeDelta key_rotation_period) = 0;
71 
72     // Clears all key data.
73     virtual void Purge() = 0;
74   };
75 
76   // Key data will use |storage_delegate| to read and upsert keys.
77   explicit KeyData(std::unique_ptr<StorageDelegate> storage_delegate);
78 
79   KeyData(const KeyData&) = delete;
80   KeyData& operator=(const KeyData&) = delete;
81 
82   ~KeyData();
83 
84   // Returns a digest of |value| for |metric| in the context of
85   // |project_name_hash|. Terminology: a metric is a (name, value) pair, and an
86   // event is a bundle of metrics. Each event is associated with a project.
87   //
88   //  - |project_name_hash| is the uint64 name hash of a project.
89   //  - |metric_name_hash| is the uint64 name hash of a metric.
90   //  - |value| is the string value to hash.
91   //
92   // The result is the HMAC digest of the |value| salted with |metric|, using
93   // the key for |project_name_hash|. That is:
94   //
95   //   HMAC_SHA256(key(project_name_hash), concat(value, hex(event),
96   //   hex(metric)))
97   //
98   // Returns 0u in case of an error.
99   //
100   // TODO(b/316419439): Change |key_rotation_period| to base::TimeDelta.
101   uint64_t HmacMetric(uint64_t project_name_hash,
102                       uint64_t metric_name_hash,
103                       const std::string& value,
104                       int key_rotation_period);
105 
106   // Returns an ID for this (user, |project_name_hash|) pair.
107   // |project_name_hash| is the name of a project, represented by the first 8
108   // bytes of the MD5 hash of its name defined in structured.xml.
109   //
110   // The derived ID is the first 8 bytes of SHA256(key(project_name_hash)).
111   // Returns 0u in case of an error.
112   //
113   // This ID is intended as the only ID for the events of a particular
114   // structured metrics project. However, events are uploaded from the device
115   // alongside the UMA client ID, which is only removed after the event reaches
116   // the server. This means events are associated with the client ID when
117   // uploaded from the device. See the class comment of
118   // StructuredMetricsProvider for more details.
119   //
120   // Default |key_rotation_period| is 90 days.
121   //
122   // TODO(b/316419439): Change |key_rotation_period| to base::TimeDelta.
123   uint64_t Id(uint64_t project_name_hash, int key_rotation_period);
124 
125   // Returns when the key for |project_name_hash| was last rotated, in days
126   // since epoch. Returns nullopt if the key doesn't exist.
127   //
128   // TODO(b/316419439): Change |key_rotation_period| to base::TimeDelta.
129   std::optional<int> LastKeyRotation(uint64_t project_name_hash) const;
130 
131   // Return the age of the key for |project_name_hash| since the last rotation,
132   // in weeks.
133   std::optional<int> GetKeyAgeInWeeks(uint64_t project_name_hash) const;
134 
135   // Clears all key data.
136   void Purge();
137 
138  private:
139   // Ensure that a valid key exists for |project|. If a key doesn't exist OR if
140   // the key needs to be rotated, then a new key with |key_rotation_period| will
141   // be created.
142   //
143   // This function assumes that |storage_delegate_->IsReady()| is true.
144   void EnsureKeyUpdated(uint64_t project_name_hash,
145                         base::TimeDelta key_rotation_period);
146 
147   // Retrieves the bytes of the key associated with |project_name_hash|.
148   // If the key does not exist OR if the key is not of size |kKeySize|, returns
149   // std::nullopt .
150   const std::optional<std::string_view> GetKeyBytes(
151       uint64_t project_name_hash) const;
152 
153   // Delegate that handles reading and upserting keys.
154   std::unique_ptr<KeyData::StorageDelegate> storage_delegate_;
155 };
156 
157 }  // namespace metrics::structured
158 
159 #endif  // COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_
160