chromium/chrome/browser/ash/dbus/encrypted_reporting_service_provider.cc

// 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 "build/chromeos_buildflags.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"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chromeos/dbus/missive/history_tracker.h"
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

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());
  }

#if BUILDFLAG(IS_CHROMEOS_ASH)
  // Turn on/off the debug state flag (for Ash only).
  response_message.set_health_data_logging_enabled(
      ::reporting::HistoryTracker::Get()->debug_state());
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

  // 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());

  if (!::reporting::StorageSelector::is_uploader_required()) {
    // We should never get to here, since the provider is only exported
    // when is_uploader_required() is true. Have this code only
    // in order to log configuration inconsistency.
    LOG(ERROR) << "Uploads are not expected in this configuration";
    return;
  }

  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;

  if (!::reporting::StorageSelector::is_uploader_required()) {
    // We should never get to here, since the provider is only exported when
    // is_uploader_required() is true. Have this code only as a door stopper in
    // order to let `missive` daemon log configuration inconsistency.
    ::reporting::Status status{
        ::reporting::error::FAILED_PRECONDITION,
        "Uploads are not expected in this configuration"};
    LOG(ERROR) << status;
    SendStatusAsResponse(std::move(response), std::move(response_sender),
                         std::move(response_message), base::unexpected(status));
    return;
  }

  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);
  const char* serialized_request_buf = nullptr;
  size_t serialized_request_buf_size = 0;
  if (!reader.PopArrayOfBytes(
          reinterpret_cast<const uint8_t**>(&serialized_request_buf),
          &serialized_request_buf_size)) {
    ::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,
                              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))};

#if BUILDFLAG(IS_CHROMEOS_ASH)
  // Accept health data if present (ChromeOS only)
  if (request.has_health_data()) {
    ::reporting::HistoryTracker::Get()->set_data(
        std::move(request.health_data()), base::DoNothing());
  }
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

  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