File: cloned_install_detector.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (229 lines) | stat: -rw-r--r-- 8,062 bytes parent folder | download | duplicates (5)
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/metrics/cloned_install_detector.h"

#include <stdint.h>

#include <string>

#include "base/callback_list.h"
#include "base/check_is_test.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/metrics_hashes.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "components/metrics/machine_id_provider.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"

namespace metrics {

namespace {

MachineIdProvider* g_machine_id_provider_for_testing = nullptr;

bool HasMachineId() {
  if (g_machine_id_provider_for_testing) {
    CHECK_IS_TEST();
    return g_machine_id_provider_for_testing->HasId();
  }

  return MachineIdProvider().HasId();
}

std::string GetMachineId() {
  if (g_machine_id_provider_for_testing) {
    CHECK_IS_TEST();
    return g_machine_id_provider_for_testing->GetMachineId();
  }

  return MachineIdProvider().GetMachineId();
}

uint32_t HashRawId(const std::string& value) {
  uint64_t hash = base::HashMetricName(value);

  // Only use 24 bits from the 64-bit hash.
  return hash & ((1 << 24) - 1);
}

// State of the generated machine id in relation to the previously stored value.
// Note: UMA histogram enum - don't re-order or remove entries
enum MachineIdState {
  ID_GENERATION_FAILED,
  ID_NO_STORED_VALUE,
  ID_CHANGED,
  ID_UNCHANGED,
  ID_ENUM_SIZE
};

// Logs the state of generating a machine id and comparing it to a stored value.
void LogMachineIdState(MachineIdState state) {
  UMA_HISTOGRAM_ENUMERATION("UMA.MachineIdState", state, ID_ENUM_SIZE);
}

}  // namespace

ClonedInstallDetector::ClonedInstallDetector() = default;

ClonedInstallDetector::~ClonedInstallDetector() {
}

void ClonedInstallDetector::CheckForClonedInstall(PrefService* local_state) {
  if (!HasMachineId()) {
    return;
  }

  base::Time check_initiated_timestamp = base::Time::Now();
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&GetMachineId),
      base::BindOnce(&ClonedInstallDetector::SaveMachineId,
                     weak_ptr_factory_.GetWeakPtr(), local_state,
                     check_initiated_timestamp));
}

void ClonedInstallDetector::SaveMachineId(PrefService* local_state,
                                          base::Time check_initiated_timestamp,
                                          const std::string& raw_id) {
  if (raw_id.empty()) {
    LogMachineIdState(ID_GENERATION_FAILED);
    local_state->ClearPref(prefs::kMetricsMachineId);
    return;
  }

  int hashed_id = HashRawId(raw_id);

  MachineIdState id_state = ID_NO_STORED_VALUE;
  if (local_state->HasPrefPath(prefs::kMetricsMachineId)) {
    if (local_state->GetInteger(prefs::kMetricsMachineId) != hashed_id) {
      DCHECK(!detected_this_session_);
      id_state = ID_CHANGED;
      detected_this_session_ = true;

      local_state->SetBoolean(prefs::kMetricsResetIds, true);
      local_state->SetInt64(prefs::kSessionStartTimestampForLastClonedDetection,
                            check_initiated_timestamp.ToTimeT());

      callback_list_.Notify();
    } else {
      id_state = ID_UNCHANGED;
    }
  }

  LogMachineIdState(id_state);

  local_state->SetInteger(prefs::kMetricsMachineId, hashed_id);
}

bool ClonedInstallDetector::ShouldResetClientIds(PrefService* local_state) {
  // The existence of the pref indicates that it has been set when we saved the
  // MachineId and thus we need to update the member variable for this session
  // and clear the pref for future runs. We shouldn't clear the pref multiple
  // times because it may have been cloned again.
  if (!should_reset_client_ids_ &&
      local_state->HasPrefPath(prefs::kMetricsResetIds)) {
    should_reset_client_ids_ = local_state->GetBoolean(prefs::kMetricsResetIds);
    local_state->ClearPref(prefs::kMetricsResetIds);
  }

  return should_reset_client_ids_;
}

