blob: 55d0e6586f3dffca3086931d7107fc4a2fa7ae25 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/metrics/entropy_state.h"
#include "base/command_line.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/metrics_switches.h"
#include "components/prefs/pref_service.h"
namespace metrics {
namespace {
// The argument used to generate a non-identifying entropy source. We want no
// more than 13 bits of entropy, so use this max to return a number in the range
// [0, 7999] as the entropy source (12.97 bits of entropy).
const int kMaxLowEntropySize = 8000;
// Generates a new non-identifying entropy source used to seed persistent
// activities. Using a NoDestructor so that the new low entropy source value
// will only be generated on first access. And thus, even though we may write
// the new low entropy source value to prefs multiple times, it stays the same
// value.
int GenerateLowEntropySource() {
static const base::NoDestructor<int> low_entropy_source(
[] { return base::RandInt(0, kMaxLowEntropySize - 1); }());
return *low_entropy_source;
}
} // namespace
EntropyState::EntropyState(PrefService* local_state)
: local_state_(local_state) {}
// static
constexpr int EntropyState::kLowEntropySourceNotSet;
void EntropyState::ClearPrefs() {
DCHECK_EQ(kLowEntropySourceNotSet, low_entropy_source_);
local_state_->ClearPref(prefs::kMetricsLowEntropySource);
local_state_->ClearPref(prefs::kMetricsOldLowEntropySource);
}
// static
void EntropyState::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource,
kLowEntropySourceNotSet);
registry->RegisterIntegerPref(prefs::kMetricsOldLowEntropySource,
kLowEntropySourceNotSet);
}
std::string EntropyState::GetHighEntropySource(
const std::string& initial_client_id) {
DCHECK(!initial_client_id.empty());
// For metrics reporting-enabled users, we combine the client ID and low
// entropy source to get the final entropy source.
// This has two useful properties:
// 1) It makes the entropy source less identifiable for parties that do not
// know the low entropy source.
// 2) It makes the final entropy source resettable.
// If this install has an old low entropy source, continue using it, to avoid
// changing the group assignments of studies using high entropy. New installs
// only have the new low entropy source. If the number of installs with old
// sources ever becomes small enough (see UMA.LowEntropySourceValue), we could
// remove it, and just use the new source here.
int low_entropy_source = GetOldLowEntropySource();
if (low_entropy_source == kLowEntropySourceNotSet)
low_entropy_source = GetLowEntropySource();
return initial_client_id + base::NumberToString(low_entropy_source);
}
int EntropyState::GetLowEntropySource() {
UpdateLowEntropySources();
return low_entropy_source_;
}
int EntropyState::GetOldLowEntropySource() {
UpdateLowEntropySources();
return old_low_entropy_source_;
}
void EntropyState::UpdateLowEntropySources() {
// The default value for |low_entropy_source_| and the default pref value are
// both |kLowEntropySourceNotSet|, which indicates the value has not been set.
if (low_entropy_source_ != kLowEntropySourceNotSet)
return;
const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess());
// Only try to load the value from prefs if the user did not request a reset.
// Otherwise, skip to generating a new value. We would have already returned
// if |low_entropy_source_| were set, ensuring we only do this reset on the
// first call to UpdateLowEntropySources().
if (!command_line->HasSwitch(switches::kResetVariationState)) {
int new_pref = local_state_->GetInteger(prefs::kMetricsLowEntropySource);
if (IsValidLowEntropySource(new_pref))
low_entropy_source_ = new_pref;
int old_pref = local_state_->GetInteger(prefs::kMetricsOldLowEntropySource);
if (IsValidLowEntropySource(old_pref))
old_low_entropy_source_ = old_pref;
}
// If the new source is missing or corrupt (or requested to be reset), then
// (re)create it. Don't bother recreating the old source if it's corrupt,
// because we only keep the old source around for consistency, and we can't
// maintain a consistent value if we recreate it.
if (low_entropy_source_ == kLowEntropySourceNotSet) {
low_entropy_source_ = GenerateLowEntropySource();
DCHECK(IsValidLowEntropySource(low_entropy_source_));
local_state_->SetInteger(prefs::kMetricsLowEntropySource,
low_entropy_source_);
}
// If the old source was present but corrupt (or requested to be reset), then
// we'll never use it again, so delete it.
if (old_low_entropy_source_ == kLowEntropySourceNotSet &&
local_state_->HasPrefPath(prefs::kMetricsOldLowEntropySource)) {
local_state_->ClearPref(prefs::kMetricsOldLowEntropySource);
}
DCHECK_NE(low_entropy_source_, kLowEntropySourceNotSet);
// TODO(crbug/1041710): Currently, these metrics might be recorded multiple
// times but that shouldn't matter because we can workaround it by using count
// unique user mode. Also, once we verify that we can persist
// low_entropy_source to our system profile proto, These two metrics are
// longer needed and should be removed.
base::UmaHistogramSparse("UMA.LowEntropySource3Value", low_entropy_source_);
if (old_low_entropy_source_ != kLowEntropySourceNotSet) {
base::UmaHistogramSparse("UMA.LowEntropySourceValue",
old_low_entropy_source_);
}
}
// static
bool EntropyState::IsValidLowEntropySource(int value) {
return value >= 0 && value < kMaxLowEntropySize;
}
} // namespace metrics