blob: 5ba29bd0471bcbf00c2dafc62c7047591f712662 [file] [log] [blame]
// Copyright 2017 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/data_reduction_proxy/core/browser/network_properties_manager.h"
#include <vector>
#include "base/base64.h"
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/time/clock.h"
#include "base/values.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_pref_names.h"
#include "components/prefs/scoped_user_pref_update.h"
namespace data_reduction_proxy {
namespace {
constexpr size_t kMaxCacheSize = 10u;
// Maximum number of times a data saver proxy with unique tuple
// (is_secure_proxy, is_core_proxy) is probed.
constexpr size_t kMaxWarmupURLFetchAttempts = 3;
// Parses a base::Value to NetworkProperties struct. If the parsing is
// unsuccessful, a nullptr is returned.
base::Optional<NetworkProperties> GetParsedNetworkProperty(
const base::Value& value) {
if (!value.is_string())
return base::nullopt;
std::string base64_decoded;
if (!base::Base64Decode(value.GetString(), &base64_decoded))
return base::nullopt;
NetworkProperties network_properties;
if (!network_properties.ParseFromString(base64_decoded))
return base::nullopt;
return network_properties;
}
} // namespace
class NetworkPropertiesManager::PrefManager {
public:
PrefManager(base::Clock* clock, PrefService* pref_service)
: clock_(clock), pref_service_(pref_service), ui_weak_ptr_factory_(this) {
DCHECK(clock_);
DictionaryPrefUpdate update(pref_service_, prefs::kNetworkProperties);
base::DictionaryValue* properties_dict = update.Get();
CleanupOldOrInvalidValues(properties_dict);
}
~PrefManager() {}
base::WeakPtr<PrefManager> GetWeakPtr() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return ui_weak_ptr_factory_.GetWeakPtr();
}
void ShutdownOnUIThread() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ui_weak_ptr_factory_.InvalidateWeakPtrs();
}
void OnChangeInNetworkPropertyOnUIThread(
const std::string& network_id,
const NetworkProperties& network_properties) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::string serialized_network_properties;
bool serialize_to_string_ok =
network_properties.SerializeToString(&serialized_network_properties);
if (!serialize_to_string_ok)
return;
std::string base64_encoded;
base::Base64Encode(serialized_network_properties, &base64_encoded);
DictionaryPrefUpdate update(pref_service_, prefs::kNetworkProperties);
base::DictionaryValue* properties_dict = update.Get();
properties_dict->SetKey(network_id, base::Value(base64_encoded));
LimitPrefSize(properties_dict);
}
void DeleteHistory() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
pref_service_->ClearPref(prefs::kNetworkProperties);
}
private:
// Limits the pref size to kMaxCacheSize.
void LimitPrefSize(base::DictionaryValue* properties_dict) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (properties_dict->size() <= kMaxCacheSize)
return;
// Delete the key that corresponds to the network with the earliest
// timestamp.
const std::string* key_to_delete = nullptr;
int64_t earliest_timestamp = std::numeric_limits<int64_t>::max();
for (const auto& it : properties_dict->DictItems()) {
base::Optional<NetworkProperties> network_properties =
GetParsedNetworkProperty(it.second);
if (!network_properties) {
// Delete the corrupted entry. No need to find the oldest entry.
key_to_delete = &it.first;
break;
}
int64_t timestamp = network_properties.value().last_modified();
if (timestamp < earliest_timestamp) {
earliest_timestamp = timestamp;
key_to_delete = &it.first;
}
}
if (key_to_delete == nullptr)
return;
properties_dict->RemoveKey(*key_to_delete);
}
// Removes all old or invalid values from the dictionary.
void CleanupOldOrInvalidValues(base::DictionaryValue* properties_dict) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Entries that have not been modified during last |kMaxEntryAge| would be
// deleted.
static constexpr base::TimeDelta kMaxEntryAge =
base::TimeDelta::FromDays(30);
const base::Time now = clock_->Now();
std::vector<std::string> keys_to_delete;
for (const auto& it : properties_dict->DictItems()) {
// At most one entry is deleted within this loop since |it| may not
// be valid once an entry is deleted from the dictionary.
base::Optional<NetworkProperties> network_properties =
GetParsedNetworkProperty(it.second);
if (!network_properties) {
// Delete the corrupted entry.
keys_to_delete.push_back(it.first);
continue;
}
base::Time timestamp_entry =
base::Time::FromJavaTime(network_properties.value().last_modified());
base::TimeDelta entry_age = now - timestamp_entry;
if (entry_age < base::TimeDelta() || entry_age > kMaxEntryAge) {
keys_to_delete.push_back(it.first);
continue;
}
}
for (const auto& key : keys_to_delete)
properties_dict->RemoveKey(key);
}
base::Clock* clock_;
// Guaranteed to be non-null during the lifetime of |this|.
PrefService* pref_service_;
SEQUENCE_CHECKER(sequence_checker_);
// Used to get |weak_ptr_| to self on the UI thread.
base::WeakPtrFactory<PrefManager> ui_weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(PrefManager);
};
NetworkPropertiesManager::NetworkPropertiesManager(
base::Clock* clock,
PrefService* pref_service,
scoped_refptr<base::SequencedTaskRunner> ui_task_runner)
: clock_(clock),
ui_task_runner_(ui_task_runner),
network_properties_container_(ConvertDictionaryValueToParsedPrefs(
pref_service->GetDictionary(prefs::kNetworkProperties))) {
DCHECK(ui_task_runner_);
DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
DCHECK(clock_);
pref_manager_.reset(new PrefManager(clock_, pref_service));
pref_manager_weak_ptr_ = pref_manager_->GetWeakPtr();
ResetWarmupURLFetchMetrics();
DETACH_FROM_SEQUENCE(sequence_checker_);
}
NetworkPropertiesManager::~NetworkPropertiesManager() {
if (ui_task_runner_->RunsTasksInCurrentSequence() && pref_manager_) {
pref_manager_->ShutdownOnUIThread();
}
}
void NetworkPropertiesManager::DeleteHistory() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ui_task_runner_->PostTask(
FROM_HERE,
base::Bind(&NetworkPropertiesManager::PrefManager::DeleteHistory,
pref_manager_weak_ptr_));
}
void NetworkPropertiesManager::ShutdownOnUIThread() {
DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
pref_manager_->ShutdownOnUIThread();
pref_manager_.reset();
}
void NetworkPropertiesManager::OnChangeInNetworkID(
const std::string& network_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!network_id.empty());
ResetWarmupURLFetchMetrics();
network_id_ = network_id;
bool cached_entry_found = false;
NetworkPropertiesContainer::const_iterator it =
network_properties_container_.find(network_id_);
if (it != network_properties_container_.end()) {
network_properties_ = it->second;
cached_entry_found = true;
if (params::ShouldDiscardCanaryCheckResult())
network_properties_.set_secure_proxy_disallowed_by_carrier(false);
} else {
// Reset to default state.
network_properties_.Clear();
}
UMA_HISTOGRAM_BOOLEAN("DataReductionProxy.NetworkProperties.CacheHit",
cached_entry_found);
}
void NetworkPropertiesManager::ResetWarmupURLFetchMetrics() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
has_warmup_url_succeded_secure_core_ = false;
has_warmup_url_succeded_secure_non_core_ = false;
has_warmup_url_succeded_insecure_core_ = false;
has_warmup_url_succeded_insecure_non_core_ = false;
warmup_url_fetch_attempt_counts_secure_core_ = 0;
warmup_url_fetch_attempt_counts_secure_non_core_ = 0;
warmup_url_fetch_attempt_counts_insecure_core_ = 0;
warmup_url_fetch_attempt_counts_insecure_non_core_ = 0;
}
void NetworkPropertiesManager::OnChangeInNetworkPropertyOnIOThread() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
network_properties_.set_last_modified(clock_->Now().ToJavaTime());
// Remove the entry from the map, if it is already present.
network_properties_container_.erase(network_id_);
network_properties_container_.emplace(
std::make_pair(network_id_, network_properties_));
ui_task_runner_->PostTask(
FROM_HERE,
base::Bind(&NetworkPropertiesManager::PrefManager::
OnChangeInNetworkPropertyOnUIThread,
pref_manager_weak_ptr_, network_id_, network_properties_));
}
// static
NetworkPropertiesManager::NetworkPropertiesContainer
NetworkPropertiesManager::ConvertDictionaryValueToParsedPrefs(
const base::Value* value) {
NetworkPropertiesContainer read_prefs;
for (const auto& it : value->DictItems()) {
base::Optional<NetworkProperties> network_properties =
GetParsedNetworkProperty(it.second);
if (!network_properties)
continue;
if (params::ShouldDiscardCanaryCheckResult())
network_properties->set_secure_proxy_disallowed_by_carrier(false);
read_prefs.emplace(std::make_pair(it.first, network_properties.value()));
}
return read_prefs;
}
bool NetworkPropertiesManager::IsSecureProxyAllowed(bool is_core_proxy) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return !network_properties_.secure_proxy_disallowed_by_carrier() &&
!network_properties_.has_captive_portal() &&
!HasWarmupURLProbeFailed(true, is_core_proxy);
}
bool NetworkPropertiesManager::IsInsecureProxyAllowed(
bool is_core_proxy) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return !HasWarmupURLProbeFailed(false, is_core_proxy);
}
bool NetworkPropertiesManager::IsSecureProxyDisallowedByCarrier() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return network_properties_.secure_proxy_disallowed_by_carrier();
}
void NetworkPropertiesManager::SetIsSecureProxyDisallowedByCarrier(
bool disallowed_by_carrier) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
network_properties_.set_secure_proxy_disallowed_by_carrier(
disallowed_by_carrier);
OnChangeInNetworkPropertyOnIOThread();
}
bool NetworkPropertiesManager::IsCaptivePortal() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return network_properties_.has_captive_portal();
}
void NetworkPropertiesManager::SetIsCaptivePortal(bool is_captive_portal) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
network_properties_.set_has_captive_portal(is_captive_portal);
OnChangeInNetworkPropertyOnIOThread();
}
bool NetworkPropertiesManager::HasWarmupURLProbeFailed(
bool secure_proxy,
bool is_core_proxy) const {
if (secure_proxy && is_core_proxy) {
return network_properties_.secure_proxy_flags()
.disallowed_due_to_warmup_probe_failure();
}
if (secure_proxy && !is_core_proxy) {
return network_properties_.secure_non_core_proxy_flags()
.disallowed_due_to_warmup_probe_failure();
}
if (!secure_proxy && is_core_proxy) {
return network_properties_.insecure_proxy_flags()
.disallowed_due_to_warmup_probe_failure();
}
if (!secure_proxy && !is_core_proxy) {
return network_properties_.insecure_non_core_proxy_flags()
.disallowed_due_to_warmup_probe_failure();
}
NOTREACHED();
return false;
}
void NetworkPropertiesManager::SetHasWarmupURLProbeFailed(
bool secure_proxy,
bool is_core_proxy,
bool warmup_url_probe_failed) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (secure_proxy && is_core_proxy) {
if (!warmup_url_probe_failed) {
UMA_HISTOGRAM_EXACT_LINEAR(
"DataReductionProxy.WarmupURL.FetchAttemptsBeforeSuccess.Secure.Core",
warmup_url_fetch_attempt_counts_secure_core_, 10);
}
has_warmup_url_succeded_secure_core_ = !warmup_url_probe_failed;
network_properties_.mutable_secure_proxy_flags()
->set_disallowed_due_to_warmup_probe_failure(warmup_url_probe_failed);
} else if (secure_proxy && !is_core_proxy) {
if (!warmup_url_probe_failed) {
UMA_HISTOGRAM_EXACT_LINEAR(
"DataReductionProxy.WarmupURL.FetchAttemptsBeforeSuccess.Secure."
"NonCore",
warmup_url_fetch_attempt_counts_secure_non_core_, 10);
}
has_warmup_url_succeded_secure_non_core_ = !warmup_url_probe_failed;
network_properties_.mutable_secure_non_core_proxy_flags()
->set_disallowed_due_to_warmup_probe_failure(warmup_url_probe_failed);
} else if (!secure_proxy && is_core_proxy) {
if (!warmup_url_probe_failed) {
UMA_HISTOGRAM_EXACT_LINEAR(
"DataReductionProxy.WarmupURL.FetchAttemptsBeforeSuccess.Insecure."
"Core",
warmup_url_fetch_attempt_counts_insecure_core_, 10);
}
has_warmup_url_succeded_insecure_core_ = !warmup_url_probe_failed;
network_properties_.mutable_insecure_proxy_flags()
->set_disallowed_due_to_warmup_probe_failure(warmup_url_probe_failed);
} else {
if (!warmup_url_probe_failed) {
UMA_HISTOGRAM_EXACT_LINEAR(
"DataReductionProxy.WarmupURL.FetchAttemptsBeforeSuccess.Insecure."
"NonCore",
warmup_url_fetch_attempt_counts_insecure_non_core_, 10);
}
has_warmup_url_succeded_insecure_non_core_ = !warmup_url_probe_failed;
network_properties_.mutable_insecure_non_core_proxy_flags()
->set_disallowed_due_to_warmup_probe_failure(warmup_url_probe_failed);
}
OnChangeInNetworkPropertyOnIOThread();
}
bool NetworkPropertiesManager::ShouldFetchWarmupProbeURL(
bool secure_proxy,
bool is_core_proxy) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (secure_proxy && is_core_proxy) {
return !has_warmup_url_succeded_secure_core_ &&
warmup_url_fetch_attempt_counts_secure_core_ <
kMaxWarmupURLFetchAttempts;
} else if (secure_proxy && !is_core_proxy) {
return !has_warmup_url_succeded_secure_non_core_ &&
warmup_url_fetch_attempt_counts_secure_non_core_ <
kMaxWarmupURLFetchAttempts;
} else if (!secure_proxy && is_core_proxy) {
return !has_warmup_url_succeded_insecure_core_ &&
warmup_url_fetch_attempt_counts_insecure_core_ <
kMaxWarmupURLFetchAttempts;
} else {
return !has_warmup_url_succeded_insecure_non_core_ &&
warmup_url_fetch_attempt_counts_insecure_non_core_ <
kMaxWarmupURLFetchAttempts;
}
}
void NetworkPropertiesManager::OnWarmupFetchInitiated(bool secure_proxy,
bool is_core_proxy) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (secure_proxy && is_core_proxy) {
++warmup_url_fetch_attempt_counts_secure_core_;
DCHECK_GE(kMaxWarmupURLFetchAttempts,
warmup_url_fetch_attempt_counts_secure_core_);
} else if (secure_proxy && !is_core_proxy) {
++warmup_url_fetch_attempt_counts_secure_non_core_;
DCHECK_GE(kMaxWarmupURLFetchAttempts,
warmup_url_fetch_attempt_counts_secure_non_core_);
} else if (!secure_proxy && is_core_proxy) {
++warmup_url_fetch_attempt_counts_insecure_core_;
DCHECK_GE(kMaxWarmupURLFetchAttempts,
warmup_url_fetch_attempt_counts_insecure_core_);
} else {
++warmup_url_fetch_attempt_counts_insecure_non_core_;
DCHECK_GE(kMaxWarmupURLFetchAttempts,
warmup_url_fetch_attempt_counts_insecure_non_core_);
}
}
size_t NetworkPropertiesManager::GetWarmupURLFetchAttemptCounts(
bool secure_proxy,
bool is_core_proxy) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (secure_proxy && is_core_proxy) {
DCHECK_GT(kMaxWarmupURLFetchAttempts,
warmup_url_fetch_attempt_counts_secure_core_);
return warmup_url_fetch_attempt_counts_secure_core_;
} else if (secure_proxy && !is_core_proxy) {
DCHECK_GT(kMaxWarmupURLFetchAttempts,
warmup_url_fetch_attempt_counts_secure_non_core_);
return warmup_url_fetch_attempt_counts_secure_non_core_;
} else if (!secure_proxy && is_core_proxy) {
DCHECK_GT(kMaxWarmupURLFetchAttempts,
warmup_url_fetch_attempt_counts_insecure_core_);
return warmup_url_fetch_attempt_counts_insecure_core_;
} else {
DCHECK_GT(kMaxWarmupURLFetchAttempts,
warmup_url_fetch_attempt_counts_insecure_non_core_);
return warmup_url_fetch_attempt_counts_insecure_non_core_;
}
}
} // namespace data_reduction_proxy