bool ClonedInstallDetector::ClonedInstallDetectedInCurrentSession() const {
  return detected_this_session_;
}

base::CallbackListSubscription
ClonedInstallDetector::AddOnClonedInstallDetectedCallback(
    base::OnceClosure callback) {
  if (detected_this_session_) {
    // If this install has already been detected as cloned, run the callback
    // immediately.
    std::move(callback).Run();
    return base::CallbackListSubscription();
  }
  return callback_list_.Add(std::move(callback));
}

void ClonedInstallDetector::SaveMachineIdForTesting(PrefService* local_state,
                                                    const std::string& raw_id) {
  SaveMachineId(local_state, base::Time::Now(), raw_id);
}

// static
void ClonedInstallDetector::SetMachineIdProviderForTesting(
    MachineIdProvider* machine_id_provider) {
  CHECK_IS_TEST();
  g_machine_id_provider_for_testing = machine_id_provider;
}

// static
void ClonedInstallDetector::RegisterPrefs(PrefRegistrySimple* registry) {
  registry->RegisterBooleanPref(prefs::kMetricsResetIds, false);
  registry->RegisterIntegerPref(prefs::kMetricsMachineId, 0);
  registry->RegisterIntegerPref(prefs::kClonedResetCount, 0);
  registry->RegisterInt64Pref(prefs::kFirstClonedResetTimestamp, 0);
  registry->RegisterInt64Pref(prefs::kLastClonedResetTimestamp, 0);
  registry->RegisterInt64Pref(
      prefs::kSessionStartTimestampForLastClonedDetection, 0);
}

ClonedInstallInfo ClonedInstallDetector::ReadClonedInstallInfo(
    PrefService* local_state) {
  return ClonedInstallInfo{
      .last_detection_timestamp = local_state->GetInt64(
          prefs::kSessionStartTimestampForLastClonedDetection),
      .last_reset_timestamp =
          local_state->GetInt64(prefs::kLastClonedResetTimestamp),
      .first_reset_timestamp =
          local_state->GetInt64(prefs::kFirstClonedResetTimestamp),
      .reset_count = local_state->GetInteger(prefs::kClonedResetCount)};
}

void ClonedInstallDetector::ClearClonedInstallInfo(PrefService* local_state) {
  local_state->ClearPref(prefs::kClonedResetCount);
  local_state->ClearPref(prefs::kFirstClonedResetTimestamp);
  local_state->ClearPref(prefs::kLastClonedResetTimestamp);
  local_state->ClearPref(prefs::kSessionStartTimestampForLastClonedDetection);
}

// static
void ClonedInstallDetector::RecordClonedInstallInfo(PrefService* local_state) {
  ClonedInstallInfo cloned = ReadClonedInstallInfo(local_state);

  // Make sure that at the first time of reset, the first_timestamp matches with
  // the last_timestamp.
  int64_t time = base::Time::Now().ToTimeT();

  // Only set |prefs::kFirstClonedResetTimestamp| when the client needs to be
  // reset due to cloned install for the first time.
  if (cloned.reset_count == 0) {
    local_state->SetInt64(prefs::kFirstClonedResetTimestamp, time);
  }
  local_state->SetInt64(prefs::kLastClonedResetTimestamp, time);
  local_state->SetInteger(prefs::kClonedResetCount, cloned.reset_count + 1);

  if (!cloned.last_detection_timestamp) {
    // `prefs::kSessionStartTimestampForLastClonedDetection` (which is read to
    // `last_detection_timestamp`) is expected to be set when we flag the
    // client as a clone to be reset. This timestamp might be missing,
    // especially on older milestones that did not set the timestamp yet, or if
    // the user toggled UMA opt-in during the session that detected the clone.
    // In this case, we can use the reset time as a fallback value. We might
    // then mistakenly attribute some events that happened on the current
    // (cloned) install as having taken place on the original install. That's
    // acceptable.
    local_state->SetInt64(prefs::kSessionStartTimestampForLastClonedDetection,
                          time);
  }
}

}  // namespace metrics