// 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 #include #include #include #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::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* variation_ids) { if (!command_line_variation_ids.empty()) { // Combine |variation_ids| with |command_line_variation_ids|. std::vector 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& 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& 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 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::GetAllVariationIds() { lock_.AssertAcquired(); std::set 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