// Copyright (c) 2012 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/service/variations_service.h" #include #include #include #include #include "base/base64.h" #include "base/base_switches.h" #include "base/bind.h" #include "base/build_time.h" #include "base/callback.h" #include "base/command_line.h" #include "base/feature_list.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/sys_info.h" #include "base/task/post_task.h" #include "base/task_runner_util.h" #include "base/timer/elapsed_timer.h" #include "base/values.h" #include "base/version.h" #include "build/build_config.h" #include "components/data_use_measurement/core/data_use_user_data.h" #include "components/encrypted_messages/encrypted_message.pb.h" #include "components/encrypted_messages/message_encrypter.h" #include "components/metrics/clean_exit_beacon.h" #include "components/metrics/metrics_state_manager.h" #include "components/network_time/network_time_tracker.h" #include "components/pref_registry/pref_registry_syncable.h" #include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_service.h" #include "components/variations/pref_names.h" #include "components/variations/proto/variations_seed.pb.h" #include "components/variations/seed_response.h" #include "components/variations/variations_seed_processor.h" #include "components/variations/variations_seed_simulator.h" #include "components/variations/variations_switches.h" #include "components/variations/variations_url_constants.h" #include "components/version_info/version_info.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/base/network_change_notifier.h" #include "net/base/url_util.h" #include "net/http/http_response_headers.h" #include "net/http/http_status_code.h" #include "net/http/http_util.h" #include "net/traffic_annotation/network_traffic_annotation.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/simple_url_loader.h" #include "ui/base/device_form_factor.h" #include "url/gurl.h" #if defined(OS_ANDROID) #include "components/variations/android/variations_seed_bridge.h" #endif // OS_ANDROID namespace variations { namespace { const base::Feature kHttpRetryFeature{"VariationsHttpRetry", base::FEATURE_ENABLED_BY_DEFAULT}; // Constants used for encrypting the if-none-match header if we are retrieving a // seed over http. const char kEncryptedMessageLabel[] = "chrome variations"; // TODO(crbug.com/792239): Change this key to a unique VariationsService one, // once the matching private key is changed server side. // Key is used to encrypt headers in seed retrieval requests that happen over // HTTP connections (when retrying after an unsuccessful HTTPS retrieval // attempt). const uint8_t kServerPublicKey[] = { 0x51, 0xcc, 0x52, 0x67, 0x42, 0x47, 0x3b, 0x10, 0xe8, 0x63, 0x18, 0x3c, 0x61, 0xa7, 0x96, 0x76, 0x86, 0x91, 0x40, 0x71, 0x39, 0x5f, 0x31, 0x1a, 0x39, 0x5b, 0x76, 0xb1, 0x6b, 0x3d, 0x6a, 0x2b}; const uint32_t kServerPublicKeyVersion = 1; // TODO(mad): To be removed when we stop updating the NetworkTimeTracker. // For the HTTP date headers, the resolution of the server time is 1 second. const int64_t kServerTimeResolutionMs = 1000; // Whether the VariationsService should fetch the seed for testing. bool g_should_fetch_for_testing = false; // Returns a string that will be used for the value of the 'osname' URL param // to the variations server. std::string GetPlatformString() { #if defined(OS_WIN) return "win"; #elif defined(OS_IOS) return "ios"; #elif defined(OS_MACOSX) return "mac"; #elif defined(OS_CHROMEOS) return "chromeos"; #elif defined(OS_ANDROID) return "android"; #elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) // Default BSD and SOLARIS to Linux to not break those builds, although these // platforms are not officially supported by Chrome. return "linux"; #else #error Unknown platform #endif } // Gets the restrict parameter from either the client or |policy_pref_service|. std::string GetRestrictParameterPref(VariationsServiceClient* client, PrefService* policy_pref_service) { std::string parameter; if (client->OverridesRestrictParameter(¶meter) || !policy_pref_service) return parameter; return policy_pref_service->GetString(prefs::kVariationsRestrictParameter); } // Reported to UMA, keep in sync with enums.xml and don't renumber entries. enum ResourceRequestsAllowedState { RESOURCE_REQUESTS_ALLOWED, RESOURCE_REQUESTS_NOT_ALLOWED, RESOURCE_REQUESTS_ALLOWED_NOTIFIED, RESOURCE_REQUESTS_NOT_ALLOWED_EULA_NOT_ACCEPTED, RESOURCE_REQUESTS_NOT_ALLOWED_NETWORK_DOWN, RESOURCE_REQUESTS_NOT_ALLOWED_COMMAND_LINE_DISABLED, RESOURCE_REQUESTS_NOT_ALLOWED_NETWORK_STATE_NOT_INITIALIZED, RESOURCE_REQUESTS_ALLOWED_ENUM_SIZE, }; // Records UMA histogram with the current resource requests allowed state. void RecordRequestsAllowedHistogram(ResourceRequestsAllowedState state) { UMA_HISTOGRAM_ENUMERATION("Variations.ResourceRequestsAllowed", state, RESOURCE_REQUESTS_ALLOWED_ENUM_SIZE); } enum VariationsSeedExpiry { VARIATIONS_SEED_EXPIRY_NOT_EXPIRED, VARIATIONS_SEED_EXPIRY_FETCH_TIME_MISSING, VARIATIONS_SEED_EXPIRY_EXPIRED, VARIATIONS_SEED_EXPIRY_ENUM_SIZE, }; // Converts ResourceRequestAllowedNotifier::State to the corresponding // ResourceRequestsAllowedState value. ResourceRequestsAllowedState ResourceRequestStateToHistogramValue( web_resource::ResourceRequestAllowedNotifier::State state) { using web_resource::ResourceRequestAllowedNotifier; switch (state) { case ResourceRequestAllowedNotifier::DISALLOWED_EULA_NOT_ACCEPTED: return RESOURCE_REQUESTS_NOT_ALLOWED_EULA_NOT_ACCEPTED; case ResourceRequestAllowedNotifier::DISALLOWED_NETWORK_DOWN: return RESOURCE_REQUESTS_NOT_ALLOWED_NETWORK_DOWN; case ResourceRequestAllowedNotifier::DISALLOWED_COMMAND_LINE_DISABLED: return RESOURCE_REQUESTS_NOT_ALLOWED_COMMAND_LINE_DISABLED; case ResourceRequestAllowedNotifier:: DISALLOWED_NETWORK_STATE_NOT_INITIALIZED: return RESOURCE_REQUESTS_NOT_ALLOWED_NETWORK_STATE_NOT_INITIALIZED; case ResourceRequestAllowedNotifier::ALLOWED: return RESOURCE_REQUESTS_ALLOWED; } NOTREACHED(); return RESOURCE_REQUESTS_NOT_ALLOWED; } // Returns the header value for |name| from |headers| or an empty string if not // set. std::string GetHeaderValue(const net::HttpResponseHeaders* headers, const base::StringPiece& name) { std::string value; headers->EnumerateHeader(nullptr, name, &value); return value; } // Returns the list of values for |name| from |headers|. If the header in not // set, return an empty list. std::vector GetHeaderValuesList( const net::HttpResponseHeaders* headers, const base::StringPiece& name) { std::vector values; size_t iter = 0; std::string value; while (headers->EnumerateHeader(&iter, name, &value)) { values.push_back(value); } return values; } // Looks for delta and gzip compression instance manipulation flags set by the // server in |headers|. Checks the order of flags and presence of unknown // instance manipulations. If successful, |is_delta_compressed| and // |is_gzip_compressed| contain compression flags and true is returned. bool GetInstanceManipulations(const net::HttpResponseHeaders* headers, bool* is_delta_compressed, bool* is_gzip_compressed) { std::vector ims = GetHeaderValuesList(headers, "IM"); const auto delta_im = std::find(ims.begin(), ims.end(), "x-bm"); const auto gzip_im = std::find(ims.begin(), ims.end(), "gzip"); *is_delta_compressed = delta_im != ims.end(); *is_gzip_compressed = gzip_im != ims.end(); // The IM field should not have anything but x-bm and gzip. size_t im_count = (*is_delta_compressed ? 1 : 0) + (*is_gzip_compressed ? 1 : 0); if (im_count != ims.size()) { DVLOG(1) << "Unrecognized instance manipulations in " << base::JoinString(ims, ",") << "; only x-bm and gzip are supported"; return false; } // The IM field defines order in which instance manipulations were applied. // The client requests and supports gzip-compressed delta-compressed seeds, // but not vice versa. if (*is_delta_compressed && *is_gzip_compressed && delta_im > gzip_im) { DVLOG(1) << "Unsupported instance manipulations order: " << "requested x-bm,gzip but received gzip,x-bm"; return false; } return true; } // Variations seed fetching is only enabled in official Chrome builds, if a URL // is specified on the command line, and for testing. bool IsFetchingEnabled() { #if !defined(GOOGLE_CHROME_BUILD) if (!base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kVariationsServerURL) && !g_should_fetch_for_testing) { DVLOG(1) << "Not performing repeated fetching in unofficial build without --" << switches::kVariationsServerURL << " specified."; return false; } #endif return true; } std::unique_ptr MaybeImportFirstRunSeed( PrefService* local_state) { #if defined(OS_ANDROID) if (!local_state->HasPrefPath(prefs::kVariationsSeedSignature)) { DVLOG(1) << "Importing first run seed from Java preferences."; return android::GetVariationsFirstRunSeed(); } #endif return nullptr; } // Called when the VariationsSeedStore first stores a seed. void OnInitialSeedStored() { #if defined(OS_ANDROID) android::MarkVariationsSeedAsStored(); android::ClearJavaFirstRunPrefs(); #endif } } // namespace VariationsService::VariationsService( std::unique_ptr client, std::unique_ptr notifier, PrefService* local_state, metrics::MetricsStateManager* state_manager, const UIStringOverrider& ui_string_overrider) : client_(std::move(client)), local_state_(local_state), state_manager_(state_manager), policy_pref_service_(local_state), initial_request_completed_(false), disable_deltas_for_next_request_(false), resource_request_allowed_notifier_(std::move(notifier)), request_count_(0), safe_seed_manager_(state_manager->clean_exit_beacon()->exited_cleanly(), local_state), field_trial_creator_(local_state, client_.get(), std::make_unique( local_state, MaybeImportFirstRunSeed(local_state), base::BindOnce(&OnInitialSeedStored)), ui_string_overrider), weak_ptr_factory_(this) { DCHECK(client_); DCHECK(resource_request_allowed_notifier_); } VariationsService::~VariationsService() { } void VariationsService::PerformPreMainMessageLoopStartup() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(field_trial_creator_.IsOverrideResourceMapEmpty()); InitResourceRequestedAllowedNotifier(); // Android instead calls OnAppEnterForeground() which then calls // StartRepeatedVariationsSeedFetch(). This is too early to do it on Android // because at this point the |restrict_mode_| hasn't been set yet. See also // the CHECK in SetRestrictMode(). #if !defined(OS_ANDROID) if (!IsFetchingEnabled()) return; StartRepeatedVariationsSeedFetch(); #endif // !defined(OS_ANDROID) } std::string VariationsService::LoadPermanentConsistencyCountry( const base::Version& version, const std::string& latest_country) { return field_trial_creator_.LoadPermanentConsistencyCountry(version, latest_country); } bool VariationsService::EncryptString(const std::string& plaintext, std::string* encrypted) { encrypted_messages::EncryptedMessage encrypted_message; if (!encrypted_messages::EncryptSerializedMessage( kServerPublicKey, kServerPublicKeyVersion, kEncryptedMessageLabel, plaintext, &encrypted_message) || !encrypted_message.SerializeToString(encrypted)) { return false; } return true; } void VariationsService::AddObserver(Observer* observer) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); observer_list_.AddObserver(observer); } void VariationsService::RemoveObserver(Observer* observer) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); observer_list_.RemoveObserver(observer); } void VariationsService::OnAppEnterForeground() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!IsFetchingEnabled()) return; // On mobile platforms, initialize the fetch scheduler when we receive the // first app foreground notification. if (!request_scheduler_) StartRepeatedVariationsSeedFetch(); request_scheduler_->OnAppEnterForeground(); } void VariationsService::SetRestrictMode(const std::string& restrict_mode) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // This should be called before the server URL has been computed. Note: This // uses a CHECK because this is relevant for the behavior in release official // builds that talk to the variations server - which don't enable DCHECKs. CHECK(variations_server_url_.is_empty()); restrict_mode_ = restrict_mode; } GURL VariationsService::GetVariationsServerURL( PrefService* policy_pref_service, const std::string& restrict_mode_override, HttpOptions http_options) { bool secure = http_options == USE_HTTPS; std::string server_url_string( base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( secure ? switches::kVariationsServerURL : switches::kVariationsInsecureServerURL)); if (server_url_string.empty()) server_url_string = secure ? kDefaultServerUrl : kDefaultInsecureServerUrl; GURL server_url = GURL(server_url_string); if (secure) { const std::string restrict_param = !restrict_mode_override.empty() ? restrict_mode_override : GetRestrictParameterPref(client_.get(), policy_pref_service); if (!restrict_param.empty()) { server_url = net::AppendOrReplaceQueryParameter(server_url, "restrict", restrict_param); } } server_url = net::AppendOrReplaceQueryParameter(server_url, "osname", GetPlatformString()); // Add channel to the request URL. version_info::Channel channel = client_->GetChannel(); if (channel != version_info::Channel::UNKNOWN) { server_url = net::AppendOrReplaceQueryParameter( server_url, "channel", version_info::GetChannelString(channel)); } // Add milestone to the request URL. std::string version = version_info::GetVersionNumber(); std::vector version_parts = base::SplitString( version, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); if (version_parts.size() > 0) { server_url = net::AppendOrReplaceQueryParameter(server_url, "milestone", version_parts[0]); } DCHECK(server_url.is_valid()); return server_url; } void VariationsService::EnsureLocaleEquals(const std::string& locale) { #if defined(OS_CHROMEOS) // Chrome OS may switch language on the fly. DCHECK_EQ(locale, field_trial_creator_.application_locale()); #else #if defined(OS_ANDROID) // TODO(asvitkine): Speculative early return to silence CHECK failures on // Android, see crbug.com/912320. if (locale.empty()) return; #endif // Uses a CHECK rather than a DCHECK to ensure that issues are caught since // problems in this area may only appear in the wild due to official builds // and end user machines. CHECK_EQ(locale, field_trial_creator_.application_locale()); #endif } // static std::string VariationsService::GetDefaultVariationsServerURLForTesting() { return kDefaultServerUrl; } // static void VariationsService::RegisterPrefs(PrefRegistrySimple* registry) { SafeSeedManager::RegisterPrefs(registry); VariationsSeedStore::RegisterPrefs(registry); // This preference will only be written by the policy service, which will fill // it according to a value stored in the User Policy. registry->RegisterStringPref(prefs::kVariationsRestrictParameter, std::string()); // This preference keeps track of the country code used to filter // permanent-consistency studies. registry->RegisterListPref(prefs::kVariationsPermanentConsistencyCountry); } // static void VariationsService::RegisterProfilePrefs( user_prefs::PrefRegistrySyncable* registry) { // This preference will only be written by the policy service, which will fill // it according to a value stored in the User Policy. registry->RegisterStringPref(prefs::kVariationsRestrictParameter, std::string()); } // static std::unique_ptr VariationsService::Create( std::unique_ptr client, PrefService* local_state, metrics::MetricsStateManager* state_manager, const char* disable_network_switch, const UIStringOverrider& ui_string_overrider, web_resource::ResourceRequestAllowedNotifier::NetworkConnectionTrackerGetter network_connection_tracker_getter) { std::unique_ptr result; result.reset(new VariationsService( std::move(client), std::make_unique( local_state, disable_network_switch, std::move(network_connection_tracker_getter)), local_state, state_manager, ui_string_overrider)); return result; } // static void VariationsService::EnableFetchForTesting() { g_should_fetch_for_testing = true; } void VariationsService::DoActualFetch() { DoFetchFromURL(variations_server_url_, false); } bool VariationsService::StoreSeed(const std::string& seed_data, const std::string& seed_signature, const std::string& country_code, base::Time date_fetched, bool is_delta_compressed, bool is_gzip_compressed, bool fetched_insecurely) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); std::unique_ptr seed(new VariationsSeed); if (!field_trial_creator_.seed_store()->StoreSeedData( seed_data, seed_signature, country_code, date_fetched, is_delta_compressed, is_gzip_compressed, fetched_insecurely, seed.get())) { return false; } RecordSuccessfulFetch(); // Now, do simulation to determine if there are any kill-switches that were // activated by this seed. To do this, first get the Chrome version to do a // simulation with, which must be done on a background thread, and then do the // actual simulation on the UI thread. base::PostTaskWithTraitsAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, client_->GetVersionForSimulationCallback(), base::Bind(&VariationsService::PerformSimulationWithVersion, weak_ptr_factory_.GetWeakPtr(), base::Passed(&seed))); return true; } std::unique_ptr VariationsService::CreateLowEntropyProvider() { return state_manager_->CreateLowEntropyProvider(); } void VariationsService::InitResourceRequestedAllowedNotifier() { // ResourceRequestAllowedNotifier does not install an observer if there is no // NetworkChangeNotifier, which results in never being notified of changes to // network status. resource_request_allowed_notifier_->Init(this, false /* leaky */); } bool VariationsService::DoFetchFromURL(const GURL& url, bool is_http_retry) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(IsFetchingEnabled()); safe_seed_manager_.RecordFetchStarted(); // Normally, there shouldn't be a |pending_request_| when this fires. However // it's not impossible - for example if Chrome was paused (e.g. in a debugger // or if the machine was suspended) and OnURLFetchComplete() hasn't had a // chance to run yet from the previous request. In this case, don't start a // new request and just let the previous one finish. if (pending_seed_request_) return false; net::NetworkTrafficAnnotationTag traffic_annotation = net::DefineNetworkTrafficAnnotation("chrome_variations_service", R"( semantics { sender: "Chrome Variations Service" description: "Retrieves the list of Google Chrome's Variations from the server, " "which will apply to the next Chrome session upon a restart." trigger: "Requests are made periodically while Google Chrome is running." data: "The operating system name." destination: GOOGLE_OWNED_SERVICE } policy { cookies_allowed: NO setting: "This feature cannot be disabled by settings." policy_exception_justification: "Not implemented, considered not required." })"); auto resource_request = std::make_unique(); resource_request->url = url; resource_request->allow_credentials = false; bool enable_deltas = false; std::string serial_number = field_trial_creator_.seed_store()->GetLatestSerialNumber(); if (!serial_number.empty() && !disable_deltas_for_next_request_) { // Tell the server that delta-compressed seeds are supported. enable_deltas = true; // Get the seed only if its serial number doesn't match what we have. // If the fetch is an HTTP retry, encrypt the If-None-Match header. if (is_http_retry) { if (!EncryptString(serial_number, &serial_number)) { return false; } base::Base64Encode(serial_number, &serial_number); } resource_request->headers.SetHeader("If-None-Match", serial_number); } // Tell the server that delta-compressed and gzipped seeds are supported. const char* supported_im = enable_deltas ? "x-bm,gzip" : "gzip"; resource_request->headers.SetHeader("A-IM", supported_im); // TODO(https://crbug.com/808498): Re-add data use measurement once // SimpleURLLoader supports it. // ID=data_use_measurement::DataUseUserData::VARIATIONS pending_seed_request_ = network::SimpleURLLoader::Create( std::move(resource_request), traffic_annotation); // Ensure our callback is called even with "304 Not Modified" responses. pending_seed_request_->SetAllowHttpErrorResults(true); // base::Unretained is safe here since this class owns // |pending_seed_request_|'s lifetime. pending_seed_request_->DownloadToStringOfUnboundedSizeUntilCrashAndDie( client_->GetURLLoaderFactory().get(), base::BindOnce(&VariationsService::OnSimpleLoaderComplete, base::Unretained(this))); const base::TimeTicks now = base::TimeTicks::Now(); base::TimeDelta time_since_last_fetch; // Record a time delta of 0 (default value) if there was no previous fetch. if (!last_request_started_time_.is_null()) time_since_last_fetch = now - last_request_started_time_; UMA_HISTOGRAM_CUSTOM_COUNTS("Variations.TimeSinceLastFetchAttempt", time_since_last_fetch.InMinutes(), 1, base::TimeDelta::FromDays(7).InMinutes(), 50); UMA_HISTOGRAM_COUNTS_100("Variations.RequestCount", request_count_); ++request_count_; last_request_started_time_ = now; disable_deltas_for_next_request_ = false; return true; } void VariationsService::StartRepeatedVariationsSeedFetch() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Initialize the Variations server URL. variations_server_url_ = GetVariationsServerURL(policy_pref_service_, restrict_mode_, USE_HTTPS); // Initialize the fallback HTTP URL if the HTTP retry feature is enabled. if (base::FeatureList::IsEnabled(kHttpRetryFeature)) { insecure_variations_server_url_ = GetVariationsServerURL(policy_pref_service_, restrict_mode_, USE_HTTP); } DCHECK(!request_scheduler_); request_scheduler_.reset(VariationsRequestScheduler::Create( base::Bind(&VariationsService::FetchVariationsSeed, weak_ptr_factory_.GetWeakPtr()), local_state_)); // Note that the act of starting the scheduler will start the fetch, if the // scheduler deems appropriate. request_scheduler_->Start(); } void VariationsService::FetchVariationsSeed() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); const web_resource::ResourceRequestAllowedNotifier::State state = resource_request_allowed_notifier_->GetResourceRequestsAllowedState(); RecordRequestsAllowedHistogram(ResourceRequestStateToHistogramValue(state)); if (state != web_resource::ResourceRequestAllowedNotifier::ALLOWED) { DVLOG(1) << "Resource requests were not allowed. Waiting for notification."; return; } DoActualFetch(); } void VariationsService::NotifyObservers( const VariationsSeedSimulator::Result& result) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (result.kill_critical_group_change_count > 0) { for (auto& observer : observer_list_) observer.OnExperimentChangesDetected(Observer::CRITICAL); } else if (result.kill_best_effort_group_change_count > 0) { for (auto& observer : observer_list_) observer.OnExperimentChangesDetected(Observer::BEST_EFFORT); } } void VariationsService::OnSimpleLoaderComplete( std::unique_ptr response_body) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); const bool is_first_request = !initial_request_completed_; initial_request_completed_ = true; int net_error = pending_seed_request_->NetError(); int response_code = -1; scoped_refptr headers; if (pending_seed_request_->ResponseInfo() && pending_seed_request_->ResponseInfo()->headers) { headers = pending_seed_request_->ResponseInfo()->headers; response_code = headers->response_code(); } bool is_success = headers && (net_error == net::OK); bool was_https = pending_seed_request_->GetFinalURL().SchemeIs(url::kHttpsScheme); pending_seed_request_.reset(); if (was_https) { base::UmaHistogramSparse("Variations.SeedFetchResponseOrErrorCode", is_success ? response_code : net_error); } else { base::UmaHistogramSparse("Variations.SeedFetchResponseOrErrorCode.HTTP", is_success ? response_code : net_error); } if (!is_success) { DVLOG(1) << "Variations server request failed with error: " << net_error << ": " << net::ErrorToString(net_error); // If the current fetch attempt was over an HTTPS connection, retry the // fetch immediately over an HTTP connection. // Currently we only do this if if the 'VariationsHttpRetry' feature is // enabled. if (was_https && !insecure_variations_server_url_.is_empty()) { // When re-trying via HTTP, return immediately. OnURLFetchComplete() will // be called with the result of that retry. if (DoFetchFromURL(insecure_variations_server_url_, true)) return; } // It's common for the very first fetch attempt to fail (e.g. the network // may not yet be available). In such a case, try again soon, rather than // waiting the full time interval. // |request_scheduler_| will be null during unit tests. if (is_first_request && request_scheduler_) request_scheduler_->ScheduleFetchShortly(); return; } const base::TimeDelta latency = base::TimeTicks::Now() - last_request_started_time_; base::Time response_date; if (response_code == net::HTTP_OK || response_code == net::HTTP_NOT_MODIFIED) { bool success = headers->GetDateValue(&response_date); DCHECK(success || response_date.is_null()); if (!response_date.is_null()) { client_->GetNetworkTimeTracker()->UpdateNetworkTime( response_date, base::TimeDelta::FromMilliseconds(kServerTimeResolutionMs), latency, base::TimeTicks::Now()); } } if (response_code != net::HTTP_OK) { DVLOG(1) << "Variations server request returned non-HTTP_OK response code: " << response_code; if (response_code == net::HTTP_NOT_MODIFIED) { RecordSuccessfulFetch(); // Update the seed date value in local state (used for expiry check on // next start up), since 304 is a successful response. Note that the // serial number included in the request is always that of the latest // seed, even when running in safe mode, so it's appropriate to always // modify the latest seed's date. field_trial_creator_.seed_store()->UpdateSeedDateAndLogDayChange( response_date); } return; } bool is_delta_compressed; bool is_gzip_compressed; if (!GetInstanceManipulations(headers.get(), &is_delta_compressed, &is_gzip_compressed)) { // The header does not specify supported instance manipulations, unable to // process data. Details of errors were logged by GetInstanceManipulations. field_trial_creator_.seed_store()->ReportUnsupportedSeedFormatError(); return; } const std::string signature = GetHeaderValue(headers.get(), "X-Seed-Signature"); const std::string country_code = GetHeaderValue(headers.get(), "X-Country"); const bool store_success = StoreSeed(*response_body, signature, country_code, response_date, is_delta_compressed, is_gzip_compressed, !was_https); if (!store_success && is_delta_compressed) { disable_deltas_for_next_request_ = true; // |request_scheduler_| will be null during unit tests. if (request_scheduler_) request_scheduler_->ScheduleFetchShortly(); } } void VariationsService::OnResourceRequestsAllowed() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Note that this only attempts to fetch the seed at most once per period // (kSeedFetchPeriodHours). This works because // |resource_request_allowed_notifier_| only calls this method if an // attempt was made earlier that fails (which implies that the period had // elapsed). After a successful attempt is made, the notifier will know not // to call this method again until another failed attempt occurs. RecordRequestsAllowedHistogram(RESOURCE_REQUESTS_ALLOWED_NOTIFIED); DVLOG(1) << "Retrying fetch."; DoActualFetch(); // This service must have created a scheduler in order for this to be called. DCHECK(request_scheduler_); request_scheduler_->Reset(); } void VariationsService::PerformSimulationWithVersion( std::unique_ptr seed, const base::Version& version) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!version.IsValid()) return; const base::ElapsedTimer timer; std::unique_ptr default_provider = state_manager_->CreateDefaultEntropyProvider(); std::unique_ptr low_provider = state_manager_->CreateLowEntropyProvider(); VariationsSeedSimulator seed_simulator(*default_provider, *low_provider); std::unique_ptr client_state = field_trial_creator_.GetClientFilterableStateForVersion(version); const VariationsSeedSimulator::Result result = seed_simulator.SimulateSeedStudies(*seed, *client_state); UMA_HISTOGRAM_COUNTS_100("Variations.SimulateSeed.NormalChanges", result.normal_group_change_count); UMA_HISTOGRAM_COUNTS_100("Variations.SimulateSeed.KillBestEffortChanges", result.kill_best_effort_group_change_count); UMA_HISTOGRAM_COUNTS_100("Variations.SimulateSeed.KillCriticalChanges", result.kill_critical_group_change_count); UMA_HISTOGRAM_TIMES("Variations.SimulateSeed.Duration", timer.Elapsed()); NotifyObservers(result); } void VariationsService::RecordSuccessfulFetch() { field_trial_creator_.seed_store()->RecordLastFetchTime(); safe_seed_manager_.RecordSuccessfulFetch(field_trial_creator_.seed_store()); } void VariationsService::GetClientFilterableStateForVersionCalledForTesting() { const base::Version current_version(version_info::GetVersionNumber()); if (!current_version.IsValid()) return; field_trial_creator_.GetClientFilterableStateForVersion(current_version); } std::string VariationsService::GetLatestCountry() const { return field_trial_creator_.GetLatestCountry(); } bool VariationsService::SetupFieldTrials( const char* kEnableGpuBenchmarking, const char* kEnableFeatures, const char* kDisableFeatures, const std::set& unforceable_field_trials, const std::vector& variation_ids, std::unique_ptr feature_list, variations::PlatformFieldTrials* platform_field_trials) { return field_trial_creator_.SetupFieldTrials( kEnableGpuBenchmarking, kEnableFeatures, kDisableFeatures, unforceable_field_trials, variation_ids, CreateLowEntropyProvider(), std::move(feature_list), platform_field_trials, &safe_seed_manager_); } void VariationsService::OverrideCachedUIStrings() { field_trial_creator_.OverrideCachedUIStrings(); } std::string VariationsService::GetStoredPermanentCountry() { const base::ListValue* list_value = local_state_->GetList(prefs::kVariationsPermanentConsistencyCountry); std::string stored_country; if (list_value->GetSize() == 2) { list_value->GetString(1, &stored_country); } return stored_country; } bool VariationsService::OverrideStoredPermanentCountry( const std::string& country_override) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (country_override.empty()) return false; const base::ListValue* list_value = local_state_->GetList(prefs::kVariationsPermanentConsistencyCountry); std::string stored_country; const bool got_stored_country = list_value->GetSize() == 2 && list_value->GetString(1, &stored_country); if (got_stored_country && stored_country == country_override) return false; base::Version version(version_info::GetVersionNumber()); field_trial_creator_.StorePermanentCountry(version, country_override); return true; } } // namespace variations