| // Copyright 2023 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/server_uploader.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/contains.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/task_runner.h" |
| #include "base/types/expected.h" |
| #include "chrome/browser/policy/messaging_layer/util/upload_declarations.h" |
| #include "components/policy/core/common/cloud/user_cloud_policy_manager.h" |
| #include "components/reporting/proto/synced/record.pb.h" |
| #include "components/reporting/proto/synced/record_constants.pb.h" |
| #include "components/reporting/resources/resource_manager.h" |
| #include "components/reporting/util/status.h" |
| #include "components/reporting/util/status_macros.h" |
| #include "components/reporting/util/statusor.h" |
| #include "components/reporting/util/task_runner_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| namespace reporting { |
| |
| ServerUploader::ServerUploader( |
| bool need_encryption_key, |
| int config_file_version, |
| std::vector<EncryptedRecord> records, |
| ScopedReservation scoped_reservation, |
| std::unique_ptr<RecordHandler> handler, |
| UploadEnqueuedCallback enqueued_cb, |
| ReportSuccessfulUploadCallback report_success_upload_cb, |
| EncryptionKeyAttachedCallback encryption_key_attached_cb, |
| ConfigFileAttachedCallback config_file_attached_cb, |
| CompletionCallback completion_cb, |
| scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner) |
| : TaskRunnerContext<CompletionResponse>(std::move(completion_cb), |
| sequenced_task_runner), |
| need_encryption_key_(need_encryption_key), |
| config_file_version_(config_file_version), |
| encrypted_records_(std::move(records)), |
| scoped_reservation_(std::move(scoped_reservation)), |
| enqueued_cb_(std::move(enqueued_cb)), |
| report_success_upload_cb_(std::move(report_success_upload_cb)), |
| encryption_key_attached_cb_(std::move(encryption_key_attached_cb)), |
| config_file_attached_cb_(std::move(config_file_attached_cb)), |
| handler_(std::move(handler)) { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| ServerUploader::~ServerUploader() = default; |
| |
| void ServerUploader::OnStart() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!handler_) { |
| Finalize( |
| base::unexpected(Status(error::INVALID_ARGUMENT, "handler was null"))); |
| return; |
| } |
| // Early exit if we don't have any records and do not need encryption key. |
| if (encrypted_records_.empty() && !need_encryption_key_) { |
| Finalize(base::unexpected( |
| Status(error::INVALID_ARGUMENT, "No records received for upload."))); |
| return; |
| } |
| |
| if (!encrypted_records_.empty()) { |
| const auto process_status = ProcessRecords(); |
| if (!process_status.ok()) { |
| Finalize(base::unexpected(process_status)); |
| return; |
| } |
| } |
| |
| HandleRecords(); |
| } |
| |
| Status ServerUploader::ProcessRecords() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| Status process_status; |
| |
| const int64_t expected_generation_id = |
| encrypted_records_.front().sequence_information().generation_id(); |
| int64_t expected_sequencing_id = |
| encrypted_records_.front().sequence_information().sequencing_id(); |
| |
| // Will stop processing records on the first record that fails to pass. |
| size_t records_added = 0; |
| for (const auto& encrypted_record : encrypted_records_) { |
| process_status = IsRecordValid(encrypted_record, expected_generation_id, |
| expected_sequencing_id); |
| if (!process_status.ok()) { |
| LOG(ERROR) << "Record was invalid or received out of order"; |
| break; |
| } |
| ++records_added; |
| ++expected_sequencing_id; |
| } |
| |
| if (records_added == 0) { |
| // No valid records found, report failure. |
| return process_status; |
| } |
| |
| // Some records are valid, discard the rest and continue. |
| encrypted_records_.resize(records_added); |
| return Status::StatusOK(); |
| } |
| |
| void ServerUploader::HandleRecords() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| handler_->HandleRecords( |
| need_encryption_key_, config_file_version_, std::move(encrypted_records_), |
| std::move(scoped_reservation_), std::move(enqueued_cb_), |
| base::BindPostTaskToCurrentDefault( |
| base::BindOnce(&ServerUploader::Finalize, base::Unretained(this))), |
| std::move(encryption_key_attached_cb_), |
| std::move(config_file_attached_cb_)); |
| } |
| |
| void ServerUploader::Finalize(CompletionResponse upload_result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (enqueued_cb_) { |
| // Finalized before upload has been enqueued - make a call now. |
| std::move(enqueued_cb_) |
| .Run(base::unexpected( |
| Status(error::NOT_FOUND, "Upload failed to enqueue"))); |
| } |
| |
| if (upload_result.has_value()) { |
| std::move(report_success_upload_cb_) |
| .Run(upload_result.value().sequence_information, |
| upload_result.value().force_confirm); |
| } else { |
| // Log any error except those listed below: |
| static constexpr std::array<error::Code, 2> kIgnoredCodes = { |
| // a transient state for managed device and an uninteresting one for |
| // unmanaged ones |
| error::NOT_FOUND, |
| // too many upload requests, tripped rate limiting and rejecting the |
| // upload (it will be resent later, so it does not cause any loss of |
| // data) |
| error::OUT_OF_RANGE, |
| }; |
| LOG_IF(WARNING, |
| !base::Contains(kIgnoredCodes, upload_result.error().code())) |
| << upload_result.error(); |
| } |
| Response(upload_result); |
| } |
| |
| Status ServerUploader::IsRecordValid( |
| const EncryptedRecord& encrypted_record, |
| const int64_t expected_generation_id, |
| const int64_t expected_sequencing_id) const { |
| // Test to ensure all records are in the same generation. |
| if (encrypted_record.sequence_information().generation_id() != |
| expected_generation_id) { |
| return Status(error::INVALID_ARGUMENT, |
| "Record does not have the correct generation"); |
| } |
| |
| if (encrypted_record.sequence_information().sequencing_id() != |
| expected_sequencing_id) { |
| return Status(error::INVALID_ARGUMENT, "Out of order sequencing_id"); |
| } |
| |
| return Status::StatusOK(); |
| } |
| } // namespace reporting |