blob: 9695650f3b1e1e93f5e4205b99878f89fa3c3c85 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/throttling/throttling_controller.h"
#include <memory>
#include "base/no_destructor.h"
#include "components/url_pattern/simple_url_pattern_matcher.h"
#include "net/http/http_request_info.h"
#include "services/network/throttling/network_conditions.h"
#include "services/network/throttling/scoped_throttling_token.h"
#include "services/network/throttling/throttling_network_interceptor.h"
#if BUILDFLAG(IS_P2P_ENABLED)
#include "services/network/throttling/throttling_p2p_network_interceptor.h"
#endif
namespace network {
ThrottlingController::ThrottlingController() = default;
ThrottlingController::~ThrottlingController() = default;
// static
ThrottlingController& ThrottlingController::instance() {
static base::NoDestructor<ThrottlingController> instance;
return *instance;
}
// static
void ThrottlingController::SetConditions(
const base::UnguessableToken& throttling_profile_id,
std::vector<MatchedNetworkConditions> conditions) {
instance().SetNetworkConditions(throttling_profile_id, std::move(conditions));
}
// static
ThrottlingNetworkInterceptor* ThrottlingController::GetInterceptor(
uint32_t net_log_source_id,
const GURL& url) {
return instance().FindInterceptor(net_log_source_id, url);
}
#if BUILDFLAG(IS_P2P_ENABLED)
// static
ThrottlingP2PNetworkInterceptor* ThrottlingController::GetP2PInterceptor(
uint32_t net_log_source_id) {
return instance().FindP2PInterceptor(net_log_source_id);
}
#endif
// static
void ThrottlingController::RegisterProfileIDForNetLogSource(
uint32_t net_log_source_id,
const base::UnguessableToken& throttling_profile_id) {
instance().Register(net_log_source_id, throttling_profile_id);
}
// static
void ThrottlingController::UnregisterNetLogSource(uint32_t net_log_source_id) {
instance().Unregister(net_log_source_id);
}
void ThrottlingController::Register(
uint32_t net_log_source_id,
const base::UnguessableToken& throttling_profile_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
net_log_source_profile_map_[net_log_source_id] = throttling_profile_id;
}
void ThrottlingController::Unregister(uint32_t net_log_source_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
net_log_source_profile_map_.erase(net_log_source_id);
}
std::optional<base::UnguessableToken> ThrottlingController::GetProfileID(
uint32_t net_log_source_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto it = net_log_source_profile_map_.find(net_log_source_id);
if (it == net_log_source_profile_map_.end()) {
return std::nullopt;
}
return it->second;
}
ThrottlingController::InterceptorMatcher::InterceptorMatcher(
NetworkConditions conditions)
: conditions(std::move(conditions)) {
interceptor = std::make_unique<ThrottlingNetworkInterceptor>();
interceptor->UpdateConditions(this->conditions);
}
ThrottlingController::InterceptorMatcher::~InterceptorMatcher() {
// May have been moved out
if (interceptor) {
interceptor->UpdateConditions({});
}
}
ThrottlingController::InterceptorMatcher::InterceptorMatcher(
InterceptorMatcher&&) = default;
ThrottlingController::InterceptorMatcher&
ThrottlingController::InterceptorMatcher::operator=(InterceptorMatcher&&) =
default;
ThrottlingController::ThrottlingProfile::ThrottlingProfile(
ThrottlingController::ThrottlingProfile&&) = default;
ThrottlingController::ThrottlingProfile&
ThrottlingController::ThrottlingProfile::operator=(
ThrottlingController::ThrottlingProfile&&) = default;
ThrottlingController::ThrottlingProfile::ThrottlingProfile() = default;
ThrottlingController::ThrottlingProfile::~ThrottlingProfile() = default;
template <typename IterT>
static IterT FindConditions(IterT begin,
IterT end,
const NetworkConditions& network_conditions) {
return std::find_if(begin, end, [&network_conditions](auto&& matcher) {
return matcher.conditions == network_conditions;
});
}
void ThrottlingController::ThrottlingProfile::SetNetworkConditions(
std::vector<MatchedNetworkConditions> conditions) {
std::vector<InterceptorMatcher> old_matchers;
std::swap(old_matchers, matchers_);
// This has quadratic (#conditions * #matchers_) complexity, but we expect
// clients to create relatively small numbers of different conditions at the
// same time. If this grows too large me need to build maps from the
// conditions here.
for (auto& [pattern, network_conditions] : conditions) {
std::unique_ptr<url_pattern::SimpleUrlPatternMatcher> pattern_matcher;
if (!pattern.empty()) {
auto maybe_pattern_matcher = url_pattern::SimpleUrlPatternMatcher::Create(
pattern, /*base_url=*/nullptr);
if (!maybe_pattern_matcher.has_value()) {
continue;
}
pattern_matcher = std::move(*maybe_pattern_matcher);
CHECK(pattern_matcher);
}
if (auto old_entry = FindConditions(old_matchers.begin(),
old_matchers.end(), network_conditions);
old_entry != old_matchers.end()) {
matchers_.emplace_back(std::move(*old_entry));
old_matchers.erase(old_entry);
matchers_.back().patterns.clear();
matchers_.back().patterns.emplace_back(std::move(pattern),
std::move(pattern_matcher));
} else if (auto new_entry = FindConditions(
matchers_.begin(), matchers_.end(), network_conditions);
new_entry != matchers_.end()) {
new_entry->patterns.emplace_back(std::move(pattern),
std::move(pattern_matcher));
} else {
matchers_.emplace_back(std::move(network_conditions));
matchers_.back().patterns.emplace_back(std::move(pattern),
std::move(pattern_matcher));
}
}
}
ThrottlingNetworkInterceptor*
ThrottlingController::ThrottlingProfile::FindInterceptor(
const GURL& url) const {
for (const InterceptorMatcher& matcher : matchers_) {
for (auto& pattern : matcher.patterns) {
if (pattern.first.empty() || pattern.second->Match(url)) {
return matcher.interceptor.get();
}
}
}
return nullptr;
}
void ThrottlingController::SetNetworkConditions(
const base::UnguessableToken& throttling_profile_id,
std::vector<MatchedNetworkConditions> matched_conditions) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto it = interceptors_.find(throttling_profile_id);
if (it == interceptors_.end()) {
if (matched_conditions.empty()) {
return;
}
ThrottlingProfile new_profile;
new_profile.SetNetworkConditions(matched_conditions);
interceptors_.emplace(throttling_profile_id, std::move(new_profile));
} else {
it->second.SetNetworkConditions(matched_conditions);
if (matched_conditions.empty()) {
interceptors_.erase(throttling_profile_id);
}
}
#if BUILDFLAG(IS_P2P_ENABLED)
auto global_conditions = std::find_if(
matched_conditions.begin(), matched_conditions.end(),
[](auto&& conditions) { return conditions.url_pattern.empty(); });
auto p2p_it = p2p_interceptors_.find(throttling_profile_id);
if (p2p_it == p2p_interceptors_.end()) {
if (global_conditions == matched_conditions.end()) {
return;
}
std::unique_ptr<ThrottlingP2PNetworkInterceptor> new_interceptor(
new ThrottlingP2PNetworkInterceptor());
new_interceptor->UpdateConditions(global_conditions->conditions);
p2p_interceptors_[throttling_profile_id] = std::move(new_interceptor);
} else {
if (global_conditions == matched_conditions.end()) {
p2p_it->second->UpdateConditions(NetworkConditions{});
p2p_interceptors_.erase(throttling_profile_id);
} else {
p2p_it->second->UpdateConditions(global_conditions->conditions);
}
}
#endif
}
ThrottlingNetworkInterceptor* ThrottlingController::FindInterceptor(
uint32_t net_log_source_id,
const GURL& url) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto source_profile_map_it =
net_log_source_profile_map_.find(net_log_source_id);
if (source_profile_map_it == net_log_source_profile_map_.end()) {
return nullptr;
}
auto it = interceptors_.find(source_profile_map_it->second);
return it != interceptors_.end() ? it->second.FindInterceptor(url) : nullptr;
}
#if BUILDFLAG(IS_P2P_ENABLED)
ThrottlingP2PNetworkInterceptor* ThrottlingController::FindP2PInterceptor(
uint32_t net_log_source_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto source_profile_map_it =
net_log_source_profile_map_.find(net_log_source_id);
if (source_profile_map_it == net_log_source_profile_map_.end()) {
return nullptr;
}
auto it = p2p_interceptors_.find(source_profile_map_it->second);
return it != p2p_interceptors_.end() ? it->second.get() : nullptr;
}
#endif
} // namespace network