1 // Copyright 2020 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/entropy_state.h"
6
7 #include "base/command_line.h"
8 #include "base/metrics/histogram_functions.h"
9 #include "base/rand_util.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "components/metrics/metrics_pref_names.h"
12 #include "components/metrics/metrics_switches.h"
13 #include "components/prefs/pref_service.h"
14
15 #if BUILDFLAG(IS_ANDROID)
16 #include "base/android/jni_android.h"
17 #include "components/metrics/jni_headers/LowEntropySource_jni.h"
18 #endif // BUILDFLAG(IS_ANDROID)
19
20 namespace metrics {
21
22 namespace {
23
24 // Generates a new non-identifying entropy source used to seed persistent
25 // activities. Make it static so that the new low entropy source value will
26 // only be generated on first access. And thus, even though we may write the
27 // new low entropy source value to prefs multiple times, it stays the same
28 // value.
GenerateLowEntropySource()29 int GenerateLowEntropySource() {
30 #if BUILDFLAG(IS_ANDROID)
31 // Note: As in the non-Android case below, the Java implementation also uses
32 // a static cache, so subsequent invocations will return the same value.
33 JNIEnv* env = base::android::AttachCurrentThread();
34 return Java_LowEntropySource_generateLowEntropySource(env);
35 #else
36 static const int low_entropy_source =
37 base::RandInt(0, EntropyState::kMaxLowEntropySize - 1);
38 return low_entropy_source;
39 #endif // BUILDFLAG(IS_ANDROID)
40 }
41
42 // Generates a new non-identifying low entropy source using the same method
43 // that's used for the actual low entropy source. This one, however, is only
44 // used for statistical validation, and *not* for randomization or experiment
45 // assignment.
GeneratePseudoLowEntropySource()46 int GeneratePseudoLowEntropySource() {
47 #if BUILDFLAG(IS_ANDROID)
48 // Note: As in the non-Android case below, the Java implementation also uses
49 // a static cache, so subsequent invocations will return the same value.
50 JNIEnv* env = base::android::AttachCurrentThread();
51 return Java_LowEntropySource_generatePseudoLowEntropySource(env);
52 #else
53 static const int pseudo_low_entropy_source =
54 base::RandInt(0, EntropyState::kMaxLowEntropySize - 1);
55 return pseudo_low_entropy_source;
56 #endif // BUILDFLAG(IS_ANDROID)
57 }
58
59 } // namespace
60
EntropyState(PrefService * local_state)61 EntropyState::EntropyState(PrefService* local_state)
62 : local_state_(local_state) {}
63
64 // static
65 constexpr int EntropyState::kLowEntropySourceNotSet;
66
67 // static
ClearPrefs(PrefService * local_state)68 void EntropyState::ClearPrefs(PrefService* local_state) {
69 local_state->ClearPref(prefs::kMetricsLowEntropySource);
70 local_state->ClearPref(prefs::kMetricsOldLowEntropySource);
71 local_state->ClearPref(prefs::kMetricsPseudoLowEntropySource);
72 }
73
74 // static
RegisterPrefs(PrefRegistrySimple * registry)75 void EntropyState::RegisterPrefs(PrefRegistrySimple* registry) {
76 registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource,
77 kLowEntropySourceNotSet);
78 registry->RegisterIntegerPref(prefs::kMetricsOldLowEntropySource,
79 kLowEntropySourceNotSet);
80 registry->RegisterIntegerPref(prefs::kMetricsPseudoLowEntropySource,
81 kLowEntropySourceNotSet);
82 }
83
GetHighEntropySource(const std::string & initial_client_id)84 std::string EntropyState::GetHighEntropySource(
85 const std::string& initial_client_id) {
86 DCHECK(!initial_client_id.empty());
87 // For metrics reporting-enabled users, we combine the client ID and low
88 // entropy source to get the final entropy source.
89 // This has two useful properties:
90 // 1) It makes the entropy source less identifiable for parties that do not
91 // know the low entropy source.
92 // 2) It makes the final entropy source resettable.
93
94 // If this install has an old low entropy source, continue using it, to avoid
95 // changing the group assignments of studies using high entropy. New installs
96 // only have the new low entropy source. If the number of installs with old
97 // sources ever becomes small enough (see UMA.LowEntropySourceValue), we could
98 // remove it, and just use the new source here.
99 int low_entropy_source = GetOldLowEntropySource();
100 if (low_entropy_source == kLowEntropySourceNotSet)
101 low_entropy_source = GetLowEntropySource();
102
103 return initial_client_id + base::NumberToString(low_entropy_source);
104 }
105
GetLowEntropySource()106 int EntropyState::GetLowEntropySource() {
107 UpdateLowEntropySources();
108 return low_entropy_source_;
109 }
110
GetPseudoLowEntropySource()111 int EntropyState::GetPseudoLowEntropySource() {
112 UpdateLowEntropySources();
113 return pseudo_low_entropy_source_;
114 }
115
GetOldLowEntropySource()116 int EntropyState::GetOldLowEntropySource() {
117 UpdateLowEntropySources();
118 return old_low_entropy_source_;
119 }
120
UpdateLowEntropySources()121 void EntropyState::UpdateLowEntropySources() {
122 // The default value for |low_entropy_source_| and the default pref value are
123 // both |kLowEntropySourceNotSet|, which indicates the value has not been set.
124 if (low_entropy_source_ != kLowEntropySourceNotSet &&
125 pseudo_low_entropy_source_ != kLowEntropySourceNotSet)
126 return;
127
128 const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess());
129 // Only try to load the value from prefs if the user did not request a reset.
130 // Otherwise, skip to generating a new value. We would have already returned
131 // if both |low_entropy_source_| and |pseudo_low_entropy_source_| were set,
132 // ensuring we only do this reset on the first call to
133 // UpdateLowEntropySources().
134 if (!command_line->HasSwitch(switches::kResetVariationState)) {
135 int new_pref = local_state_->GetInteger(prefs::kMetricsLowEntropySource);
136 if (IsValidLowEntropySource(new_pref))
137 low_entropy_source_ = new_pref;
138 int old_pref = local_state_->GetInteger(prefs::kMetricsOldLowEntropySource);
139 if (IsValidLowEntropySource(old_pref))
140 old_low_entropy_source_ = old_pref;
141 int pseudo_pref =
142 local_state_->GetInteger(prefs::kMetricsPseudoLowEntropySource);
143 if (IsValidLowEntropySource(pseudo_pref))
144 pseudo_low_entropy_source_ = pseudo_pref;
145 }
146
147 // If the new source is missing or corrupt (or requested to be reset), then
148 // (re)create it. Don't bother recreating the old source if it's corrupt,
149 // because we only keep the old source around for consistency, and we can't
150 // maintain a consistent value if we recreate it.
151 if (low_entropy_source_ == kLowEntropySourceNotSet) {
152 low_entropy_source_ = GenerateLowEntropySource();
153 DCHECK(IsValidLowEntropySource(low_entropy_source_));
154 local_state_->SetInteger(prefs::kMetricsLowEntropySource,
155 low_entropy_source_);
156 }
157
158 // If the pseudo source is missing or corrupt (or requested to be reset), then
159 // (re)create it. Don't bother recreating the old source if it's corrupt,
160 // because we only keep the old source around for consistency, and we can't
161 // maintain a consistent value if we recreate it.
162 if (pseudo_low_entropy_source_ == kLowEntropySourceNotSet) {
163 pseudo_low_entropy_source_ = GeneratePseudoLowEntropySource();
164 DCHECK(IsValidLowEntropySource(pseudo_low_entropy_source_));
165 local_state_->SetInteger(prefs::kMetricsPseudoLowEntropySource,
166 pseudo_low_entropy_source_);
167 }
168
169 // If the old source was present but corrupt (or requested to be reset), then
170 // we'll never use it again, so delete it.
171 if (old_low_entropy_source_ == kLowEntropySourceNotSet &&
172 local_state_->HasPrefPath(prefs::kMetricsOldLowEntropySource)) {
173 local_state_->ClearPref(prefs::kMetricsOldLowEntropySource);
174 }
175
176 DCHECK_NE(low_entropy_source_, kLowEntropySourceNotSet);
177 }
178
179 // static
IsValidLowEntropySource(int value)180 bool EntropyState::IsValidLowEntropySource(int value) {
181 return value >= 0 && value < kMaxLowEntropySize;
182 }
183
184 } // namespace metrics
185