| // 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/ash/dbus/encrypted_reporting_service_provider.h" |
| |
| #include <limits> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/functional/callback.h" |
| #include "base/memory/ref_counted_delete_on_sequence.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/strcat.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "chrome/browser/policy/messaging_layer/storage_selector/storage_selector.h" |
| #include "chrome/browser/policy/messaging_layer/upload/event_upload_size_controller.h" |
| #include "chrome/browser/policy/messaging_layer/upload/file_upload_impl.h" |
| #include "chrome/browser/policy/messaging_layer/upload/upload_client.h" |
| #include "chrome/browser/policy/messaging_layer/upload/upload_provider.h" |
| #include "chromeos/dbus/missive/history_tracker.h" |
| #include "chromeos/dbus/missive/missive_client.h" |
| #include "components/reporting/proto/synced/interface.pb.h" |
| #include "components/reporting/proto/synced/status.pb.h" |
| #include "components/reporting/resources/resource_manager.h" |
| #include "components/reporting/util/status.h" |
| #include "components/reporting/util/statusor.h" |
| #include "dbus/bus.h" |
| #include "dbus/exported_object.h" |
| #include "dbus/message.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| static constexpr uint64_t kDefaultMemoryAllocation = |
| 64u * 1024uLL * 1024uLL; // 64 MiB by default |
| |
| // UMA name for memory usage by uploads. |
| // The memory is logged as a `used` percent of `total`. Recorded every time we |
| // receive a new upload request. Expected to be well below 100%. |
| constexpr char kUploadMemoryUsage[] = "Browser.ERP.UploadMemoryUsagePercent"; |
| |
| void SendStatusAsResponse( |
| std::unique_ptr<dbus::Response> response, |
| dbus::ExportedObject::ResponseSender response_sender, |
| ::reporting::UploadEncryptedRecordResponse response_message, |
| ::reporting::StatusOr<std::list<int64_t>> result) { |
| if (result.has_value()) { |
| // Log cache state in `response_message` |
| for (const auto& seq_id : result.value()) { |
| response_message.add_cached_events_seq_ids(seq_id); |
| } |
| } else { |
| // Build `StatusProto` in `response_message` |
| result.error().SaveTo(response_message.mutable_status()); |
| } |
| |
| // Turn on/off the debug state flag. |
| response_message.set_health_data_logging_enabled( |
| ::reporting::HistoryTracker::Get()->debug_state()); |
| |
| // Encode whole `response_message` |
| dbus::MessageWriter writer(response.get()); |
| writer.AppendProtoAsArrayOfBytes(response_message); |
| |
| // Send `response` |
| std::move(response_sender).Run(std::move(response)); |
| } |
| |
| } // namespace |
| |
| // EncryptedReportingServiceProvider implementation. |
| |
| EncryptedReportingServiceProvider::EncryptedReportingServiceProvider( |
| std::unique_ptr<::reporting::EncryptedReportingUploadProvider> |
| upload_provider) |
| : origin_thread_id_(base::PlatformThread::CurrentId()), |
| origin_thread_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()), |
| memory_resource_(base::MakeRefCounted<::reporting::ResourceManager>( |
| kDefaultMemoryAllocation)), |
| upload_provider_(std::move(upload_provider)) { |
| CHECK(upload_provider_); |
| } |
| |
| EncryptedReportingServiceProvider::~EncryptedReportingServiceProvider() = |
| default; |
| |
| void EncryptedReportingServiceProvider::Start( |
| scoped_refptr<dbus::ExportedObject> exported_object) { |
| CHECK(OnOriginThread()); |
| exported_object->ExportMethod( |
| chromeos::kChromeReportingServiceInterface, |
| chromeos::kChromeReportingServiceUploadEncryptedRecordMethod, |
| base::BindRepeating( |
| &EncryptedReportingServiceProvider::RequestUploadEncryptedRecords, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&EncryptedReportingServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void EncryptedReportingServiceProvider::OnExported( |
| const std::string& interface_name, |
| const std::string& method_name, |
| bool success) { |
| LOG_IF(ERROR, !success) << "Failed to export " << interface_name << "." |
| << method_name; |
| } |
| |
| // static |
| ::reporting::ReportSuccessfulUploadCallback |
| EncryptedReportingServiceProvider::GetReportSuccessUploadCallback() { |
| chromeos::MissiveClient* const missive_client = |
| chromeos::MissiveClient::Get(); |
| return base::BindPostTask( |
| missive_client->origin_task_runner(), |
| base::BindRepeating( |
| [](base::WeakPtr<chromeos::MissiveClient> missive_client, |
| ::reporting::SequenceInformation sequence_information, |
| bool force_confirm) { |
| if (missive_client) { |
| missive_client->ReportSuccess(std::move(sequence_information), |
| force_confirm); |
| } |
| }, |
| missive_client->GetWeakPtr())); |
| } |
| |
| // static |
| ::reporting::EncryptionKeyAttachedCallback |
| EncryptedReportingServiceProvider::GetEncryptionKeyAttachedCallback() { |
| chromeos::MissiveClient* const missive_client = |
| chromeos::MissiveClient::Get(); |
| return base::BindPostTask( |
| missive_client->origin_task_runner(), |
| base::BindRepeating( |
| [](base::WeakPtr<chromeos::MissiveClient> missive_client, |
| ::reporting::SignedEncryptionInfo signed_encryption_info) { |
| if (missive_client) { |
| missive_client->UpdateEncryptionKey( |
| std::move(signed_encryption_info)); |
| } |
| }, |
| missive_client->GetWeakPtr())); |
| } |
| |
| // static |
| ::reporting::UpdateConfigInMissiveCallback |
| EncryptedReportingServiceProvider::GetUpdateConfigInMissiveCallback() { |
| chromeos::MissiveClient* const missive_client = |
| chromeos::MissiveClient::Get(); |
| return base::BindPostTask( |
| missive_client->origin_task_runner(), |
| base::BindRepeating( |
| [](base::WeakPtr<chromeos::MissiveClient> missive_client, |
| ::reporting::ListOfBlockedDestinations destinations) { |
| if (missive_client) { |
| missive_client->UpdateConfigInMissive(std::move(destinations)); |
| } |
| }, |
| missive_client->GetWeakPtr())); |
| } |
| |
| void EncryptedReportingServiceProvider::RequestUploadEncryptedRecords( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| CHECK(OnOriginThread()); |
| auto response = dbus::Response::FromMethodCall(method_call); |
| ::reporting::UploadEncryptedRecordResponse response_message; |
| |
| chromeos::MissiveClient* const missive_client = |
| chromeos::MissiveClient::Get(); |
| if (!missive_client) { |
| ::reporting::Status status{::reporting::error::FAILED_PRECONDITION, |
| "No Missive client available"}; |
| LOG(ERROR) << status; |
| SendStatusAsResponse(std::move(response), std::move(response_sender), |
| std::move(response_message), base::unexpected(status)); |
| return; |
| } |
| |
| if (!missive_client->has_valid_api_key()) { |
| response_message.set_disable(true); // Signal `missived` to disable itself. |
| ::reporting::Status status{ |
| ::reporting::error::FAILED_PRECONDITION, |
| "Cannot communicate with server, unsupported API Key"}; |
| LOG(ERROR) << status; |
| SendStatusAsResponse(std::move(response), std::move(response_sender), |
| std::move(response_message), base::unexpected(status)); |
| return; |
| } |
| |
| dbus::MessageReader reader(method_call); |
| base::span<const uint8_t> serialized_request_buf; |
| if (!reader.PopArrayOfBytes(&serialized_request_buf)) { |
| ::reporting::Status status{ |
| ::reporting::error::INVALID_ARGUMENT, |
| "Error reading UploadEncryptedRecordRequest as array of bytes"}; |
| LOG(ERROR) << "Unable to process UploadEncryptedRecordRequest. status: " |
| << status; |
| SendStatusAsResponse(std::move(response), std::move(response_sender), |
| std::move(response_message), base::unexpected(status)); |
| return; |
| } |
| |
| ::reporting::ScopedReservation scoped_reservation( |
| serialized_request_buf.size(), memory_resource_); |
| |
| // Update UMA on actual memory usage. |
| base::UmaHistogramPercentage( |
| kUploadMemoryUsage, |
| static_cast<int>(memory_resource_->GetUsed() * 100uL / |
| memory_resource_->GetTotal())); // Never zero. |
| |
| if (!scoped_reservation.reserved()) { |
| ::reporting::Status status{::reporting::error::RESOURCE_EXHAUSTED, |
| "UploadEncryptedRecordRequest has exhausted " |
| "assigned memory pool in Chrome"}; |
| LOG(ERROR) << "Unable to process UploadEncryptedRecordRequest. status: " |
| << status; |
| SendStatusAsResponse(std::move(response), std::move(response_sender), |
| std::move(response_message), base::unexpected(status)); |
| return; |
| } |
| |
| ::reporting::UploadEncryptedRecordRequest request; |
| if (!request.ParseFromArray(serialized_request_buf.data(), |
| serialized_request_buf.size())) { |
| ::reporting::Status status{ |
| ::reporting::error::INVALID_ARGUMENT, |
| "Failed to parse UploadEncryptedRecordRequest from array of " |
| "bytes."}; |
| LOG(ERROR) << "Unable to process UploadEncryptedRecordRequest. status: " |
| << status; |
| SendStatusAsResponse(std::move(response), std::move(response_sender), |
| std::move(response_message), base::unexpected(status)); |
| return; |
| } |
| |
| // Missive should always send the remaining storage capacity and new |
| // events rate. If not, probably an outdated version of missive is |
| // running. In this case, we ignore the effect of remaining storage |
| // capacity/new events rate and give it the max/min possible value. |
| const auto remaining_storage_capacity = |
| request.has_remaining_storage_capacity() |
| ? request.remaining_storage_capacity() |
| : std::numeric_limits<uint64_t>::max(); |
| const auto new_events_rate = |
| request.has_new_events_rate() ? request.new_events_rate() : 1U; |
| // Move events from |request| into a separate vector |records|, using more |
| // or less the same amount of memory that has been reserved above. |
| auto records{::reporting::EventUploadSizeController::BuildEncryptedRecords( |
| std::move(*request.mutable_encrypted_record()), |
| ::reporting::EventUploadSizeController( |
| network_condition_service_, new_events_rate, |
| remaining_storage_capacity, |
| ::reporting::FileUploadDelegate::kMaxUploadBufferSize))}; |
| |
| // Accept health data if present. |
| if (request.has_health_data()) { |
| ::reporting::HistoryTracker::Get()->set_data( |
| std::move(request.health_data()), base::DoNothing()); |
| } |
| |
| upload_provider_->RequestUploadEncryptedRecords( |
| request.need_encryption_keys(), std::move(records), |
| std::move(scoped_reservation), |
| base::BindPostTask( |
| origin_thread_runner_, |
| base::BindOnce(&SendStatusAsResponse, std::move(response), |
| std::move(response_sender), |
| std::move(response_message)))); |
| } |
| |
| bool EncryptedReportingServiceProvider::OnOriginThread() const { |
| return base::PlatformThread::CurrentId() == origin_thread_id_; |
| } |
| |
| // static |
| std::unique_ptr<::reporting::EncryptedReportingUploadProvider> |
| EncryptedReportingServiceProvider::GetDefaultUploadProvider() { |
| return std::make_unique<::reporting::EncryptedReportingUploadProvider>( |
| GetReportSuccessUploadCallback(), GetEncryptionKeyAttachedCallback(), |
| GetUpdateConfigInMissiveCallback()); |
| } |
| } // namespace ash |