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 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
|
// Copyright 2014 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/variations/variations_http_header_provider.h"
#include <stddef.h>
#include <set>
#include <string>
#include <vector>
#include "base/base64.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/variations/proto/client_variations.pb.h"
namespace variations {
// static
VariationsHttpHeaderProvider* VariationsHttpHeaderProvider::GetInstance() {
return base::Singleton<VariationsHttpHeaderProvider>::get();
}
std::string VariationsHttpHeaderProvider::GetClientDataHeader(
bool is_signed_in) {
// Lazily initialize the header, if not already done, before attempting to
// transmit it.
InitVariationIDsCacheIfNeeded();
std::string variation_ids_header_copy;
{
base::AutoLock scoped_lock(lock_);
variation_ids_header_copy = is_signed_in
? cached_variation_ids_header_signed_in_
: cached_variation_ids_header_;
}
return variation_ids_header_copy;
}
std::string VariationsHttpHeaderProvider::GetVariationsString() {
InitVariationIDsCacheIfNeeded();
// Construct a space-separated string with leading and trailing spaces from
// the variations set. Note: The ids in it will be in sorted order per the
// std::set contract.
std::string ids_string = " ";
{
base::AutoLock scoped_lock(lock_);
for (const VariationIDEntry& entry : GetAllVariationIds()) {
if (entry.second == GOOGLE_WEB_PROPERTIES) {
ids_string.append(base::IntToString(entry.first));
ids_string.push_back(' ');
}
}
}
return ids_string;
}
bool VariationsHttpHeaderProvider::ForceVariationIds(
const std::string& command_line_variation_ids,
std::vector<std::string>* variation_ids) {
if (!command_line_variation_ids.empty()) {
// Combine |variation_ids| with |command_line_variation_ids|.
std::vector<std::string> variation_ids_flags =
base::SplitString(command_line_variation_ids, ",",
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
variation_ids->insert(variation_ids->end(), variation_ids_flags.begin(),
variation_ids_flags.end());
}
if (!variation_ids->empty()) {
// Create default variation ids which will always be included in the
// X-Client-Data request header.
return SetDefaultVariationIds(*variation_ids);
}
return true;
}
bool VariationsHttpHeaderProvider::SetDefaultVariationIds(
const std::vector<std::string>& variation_ids) {
default_variation_ids_set_.clear();
for (const std::string& entry : variation_ids) {
if (entry.empty()) {
default_variation_ids_set_.clear();
return false;
}
bool trigger_id =
base::StartsWith(entry, "t", base::CompareCase::SENSITIVE);
// Remove the "t" prefix if it's there.
std::string trimmed_entry = trigger_id ? entry.substr(1) : entry;
int variation_id = 0;
if (!base::StringToInt(trimmed_entry, &variation_id)) {
default_variation_ids_set_.clear();
return false;
}
default_variation_ids_set_.insert(VariationIDEntry(
variation_id,
trigger_id ? GOOGLE_WEB_PROPERTIES_TRIGGER : GOOGLE_WEB_PROPERTIES));
}
return true;
}
void VariationsHttpHeaderProvider::ResetForTesting() {
base::AutoLock scoped_lock(lock_);
// Stop observing field trials so that it can be restarted when this is
// re-inited. Note: This is a no-op if this is not currently observing.
base::FieldTrialList::RemoveObserver(this);
variation_ids_cache_initialized_ = false;
}
VariationsHttpHeaderProvider::VariationsHttpHeaderProvider()
: variation_ids_cache_initialized_(false) {}
VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {}
void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized(
const std::string& trial_name,
const std::string& group_name) {
base::AutoLock scoped_lock(lock_);
const size_t old_size = variation_ids_set_.size();
CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES);
CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES_SIGNED_IN);
CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES_TRIGGER);
if (variation_ids_set_.size() != old_size)
UpdateVariationIDsHeaderValue();
}
void VariationsHttpHeaderProvider::OnSyntheticTrialsChanged(
const std::vector<SyntheticTrialGroup>& groups) {
base::AutoLock scoped_lock(lock_);
synthetic_variation_ids_set_.clear();
for (const SyntheticTrialGroup& group : groups) {
VariationID id =
GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES, group.id);
if (id != EMPTY_ID) {
synthetic_variation_ids_set_.insert(
VariationIDEntry(id, GOOGLE_WEB_PROPERTIES));
}
id = GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES_SIGNED_IN,
group.id);
if (id != EMPTY_ID) {
synthetic_variation_ids_set_.insert(
VariationIDEntry(id, GOOGLE_WEB_PROPERTIES_SIGNED_IN));
}
}
UpdateVariationIDsHeaderValue();
}
void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() {
base::AutoLock scoped_lock(lock_);
if (variation_ids_cache_initialized_)
return;
// Register for additional cache updates. This is done first to avoid a race
// that could cause registered FieldTrials to be missed.
DCHECK(base::ThreadTaskRunnerHandle::IsSet());
base::FieldTrialList::AddObserver(this);
base::TimeTicks before_time = base::TimeTicks::Now();
base::FieldTrial::ActiveGroups initial_groups;
base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups);
for (const auto& entry : initial_groups) {
CacheVariationsId(entry.trial_name, entry.group_name,
GOOGLE_WEB_PROPERTIES);
CacheVariationsId(entry.trial_name, entry.group_name,
GOOGLE_WEB_PROPERTIES_SIGNED_IN);
CacheVariationsId(entry.trial_name, entry.group_name,
GOOGLE_WEB_PROPERTIES_TRIGGER);
}
UpdateVariationIDsHeaderValue();
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Variations.HeaderConstructionTime",
(base::TimeTicks::Now() - before_time).InMicroseconds(), 1,
base::TimeDelta::FromSeconds(1).InMicroseconds(), 50);
variation_ids_cache_initialized_ = true;
}
void VariationsHttpHeaderProvider::CacheVariationsId(
const std::string& trial_name,
const std::string& group_name,
IDCollectionKey key) {
const VariationID id = GetGoogleVariationID(key, trial_name, group_name);
if (id != EMPTY_ID)
variation_ids_set_.insert(VariationIDEntry(id, key));
}
void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() {
lock_.AssertAcquired();
// The header value is a serialized protobuffer of Variation IDs which is
// base64 encoded before transmitting as a string.
cached_variation_ids_header_.clear();
cached_variation_ids_header_signed_in_.clear();
// If successful, swap the header value with the new one.
// Note that the list of IDs and the header could be temporarily out of sync
// if IDs are added as the header is recreated. The receiving servers are OK
// with such discrepancies.
cached_variation_ids_header_ = GenerateBase64EncodedProto(false);
cached_variation_ids_header_signed_in_ = GenerateBase64EncodedProto(true);
}
std::string VariationsHttpHeaderProvider::GenerateBase64EncodedProto(
bool is_signed_in) {
std::set<VariationIDEntry> all_variation_ids_set = GetAllVariationIds();
ClientVariations proto;
for (const VariationIDEntry& entry : all_variation_ids_set) {
switch (entry.second) {
case GOOGLE_WEB_PROPERTIES_SIGNED_IN:
if (is_signed_in)
proto.add_variation_id(entry.first);
break;
case GOOGLE_WEB_PROPERTIES:
proto.add_variation_id(entry.first);
break;
case GOOGLE_WEB_PROPERTIES_TRIGGER:
proto.add_trigger_variation_id(entry.first);
break;
case CHROME_SYNC_SERVICE:
case ID_COLLECTION_COUNT:
// These cases included to get full enum coverage for switch, so that
// new enums introduce compiler warnings. Nothing to do for these.
break;
}
}
const size_t total_id_count =
proto.variation_id_size() + proto.trigger_variation_id_size();
if (total_id_count == 0)
return std::string();
// This is the bottleneck for the creation of the header, so validate the size
// here. Force a hard maximum on the ID count in case the Variations server
// returns too many IDs and DOSs receiving servers with large requests.
DCHECK_LE(total_id_count, 10U);
UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount",
total_id_count);
if (total_id_count > 20)
return std::string();
std::string serialized;
proto.SerializeToString(&serialized);
std::string hashed;
base::Base64Encode(serialized, &hashed);
return hashed;
}
std::set<VariationsHttpHeaderProvider::VariationIDEntry>
VariationsHttpHeaderProvider::GetAllVariationIds() {
lock_.AssertAcquired();
std::set<VariationIDEntry> all_variation_ids_set = default_variation_ids_set_;
for (const VariationIDEntry& entry : variation_ids_set_) {
all_variation_ids_set.insert(entry);
}
for (const VariationIDEntry& entry : synthetic_variation_ids_set_) {
all_variation_ids_set.insert(entry);
}
return all_variation_ids_set;
}
} // namespace variations
|