blob: 2b9001c227c914b9f561c46f17ebfd0ef1e123a3 [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 "extensions/browser/api/feedback_private/log_source_access_manager.h"
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "base/strings/string_split.h"
#include "base/task/post_task.h"
#include "base/task_runner_util.h"
#include "base/time/default_tick_clock.h"
#include "extensions/browser/api/api_resource_manager.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/browser/api/feedback_private/feedback_private_delegate.h"
#include "extensions/browser/api/feedback_private/log_source_resource.h"
namespace extensions {
namespace {
using LogSource = api::feedback_private::LogSource;
using ReadLogSourceParams = api::feedback_private::ReadLogSourceParams;
using ReadLogSourceResult = api::feedback_private::ReadLogSourceResult;
using SystemLogsResponse = system_logs::SystemLogsResponse;
// Default value of |g_max_num_burst_accesses|.
constexpr int kDefaultMaxNumBurstAccesses = 10;
// The minimum time between consecutive reads of a log source by a particular
// extension.
constexpr base::TimeDelta kDefaultRateLimitingTimeout =
base::TimeDelta::FromMilliseconds(1000);
// The maximum number of accesses on a single log source that can be allowed
// before the next recharge increment. See access_rate_limiter.h for more info.
int g_max_num_burst_accesses = kDefaultMaxNumBurstAccesses;
// If this is null, then |kDefaultRateLimitingTimeoutMs| is used as the timeout.
const base::TimeDelta* g_rate_limiting_timeout = nullptr;
base::TimeDelta GetMinTimeBetweenReads() {
return g_rate_limiting_timeout ? *g_rate_limiting_timeout
: kDefaultRateLimitingTimeout;
}
// SystemLogsResponse is a map of strings -> strings. The map value has the
// actual log contents, a string containing all lines, separated by newlines.
// This function extracts the individual lines and converts them into a vector
// of strings, each string containing a single line.
void GetLogLinesFromSystemLogsResponse(const SystemLogsResponse& response,
std::vector<std::string>* log_lines) {
for (const std::pair<std::string, std::string>& pair : response) {
std::vector<std::string> new_lines = base::SplitString(
pair.second, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
log_lines->reserve(log_lines->size() + new_lines.size());
log_lines->insert(log_lines->end(), new_lines.begin(), new_lines.end());
}
}
// Anonymizes the strings in |result|.
void AnonymizeResults(
scoped_refptr<feedback::AnonymizerToolContainer> anonymizer_container,
ReadLogSourceResult* result) {
feedback::AnonymizerTool* anonymizer = anonymizer_container->Get();
for (std::string& line : result->log_lines)
line = anonymizer->Anonymize(line);
}
} // namespace
LogSourceAccessManager::LogSourceAccessManager(content::BrowserContext* context)
: context_(context),
tick_clock_(base::DefaultTickClock::GetInstance()),
task_runner_for_anonymizer_(base::CreateSequencedTaskRunnerWithTraits(
// User visible as the feedback_api is used by the Chrome (OS)
// feedback extension while the user may be looking at a spinner.
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
anonymizer_container_(
base::MakeRefCounted<feedback::AnonymizerToolContainer>(
task_runner_for_anonymizer_,
/* first_party_extension_ids= */ nullptr)),
weak_factory_(this) {}
LogSourceAccessManager::~LogSourceAccessManager() {}
// static
void LogSourceAccessManager::SetMaxNumBurstAccessesForTesting(
int num_accesses) {
g_max_num_burst_accesses = num_accesses;
}
// static
void LogSourceAccessManager::SetRateLimitingTimeoutForTesting(
const base::TimeDelta* timeout) {
g_rate_limiting_timeout = timeout;
}
bool LogSourceAccessManager::FetchFromSource(const ReadLogSourceParams& params,
const std::string& extension_id,
ReadLogSourceCallback callback) {
int requested_resource_id =
params.reader_id ? *params.reader_id : kInvalidResourceId;
ApiResourceManager<LogSourceResource>* resource_manager =
ApiResourceManager<LogSourceResource>::Get(context_);
const ResourceId resource_id =
requested_resource_id != kInvalidResourceId
? requested_resource_id
: CreateResource(params.source, extension_id);
if (resource_id == kInvalidResourceId)
return false;
LogSourceResource* resource =
resource_manager->Get(extension_id, resource_id);
if (!resource)
return false;
// Enforce the rules: rate-limit access to the source from the current reader
// handle. If not enough time has elapsed since the last access, do not read
// from the source, but instead return an empty response. From the caller's
// perspective, there is no new data. There is no need for the caller to keep
// track of the time since last access.
if (!UpdateSourceAccessTime(resource_id)) {
std::move(callback).Run(
std::make_unique<api::feedback_private::ReadLogSourceResult>());
return true;
}
// If the API call requested a non-incremental access, clean up the
// SingleLogSource by removing its API resource. Even if the existing source
// were originally created as incremental, passing in incremental=false on a
// later access indicates that the source should be closed afterwards.
const bool delete_resource_when_done = !params.incremental;
resource->GetLogSource()->Fetch(
base::BindOnce(&LogSourceAccessManager::OnFetchComplete,
weak_factory_.GetWeakPtr(), extension_id, resource_id,
delete_resource_when_done, std::move(callback)));
return true;
}
void LogSourceAccessManager::OnFetchComplete(
const std::string& extension_id,
ResourceId resource_id,
bool delete_resource,
ReadLogSourceCallback callback,
std::unique_ptr<SystemLogsResponse> response) {
auto result = std::make_unique<ReadLogSourceResult>();
// Always return invalid resource ID if there is a cleanup.
result->reader_id = delete_resource ? kInvalidResourceId : resource_id;
GetLogLinesFromSystemLogsResponse(*response, &result->log_lines);
// Retrieve result pointer before the PostTaskAndReply to fix issues with
// an undefined execution order of arguments in a function call
// (std::move(result) being executed before result.get()).
ReadLogSourceResult* result_ptr = result.get();
task_runner_for_anonymizer_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(AnonymizeResults, anonymizer_container_,
base::Unretained(result_ptr)),
base::BindOnce(std::move(callback), std::move(result)));
if (delete_resource) {
// This should also remove the entry from |sources_|.
ApiResourceManager<LogSourceResource>::Get(context_)->Remove(extension_id,
resource_id);
}
}
void LogSourceAccessManager::RemoveHandle(ResourceId id) {
auto iter = open_handles_.find(id);
if (iter == open_handles_.end())
return;
const SourceAndExtension& source_and_extension = *iter->second;
const LogSource source = source_and_extension.source;
if (num_readers_per_source_.find(source) != num_readers_per_source_.end())
num_readers_per_source_[source]--;
open_handles_.erase(id);
}
LogSourceAccessManager::SourceAndExtension::SourceAndExtension(
LogSource source,
const std::string& extension_id)
: source(source), extension_id(extension_id) {}
LogSourceAccessManager::ResourceId LogSourceAccessManager::CreateResource(
LogSource source,
const std::string& extension_id) {
// Enforce the rules: Do not create too many SingleLogSource objects to read
// from a source, even if they are from different extensions.
if (GetNumActiveResourcesForSource(source) >= kMaxReadersPerSource)
return kInvalidResourceId;
auto new_resource = std::make_unique<LogSourceResource>(
extension_id, ExtensionsAPIClient::Get()
->GetFeedbackPrivateDelegate()
->CreateSingleLogSource(source));
auto* resource_manager = ApiResourceManager<LogSourceResource>::Get(context_);
// Create an ID for the resource using the API Resource Manager, but don't
// release ownership of it until a valid ID has been secured.
ResourceId resource_id = resource_manager->Add(new_resource.get());
if (resource_id == kInvalidResourceId)
return kInvalidResourceId;
if (open_handles_.find(resource_id) != open_handles_.end())
return kInvalidResourceId;
// Now that |resource_id| has been determined to be valid, release ownership
// of the LogSourceResource, which is now owned by the API resource manager.
new_resource.release();
// The resource ID isn't known until |new_resource| is added to the API
// Resource Manager, but it needs to be passed into the resource afterward, so
// that the resource can unregister itself from LogSourceAccessManager. It is
// passed in as part of a callback.
resource_manager->Get(extension_id, resource_id)
->set_unregister_callback(
base::Bind(&LogSourceAccessManager::RemoveHandle,
weak_factory_.GetWeakPtr(), resource_id));
open_handles_.emplace(
resource_id, std::make_unique<SourceAndExtension>(source, extension_id));
num_readers_per_source_[source]++;
return resource_id;
}
bool LogSourceAccessManager::UpdateSourceAccessTime(ResourceId id) {
auto iter = open_handles_.find(id);
if (iter == open_handles_.end())
return false;
const SourceAndExtension& key = *iter->second;
if (rate_limiters_.find(key) == rate_limiters_.end()) {
rate_limiters_.emplace(key, std::make_unique<AccessRateLimiter>(
g_max_num_burst_accesses,
GetMinTimeBetweenReads(), tick_clock_));
}
return rate_limiters_[key]->AttemptAccess();
}
size_t LogSourceAccessManager::GetNumActiveResourcesForSource(
LogSource source) const {
auto iter = num_readers_per_source_.find(source);
if (iter == num_readers_per_source_.end())
return 0;
return iter->second;
}
} // namespace extensions