1// Copyright 2014 The Chromium Authors. All rights reserved.
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/variations/caching_permuted_entropy_provider.h"
6
7#include <string>
8
9#include "base/base64.h"
10#include "base/logging.h"
11#include "base/prefs/pref_registry_simple.h"
12#include "base/prefs/pref_service.h"
13#include "components/variations/pref_names.h"
14
15namespace metrics {
16
17CachingPermutedEntropyProvider::CachingPermutedEntropyProvider(
18    PrefService* local_state,
19    uint16 low_entropy_source,
20    size_t low_entropy_source_max)
21    : PermutedEntropyProvider(low_entropy_source, low_entropy_source_max),
22      local_state_(local_state) {
23  ReadFromLocalState();
24}
25
26CachingPermutedEntropyProvider::~CachingPermutedEntropyProvider() {
27}
28
29// static
30void CachingPermutedEntropyProvider::RegisterPrefs(
31    PrefRegistrySimple* registry) {
32  registry->RegisterStringPref(prefs::kVariationsPermutedEntropyCache,
33                               std::string());
34}
35
36// static
37void CachingPermutedEntropyProvider::ClearCache(PrefService* local_state) {
38  local_state->ClearPref(prefs::kVariationsPermutedEntropyCache);
39}
40
41uint16 CachingPermutedEntropyProvider::GetPermutedValue(
42    uint32 randomization_seed) const {
43  DCHECK(thread_checker_.CalledOnValidThread());
44
45  uint16 value = 0;
46  if (!FindValue(randomization_seed, &value)) {
47    value = PermutedEntropyProvider::GetPermutedValue(randomization_seed);
48    AddToCache(randomization_seed, value);
49  }
50  return value;
51}
52
53void CachingPermutedEntropyProvider::ReadFromLocalState() const {
54  const std::string base64_cache_data =
55      local_state_->GetString(prefs::kVariationsPermutedEntropyCache);
56  std::string cache_data;
57  if (!base::Base64Decode(base64_cache_data, &cache_data) ||
58      !cache_.ParseFromString(cache_data)) {
59    local_state_->ClearPref(prefs::kVariationsPermutedEntropyCache);
60    NOTREACHED();
61  }
62}
63
64void CachingPermutedEntropyProvider::UpdateLocalState() const {
65  std::string serialized;
66  cache_.SerializeToString(&serialized);
67
68  std::string base64_encoded;
69  base::Base64Encode(serialized, &base64_encoded);
70  local_state_->SetString(prefs::kVariationsPermutedEntropyCache,
71                          base64_encoded);
72}
73
74void CachingPermutedEntropyProvider::AddToCache(uint32 randomization_seed,
75                                                uint16 value) const {
76  PermutedEntropyCache::Entry* entry;
77  const int kMaxSize = 25;
78  if (cache_.entry_size() >= kMaxSize) {
79    // If the cache is full, evict the first entry, swapping later entries in
80    // to take its place. This effectively creates a FIFO cache, which is good
81    // enough here because the expectation is that there shouldn't be more than
82    // |kMaxSize| field trials at any given time, so eviction should happen very
83    // rarely, only as new trials are introduced, evicting old expired trials.
84    for (int i = 1; i < kMaxSize; ++i)
85      cache_.mutable_entry()->SwapElements(i - 1, i);
86    entry = cache_.mutable_entry(kMaxSize - 1);
87  } else {
88    entry = cache_.add_entry();
89  }
90
91  entry->set_randomization_seed(randomization_seed);
92  entry->set_value(value);
93
94  UpdateLocalState();
95}
96
97bool CachingPermutedEntropyProvider::FindValue(uint32 randomization_seed,
98                                               uint16* value) const {
99  for (int i = 0; i < cache_.entry_size(); ++i) {
100    if (cache_.entry(i).randomization_seed() == randomization_seed) {
101      *value = cache_.entry(i).value();
102      return true;
103    }
104  }
105  return false;
106}
107
108}  // namespace metrics
109