chromium/chromeos/ash/services/device_sync/cryptauth_v2_device_manager_impl.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/services/device_sync/cryptauth_v2_device_manager_impl.h"

#include <utility>

#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/services/device_sync/attestation_certificates_syncer_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_client.h"
#include "chromeos/ash/services/device_sync/cryptauth_device_syncer_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_key_registry.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_logging.h"
#include "chromeos/ash/services/device_sync/synced_bluetooth_address_tracker_impl.h"

namespace ash {

namespace device_sync {

namespace {

void RecordDeviceSyncResult(CryptAuthDeviceSyncResult result) {
  base::UmaHistogramEnumeration("CryptAuth.DeviceSyncV2.Result.ResultType",
                                result.GetResultType());
  base::UmaHistogramEnumeration("CryptAuth.DeviceSyncV2.Result.ResultCode",
                                result.result_code());
  base::UmaHistogramBoolean(
      "CryptAuth.DeviceSyncV2.Result.DidDeviceRegistryChange",
      result.did_device_registry_change());
}

}  // namespace

// static
CryptAuthV2DeviceManagerImpl::Factory*
    CryptAuthV2DeviceManagerImpl::Factory::test_factory_ = nullptr;

// static
std::unique_ptr<CryptAuthV2DeviceManager>
CryptAuthV2DeviceManagerImpl::Factory::Create(
    const cryptauthv2::ClientAppMetadata& client_app_metadata,
    CryptAuthDeviceRegistry* device_registry,
    CryptAuthKeyRegistry* key_registry,
    CryptAuthClientFactory* client_factory,
    CryptAuthGCMManager* gcm_manager,
    CryptAuthScheduler* scheduler,
    PrefService* pref_service,
    AttestationCertificatesSyncer::GetAttestationCertificatesFunction
        get_attestation_certificates_function) {
  if (test_factory_) {
    return test_factory_->CreateInstance(client_app_metadata, device_registry,
                                         key_registry, client_factory,
                                         gcm_manager, scheduler, pref_service,
                                         get_attestation_certificates_function);
  }

  return base::WrapUnique(new CryptAuthV2DeviceManagerImpl(
      client_app_metadata, device_registry, key_registry, client_factory,
      gcm_manager, scheduler, pref_service,
      get_attestation_certificates_function));
}

// static
void CryptAuthV2DeviceManagerImpl::Factory::SetFactoryForTesting(
    Factory* test_factory) {
  test_factory_ = test_factory;
}

CryptAuthV2DeviceManagerImpl::Factory::~Factory() = default;

CryptAuthV2DeviceManagerImpl::CryptAuthV2DeviceManagerImpl(
    const cryptauthv2::ClientAppMetadata& client_app_metadata,
    CryptAuthDeviceRegistry* device_registry,
    CryptAuthKeyRegistry* key_registry,
    CryptAuthClientFactory* client_factory,
    CryptAuthGCMManager* gcm_manager,
    CryptAuthScheduler* scheduler,
    PrefService* pref_service,
    AttestationCertificatesSyncer::GetAttestationCertificatesFunction
        get_attestation_certificates_function)
    : synced_bluetooth_address_tracker_(
          SyncedBluetoothAddressTrackerImpl::Factory::Create(scheduler,
                                                             pref_service)),
      attestation_certificates_syncer_(
          AttestationCertificatesSyncerImpl::Factory::Create(
              scheduler,
              pref_service,
              get_attestation_certificates_function)),
      client_app_metadata_(client_app_metadata),
      device_registry_(device_registry),
      key_registry_(key_registry),
      client_factory_(client_factory),
      gcm_manager_(gcm_manager),
      scheduler_(scheduler),
      pref_service_(pref_service) {
  gcm_manager_->AddObserver(this);
}

CryptAuthV2DeviceManagerImpl::~CryptAuthV2DeviceManagerImpl() {
  gcm_manager_->RemoveObserver(this);
}

void CryptAuthV2DeviceManagerImpl::Start() {
  PA_LOG(VERBOSE)
      << "Starting CryptAuth v2 device manager with device registry:\n"
      << *device_registry_;

  scheduler_->StartDeviceSyncScheduling(
      scheduler_weak_ptr_factory_.GetWeakPtr());
}

const CryptAuthDeviceRegistry::InstanceIdToDeviceMap&
CryptAuthV2DeviceManagerImpl::GetSyncedDevices() const {
  return device_registry_->instance_id_to_device_map();
}

void CryptAuthV2DeviceManagerImpl::ForceDeviceSyncNow(
    const cryptauthv2::ClientMetadata::InvocationReason& invocation_reason,
    const std::optional<std::string>& session_id) {
  scheduler_->RequestDeviceSync(invocation_reason, session_id);
}

BetterTogetherMetadataStatus
CryptAuthV2DeviceManagerImpl::GetDeviceSyncerBetterTogetherMetadataStatus()
    const {
  if (!device_syncer_) {
    return BetterTogetherMetadataStatus::
        kStatusUnavailableBecauseNoDeviceSyncerSet;
  }

  return device_syncer_->better_together_metadata_status();
}

GroupPrivateKeyStatus
CryptAuthV2DeviceManagerImpl::GetDeviceSyncerGroupPrivateKeyStatus() const {
  if (!device_syncer_) {
    return GroupPrivateKeyStatus::kStatusUnavailableBecauseNoDeviceSyncerSet;
  }

  return device_syncer_->group_private_key_status();
}

std::optional<base::Time> CryptAuthV2DeviceManagerImpl::GetLastDeviceSyncTime()
    const {
  return scheduler_->GetLastSuccessfulDeviceSyncTime();
}

std::optional<base::TimeDelta>
CryptAuthV2DeviceManagerImpl::GetTimeToNextAttempt() const {
  return scheduler_->GetTimeToNextDeviceSyncRequest();
}

bool CryptAuthV2DeviceManagerImpl::IsDeviceSyncInProgress() const {
  return scheduler_->IsWaitingForDeviceSyncResult();
}

bool CryptAuthV2DeviceManagerImpl::IsRecoveringFromFailure() const {
  return scheduler_->GetNumConsecutiveDeviceSyncFailures() > 0;
}

void CryptAuthV2DeviceManagerImpl::OnDeviceSyncRequested(
    const cryptauthv2::ClientMetadata& client_metadata) {
  NotifyDeviceSyncStarted(client_metadata);

  current_client_metadata_ = client_metadata;

  base::UmaHistogramExactLinear(
      "CryptAuth.DeviceSyncV2.InvocationReason",
      current_client_metadata_->invocation_reason(),
      cryptauthv2::ClientMetadata::InvocationReason_ARRAYSIZE);

  PA_LOG(VERBOSE) << "Starting CryptAuth v2 DeviceSync.";
  device_syncer_ = CryptAuthDeviceSyncerImpl::Factory::Create(
      device_registry_, key_registry_, client_factory_,
      synced_bluetooth_address_tracker_.get(),
      attestation_certificates_syncer_.get(), pref_service_);
  device_syncer_->Sync(
      *current_client_metadata_, client_app_metadata_,
      base::BindOnce(&CryptAuthV2DeviceManagerImpl::OnDeviceSyncFinished,
                     base::Unretained(this)));
}

void CryptAuthV2DeviceManagerImpl::OnResyncMessage(
    const std::optional<std::string>& session_id,
    const std::optional<CryptAuthFeatureType>& feature_type) {
  PA_LOG(VERBOSE) << "Received GCM message to re-sync devices (session ID: "
                  << session_id.value_or("[No session ID]") << ").";

  ForceDeviceSyncNow(cryptauthv2::ClientMetadata::SERVER_INITIATED, session_id);
}

void CryptAuthV2DeviceManagerImpl::OnDeviceSyncFinished(
    CryptAuthDeviceSyncResult device_sync_result) {
  device_syncer_.reset();

  std::stringstream prefix;
  prefix << "DeviceSync attempt with invocation reason "
         << current_client_metadata_->invocation_reason();
  std::stringstream suffix;
  suffix << "with result code " << device_sync_result.result_code() << ".";
  switch (device_sync_result.GetResultType()) {
    case CryptAuthDeviceSyncResult::ResultType::kSuccess:
      PA_LOG(INFO) << prefix.str() << " succeeded  " << suffix.str();
      break;
    case CryptAuthDeviceSyncResult::ResultType::kNonFatalError:
      PA_LOG(WARNING) << prefix.str() << " finished with non-fatal errors "
                      << suffix.str();
      break;
    case CryptAuthDeviceSyncResult::ResultType::kFatalError:
      PA_LOG(ERROR) << prefix.str() << " failed " << suffix.str();
      break;
  }

  PA_LOG(INFO) << "The device registry "
               << (device_sync_result.did_device_registry_change()
                       ? "changed."
                       : "did not change.");
  PA_LOG(VERBOSE) << "Device registry:\n" << *device_registry_;

  current_client_metadata_.reset();

  RecordDeviceSyncResult(device_sync_result);

  scheduler_->HandleDeviceSyncResult(device_sync_result);

  std::optional<base::TimeDelta> time_to_next_attempt = GetTimeToNextAttempt();
  if (time_to_next_attempt) {
    PA_LOG(INFO) << "Time until next DeviceSync attempt: "
                 << *time_to_next_attempt << ".";
  } else {
    PA_LOG(INFO) << "No future DeviceSync requests currently scheduled.";
  }

  if (!device_sync_result.IsSuccess()) {
    PA_LOG(INFO) << "Number of consecutive DeviceSync failures: "
                 << scheduler_->GetNumConsecutiveDeviceSyncFailures() << ".";
  }

  NotifyDeviceSyncFinished(device_sync_result);
}

}  // namespace device_sync

}  // namespace ash