blob: f708a48f2127af2ff01648d05f4532a03a7a35d1 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/policy/messaging_layer/upload/upload_provider.h"
#include <list>
#include <memory>
#include <utility>
#include "base/containers/flat_map.h"
#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/strcat.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/thread_annotations.h"
#include "build/build_config.h"
#include "chrome/browser/policy/messaging_layer/upload/configuration_file_controller.h"
#include "chrome/browser/policy/messaging_layer/upload/upload_client.h"
#include "chrome/browser/policy/messaging_layer/util/upload_declarations.h"
#include "components/reporting/proto/synced/configuration_file.pb.h"
#include "components/reporting/proto/synced/record.pb.h"
#include "components/reporting/proto/synced/status.pb.h"
#include "components/reporting/util/backoff_settings.h"
#include "components/reporting/util/status.h"
#include "components/reporting/util/statusor.h"
#include "net/base/backoff_entry.h"
namespace reporting {
namespace {
std::unique_ptr<ConfigurationFileController> CreateConfigurationFileController(
UploadClient::UpdateConfigInMissiveCallback update_config_in_missive_cb) {
#if BUILDFLAG(IS_CHROMEOS)
return std::make_unique<ConfigurationFileController>(
update_config_in_missive_cb);
#else // !BUILDFLAG(IS_CHROMEOS)
return nullptr;
#endif // BUILDFLAG(IS_CHROMEOS)
}
} // namespace
// EncryptedReportingUploadProvider refcounted helper class.
class EncryptedReportingUploadProvider::UploadHelper
: public base::RefCountedDeleteOnSequence<UploadHelper> {
public:
UploadHelper(ReportSuccessfulUploadCallback report_successful_upload_cb,
EncryptionKeyAttachedCallback encryption_key_attached_cb,
UpdateConfigInMissiveCallback update_config_in_missive_cb,
UploadClientBuilderCb upload_client_builder_cb,
scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner);
UploadHelper(const UploadHelper& other) = delete;
UploadHelper& operator=(const UploadHelper& other) = delete;
// Requests new cloud policy client (can be invoked on any thread)
void PostNewUploadClientRequest();
// Uploads encrypted records (can be invoked on any thread).
void EnqueueUpload(bool need_encryption_key,
std::vector<EncryptedRecord> records,
ScopedReservation scoped_reservation,
UploadEnqueuedCallback enqueued_cb) const;
private:
friend class base::RefCountedDeleteOnSequence<UploadHelper>;
friend class base::DeleteHelper<UploadHelper>;
// Refcounted object can only have private or protected destructor.
~UploadHelper();
// Stages of upload client creation, scheduled on a sequenced task runner.
void TryNewUploadClientRequest();
void OnUploadClientResult(
StatusOr<std::unique_ptr<UploadClient>> client_result);
// Helpers to send the configuration file gotten from the response
// to the `ConfigurationFileController`.
void UpdateConfigFile(ConfigFile file);
// Uploads encrypted records on sequenced task runner (and thus capable of
// detecting whether upload client is ready or not). If not ready,
// it will wait and then upload.
void EnqueueUploadInternal(bool need_encryption_key,
std::vector<EncryptedRecord> records,
ScopedReservation scoped_reservation,
UploadEnqueuedCallback enqueued_cb);
// Sequence task runner and checker used during
// `PostNewUploadClientRequest` processing.
// It is also used to protect `upload_client_`.
const scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
SEQUENCE_CHECKER(sequenced_task_checker_);
// Callbacks for successful upload, key delivery and configuration file
// attached.
const ReportSuccessfulUploadCallback report_successful_upload_cb_;
const EncryptionKeyAttachedCallback encryption_key_attached_cb_;
ConfigFileAttachedCallback config_file_attached_cb_;
// Callback for upload client creation.
UploadClientBuilderCb upload_client_builder_cb_;
// Tracking of asynchronous stages.
std::atomic<bool> upload_client_request_in_progress_{false};
const std::unique_ptr<::net::BackoffEntry> backoff_entry_;
// Stored data from upload requests before upload client is ready.
// Note that vectors of records submitted for upload are mapped by
// generation id (which is the same for all records being uploaded at once
// since they originate from the same priority queue). As a result, if
// the caller attempts to upload the same records multiple times (e.g.
// because it did not yet get a confirmation from server), we will only
// hold to one set of records.
// |stored_reservations_| reflect amount of memory assigned to the respective
// element in |stored_records_| (if it is assigned, otherwise it is absent
// from the map). unique_ptr is used, to enable moving/erasing the map
// elements. Guarded by sequenced_task_runner_.
base::flat_map</*generation_id*/ int64_t, std::vector<EncryptedRecord>>
stored_records_ GUARDED_BY_CONTEXT(sequenced_task_checker_);
base::flat_map</*generation_id*/ int64_t, std::unique_ptr<ScopedReservation>>
stored_reservations_ GUARDED_BY_CONTEXT(sequenced_task_checker_);
bool stored_need_encryption_key_ GUARDED_BY_CONTEXT(sequenced_task_checker_){
false};
int stored_config_file_version_ GUARDED_BY_CONTEXT(sequenced_task_checker_){
0};
// Upload client (protected by sequenced task runner). Once set, is used
// repeatedly.
std::unique_ptr<UploadClient> upload_client_
GUARDED_BY_CONTEXT(sequenced_task_checker_);
// Configuration file controller (protected by sequenced task runner). Once
// set, is used repeatedly.
std::unique_ptr<ConfigurationFileController> config_controller_
GUARDED_BY_CONTEXT(sequenced_task_checker_);
// Keep this last so that all weak pointers will be invalidated at the
// beginning of destruction.
base::WeakPtrFactory<UploadHelper> weak_ptr_factory_{this};
};
EncryptedReportingUploadProvider::UploadHelper::UploadHelper(
ReportSuccessfulUploadCallback report_successful_upload_cb,
EncryptionKeyAttachedCallback encryption_key_attached_cb,
UpdateConfigInMissiveCallback update_config_in_missive_cb,
UploadClientBuilderCb upload_client_builder_cb,
scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner)
: base::RefCountedDeleteOnSequence<UploadHelper>(sequenced_task_runner),
sequenced_task_runner_(std::move(sequenced_task_runner)),
report_successful_upload_cb_(report_successful_upload_cb),
encryption_key_attached_cb_(encryption_key_attached_cb),
upload_client_builder_cb_(std::move(upload_client_builder_cb)),
backoff_entry_(GetBackoffEntry()),
config_controller_(CreateConfigurationFileController(
std::move(update_config_in_missive_cb))) {
config_file_attached_cb_ =
base::BindPostTask(sequenced_task_runner_,
base::BindRepeating(&UploadHelper::UpdateConfigFile,
weak_ptr_factory_.GetWeakPtr()));
DETACH_FROM_SEQUENCE(sequenced_task_checker_);
}
EncryptedReportingUploadProvider::UploadHelper::~UploadHelper() {
// Weak pointer factory must be destructed on the sequence.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequenced_task_checker_);
}
void EncryptedReportingUploadProvider::UploadHelper::
PostNewUploadClientRequest() {
sequenced_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&UploadHelper::TryNewUploadClientRequest,
weak_ptr_factory_.GetWeakPtr()));
}
void EncryptedReportingUploadProvider::UploadHelper::
TryNewUploadClientRequest() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequenced_task_checker_);
if (upload_client_ != nullptr) {
return;
}
if (upload_client_request_in_progress_) {
return;
}
upload_client_request_in_progress_ = true;
sequenced_task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(
[](base::WeakPtr<UploadHelper> self) {
if (!self) {
return; // Provider expired
}
std::move(self->upload_client_builder_cb_)
.Run(base::BindPostTask(
self->sequenced_task_runner_,
base::BindRepeating(&UploadHelper::OnUploadClientResult,
self)));
},
weak_ptr_factory_.GetWeakPtr()),
backoff_entry_->GetTimeUntilRelease());
// Increase backoff_entry_ for next request.
backoff_entry_->InformOfRequest(/*succeeded=*/false);
}
void EncryptedReportingUploadProvider::UploadHelper::OnUploadClientResult(
StatusOr<std::unique_ptr<UploadClient>> client_result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequenced_task_checker_);
if (!client_result.has_value()) {
upload_client_request_in_progress_ = false;
PostNewUploadClientRequest();
return;
}
// Record upload client to be used.
upload_client_ = std::move(client_result.value());
backoff_entry_->InformOfRequest(/*succeeded=*/true);
upload_client_request_in_progress_ = false;
// Upload client is ready, upload all previously stored requests (if any).
while (!stored_records_.empty() || stored_need_encryption_key_) {
std::vector<EncryptedRecord> records;
ScopedReservation scoped_reservation;
if (!stored_records_.empty()) {
records = std::move(stored_records_.begin()->second);
auto it = stored_reservations_.find(stored_records_.begin()->first);
if (it != stored_reservations_.end()) {
scoped_reservation.HandOver(*it->second);
CHECK(!it->second->reserved());
stored_reservations_.erase(it);
}
stored_records_.erase(stored_records_.begin());
}
const bool need_encryption_key =
std::exchange(stored_need_encryption_key_, false);
upload_client_->EnqueueUpload(
need_encryption_key, stored_config_file_version_, std::move(records),
std::move(scoped_reservation),
base::BindOnce([](StatusOr<std::list<int64_t>> cached_records_seq_ids) {
LOG_IF(ERROR, !cached_records_seq_ids.has_value())
<< "Upload not enqueued, error="
<< cached_records_seq_ids.error();
}),
report_successful_upload_cb_, encryption_key_attached_cb_,
config_file_attached_cb_);
}
}
void EncryptedReportingUploadProvider::UploadHelper::UpdateConfigFile(
ConfigFile file) {
// This function is only called in ChromeOS so no nullptr check is needed.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequenced_task_checker_);
stored_config_file_version_ =
config_controller_->HandleConfigurationFile(std::move(file));
}
void EncryptedReportingUploadProvider::UploadHelper::EnqueueUpload(
bool need_encryption_key,
std::vector<EncryptedRecord> records,
ScopedReservation scoped_reservation,
UploadEnqueuedCallback enqueued_cb) const {
sequenced_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&UploadHelper::EnqueueUploadInternal,
weak_ptr_factory_.GetMutableWeakPtr(), need_encryption_key,
std::move(records), std::move(scoped_reservation),
Scoped<StatusOr<std::list<int64_t>>>(
std::move(enqueued_cb),
base::unexpected(Status(error::UNAVAILABLE,
"UploadHelper has been destructed")))));
}
// static
void EncryptedReportingUploadProvider::UploadHelper::EnqueueUploadInternal(
bool need_encryption_key,
std::vector<EncryptedRecord> records,
ScopedReservation scoped_reservation,
UploadEnqueuedCallback enqueued_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequenced_task_checker_);
if (upload_client_ == nullptr) {
stored_need_encryption_key_ |= need_encryption_key;
int64_t generation_id = 0;
if (!records.empty() && records.begin()->has_sequence_information() &&
records.begin()->sequence_information().has_generation_id()) {
generation_id = records.begin()->sequence_information().generation_id();
}
std::list<int64_t> cached_records_seq_ids;
for (const auto& record : records) {
cached_records_seq_ids.push_back(
record.sequence_information().sequencing_id());
}
stored_records_.emplace(generation_id, std::move(records));
stored_reservations_.emplace(
generation_id,
std::make_unique<ScopedReservation>(std::move(scoped_reservation)));
// Report success even though the upload has not been executed.
// All stored records are listed as cached, because we don't need to re-send
// them (although actual cache will be created later).
// And actual upload success is reported through two permanent repeating
// callbacks, once the client is created and upload is executed.
std::move(enqueued_cb).Run(std::move(cached_records_seq_ids));
return;
}
upload_client_->EnqueueUpload(
need_encryption_key, stored_config_file_version_, std::move(records),
std::move(scoped_reservation), std::move(enqueued_cb),
report_successful_upload_cb_, encryption_key_attached_cb_,
config_file_attached_cb_);
}
// EncryptedReportingUploadProvider implementation.
EncryptedReportingUploadProvider::EncryptedReportingUploadProvider(
ReportSuccessfulUploadCallback report_successful_upload_cb,
EncryptionKeyAttachedCallback encryption_key_attached_cb,
UpdateConfigInMissiveCallback update_config_in_missive_cb,
UploadClientBuilderCb upload_client_builder_cb)
: helper_(base::MakeRefCounted<UploadHelper>(
report_successful_upload_cb,
encryption_key_attached_cb,
update_config_in_missive_cb,
std::move(upload_client_builder_cb),
base::SequencedTaskRunner::GetCurrentDefault())) {
helper_->PostNewUploadClientRequest();
}
EncryptedReportingUploadProvider::~EncryptedReportingUploadProvider() = default;
void EncryptedReportingUploadProvider::RequestUploadEncryptedRecords(
bool need_encryption_key,
std::vector<EncryptedRecord> records,
ScopedReservation scoped_reservation,
UploadEnqueuedCallback enqueued_cb) {
CHECK(helper_);
helper_->EnqueueUpload(need_encryption_key, std::move(records),
std::move(scoped_reservation), std::move(enqueued_cb));
}
base::WeakPtr<EncryptedReportingUploadProvider>
EncryptedReportingUploadProvider::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
// static
EncryptedReportingUploadProvider::UploadClientBuilderCb
EncryptedReportingUploadProvider::GetUploadClientBuilder() {
return base::BindOnce(&UploadClient::Create);
}
} // namespace reporting