chromium/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl.cc

// 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 "chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl.h"

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/time/clock.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "chromeos/ash/components/nearby/common/client/nearby_api_call_flow.h"
#include "chromeos/ash/components/nearby/common/client/nearby_api_call_flow_impl.h"
#include "chromeos/ash/components/nearby/common/scheduling/nearby_on_demand_scheduler.h"
#include "chromeos/ash/components/nearby/common/scheduling/nearby_scheduler_factory.h"
#include "chromeos/ash/components/nearby/presence/conversions/proto_conversions.h"
#include "chromeos/ash/components/nearby/presence/credentials/local_device_data_provider.h"
#include "chromeos/ash/components/nearby/presence/credentials/local_device_data_provider_impl.h"
#include "chromeos/ash/components/nearby/presence/credentials/nearby_presence_server_client.h"
#include "chromeos/ash/components/nearby/presence/credentials/nearby_presence_server_client_impl.h"
#include "chromeos/ash/components/nearby/presence/credentials/prefs.h"
#include "chromeos/ash/components/nearby/presence/proto/list_shared_credentials_rpc.pb.h"
#include "chromeos/ash/components/nearby/presence/proto/rpc_resources.pb.h"
#include "chromeos/ash/components/nearby/presence/proto/update_device_rpc.pb.h"
#include "components/cross_device/logging/logging.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "third_party/nearby/internal/proto/credential.pb.h"

namespace {

// static
bool g_is_credential_manager_set_for_testing_ = false;

const char kDeviceIdPrefix[] = "users/me/devices/";
const char kFirstTimeRegistrationFieldMaskPath[] = "display_name";
const char kUploadCredentialsFieldMaskPath[] = "certificates";
const base::TimeDelta kServerResponseTimeout = base::Seconds(5);
constexpr int kServerCommunicationMaxAttempts = 5;
const base::TimeDelta kSyncCredentialsDailyTimePeriod = base::Hours(24);
constexpr int kMaxUpdateCredentialRequestCount = 6;
std::vector<base::TimeDelta> kUpdateCredentialCoolDownPeriods = {
    base::Seconds(0), base::Seconds(15), base::Seconds(30), base::Minutes(1),
    base::Minutes(2), base::Minutes(5),  base::Minutes(10)};

bool HasCoolOffPeriodPassed(int update_credential_request_count,
                            base::Time last_daily_sync_success_time) {
  CHECK(update_credential_request_count <= kMaxUpdateCredentialRequestCount);
  return (base::Time::Now() - last_daily_sync_success_time) >=
         kUpdateCredentialCoolDownPeriods[update_credential_request_count];
}

}  // namespace

namespace ash::nearby::presence {

NearbyPresenceCredentialManagerImpl::Creator::Creator() = default;
NearbyPresenceCredentialManagerImpl::Creator::~Creator() = default;

// static
NearbyPresenceCredentialManagerImpl::Creator*
NearbyPresenceCredentialManagerImpl::Creator::Get() {
  static base::NoDestructor<NearbyPresenceCredentialManagerImpl::Creator>
      creator;
  return creator.get();
}

void NearbyPresenceCredentialManagerImpl::Creator::Create(
    PrefService* pref_service,
    signin::IdentityManager* identity_manager,
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
    const mojo::SharedRemote<mojom::NearbyPresence>& nearby_presence,
    CreateCallback on_created) {
  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": Creating NearbyPresenceCredentialManager";
  Create(pref_service, identity_manager, url_loader_factory, nearby_presence,
         std::make_unique<LocalDeviceDataProviderImpl>(pref_service,
                                                       identity_manager),
         std::move(on_created));
}

void NearbyPresenceCredentialManagerImpl::Creator::Create(
    PrefService* pref_service,
    signin::IdentityManager* identity_manager,
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
    const mojo::SharedRemote<mojom::NearbyPresence>& nearby_presence,
    std::unique_ptr<LocalDeviceDataProvider> local_device_data_provider,
    CreateCallback on_created) {
  CHECK(!has_credential_manager_been_created_ ||
        g_is_credential_manager_set_for_testing_);
  has_credential_manager_been_created_ = true;

  on_created_ = (std::move(on_created));

  // This can only be set via `SetNextCredentialManagerInstanceForTesting`
  // since the class assumes only one CredentialManager is created per lifetime,
  // and asserts that there isn't an existing CredentialManager outside of unit
  // tests.
  if (g_is_credential_manager_set_for_testing_) {
    CHECK(credential_manager_under_initialization_);
    std::move(on_created_)
        .Run(std::move(credential_manager_under_initialization_));
    g_is_credential_manager_set_for_testing_ = false;
    return;
  }

  CHECK(!credential_manager_under_initialization_);
  credential_manager_under_initialization_ =
      base::WrapUnique(new NearbyPresenceCredentialManagerImpl(
          pref_service, identity_manager, url_loader_factory, nearby_presence,
          std::move(local_device_data_provider)));

  if (!credential_manager_under_initialization_->IsLocalDeviceRegistered()) {
    CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
        << __func__
        << ": Device is not registered with server. "
           "Registering the local device.";
    credential_manager_under_initialization_->RegisterPresence(
        base::BindOnce(&NearbyPresenceCredentialManagerImpl::Creator::
                           OnCredentialManagerRegistered,
                       base::Unretained(this)));
    return;
  }

  credential_manager_under_initialization_->InitializeDeviceMetadata(
      base::BindOnce(&NearbyPresenceCredentialManagerImpl::Creator::
                         OnCredentialManagerInitialized,
                     base::Unretained(this)));
}

// static
void NearbyPresenceCredentialManagerImpl::Creator::
    SetNextCredentialManagerInstanceForTesting(
        std::unique_ptr<NearbyPresenceCredentialManager> credential_manager) {
  CHECK(credential_manager);
  CHECK(credential_manager->IsLocalDeviceRegistered());

  g_is_credential_manager_set_for_testing_ = true;
  Get()->credential_manager_under_initialization_ =
      std::move(credential_manager);
}

void NearbyPresenceCredentialManagerImpl::Creator::
    OnCredentialManagerRegistered(bool success) {
  CHECK(credential_manager_under_initialization_);
  CHECK(on_created_);

  if (!success) {
    CD_LOG(ERROR, Feature::NEARBY_INFRA)
        << __func__ << ": Credential manager failed to register.";
    // TODO(b/276307539): Add metrics to record failures.
    std::move(on_created_).Run(nullptr);
    return;
  }

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": Credential manager successfully registered.";

  CHECK(credential_manager_under_initialization_->IsLocalDeviceRegistered());
  std::move(on_created_)
      .Run(std::move(credential_manager_under_initialization_));
  credential_manager_under_initialization_ = nullptr;
}

void NearbyPresenceCredentialManagerImpl::Creator::
    OnCredentialManagerInitialized() {
  CHECK(on_created_);
  CHECK(credential_manager_under_initialization_);
  CHECK(credential_manager_under_initialization_->IsLocalDeviceRegistered());

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": Credential manager successfully initialized.";
  std::move(on_created_)
      .Run(std::move(credential_manager_under_initialization_));
}

NearbyPresenceCredentialManagerImpl::NearbyPresenceCredentialManagerImpl(
    PrefService* pref_service,
    signin::IdentityManager* identity_manager,
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
    const mojo::SharedRemote<mojom::NearbyPresence>& nearby_presence,
    std::unique_ptr<LocalDeviceDataProvider> local_device_data_provider)
    : pref_service_(pref_service),
      identity_manager_(identity_manager),
      local_device_data_provider_(std::move(local_device_data_provider)),
      nearby_presence_(nearby_presence),
      url_loader_factory_(url_loader_factory) {
  CHECK(pref_service_);
  CHECK(identity_manager_);
  CHECK(url_loader_factory_);
  CHECK(local_device_data_provider_);

  daily_credential_sync_scheduler_ =
      ash::nearby::NearbySchedulerFactory::CreatePeriodicScheduler(
          /*request_period=*/kSyncCredentialsDailyTimePeriod,
          /*retry_failures=*/true, /*require_connectivity=*/true,
          prefs::kNearbyPresenceSchedulingCredentialDailySyncPrefName,
          pref_service_,
          base::BindRepeating(
              &NearbyPresenceCredentialManagerImpl::StartDailySync,
              weak_ptr_factory_.GetWeakPtr()),
          Feature::NEARBY_INFRA, base::DefaultClock::GetInstance());
  daily_credential_sync_scheduler_->Start();
}

NearbyPresenceCredentialManagerImpl::~NearbyPresenceCredentialManagerImpl() =
    default;

bool NearbyPresenceCredentialManagerImpl::IsLocalDeviceRegistered() {
  return local_device_data_provider_->IsRegistrationCompleteAndUserInfoSaved();
}

void NearbyPresenceCredentialManagerImpl::RegisterPresence(
    base::OnceCallback<void(bool)> on_registered_callback) {
  CHECK(!IsLocalDeviceRegistered());
  on_registered_callback_ = std::move(on_registered_callback);

  first_time_registration_on_demand_scheduler_ =
      ash::nearby::NearbySchedulerFactory::CreateOnDemandScheduler(
          /*retry_failures=*/true,
          /*require_connectivity=*/true,
          prefs::kNearbyPresenceSchedulingFirstTimeRegistrationPrefName,
          pref_service_,
          base::BindRepeating(
              &NearbyPresenceCredentialManagerImpl::StartFirstTimeRegistration,
              weak_ptr_factory_.GetWeakPtr()),
          Feature::NEARBY_INFRA, base::DefaultClock::GetInstance());
  first_time_registration_on_demand_scheduler_->Start();
  first_time_registration_on_demand_scheduler_->MakeImmediateRequest();
}

void NearbyPresenceCredentialManagerImpl::UpdateCredentials() {
  if (is_daily_sync_in_progress_) {
    return;
  }

  // Reset the request counter if we are at the max request count and the max
  // request cooloff period has passed.
  if (update_credential_request_count_ >= kMaxUpdateCredentialRequestCount) {
    CHECK(last_daily_sync_success_time_.has_value());
    if (HasCoolOffPeriodPassed(kMaxUpdateCredentialRequestCount,
                               last_daily_sync_success_time_.value())) {
      update_credential_request_count_ = 0;
    } else {
      // We're still in a cool-off period. Don't continue yet.
      return;
    }
  }

  // Trigger daily sync if:
  //   a. No daily sync has yet occurred during this profile session lifetime
  //      (signaled by `last_daily_sync_success_time_` being unset).
  //   b. The cool-off period between successful daily sync attempts has passed.
  if (!last_daily_sync_success_time_.has_value() ||
      HasCoolOffPeriodPassed(update_credential_request_count_,
                             last_daily_sync_success_time_.value())) {
    update_credential_request_count_++;
    daily_credential_sync_scheduler_->MakeImmediateRequest();
  }
}

void NearbyPresenceCredentialManagerImpl::InitializeDeviceMetadata(
    base::OnceClosure on_metadata_initialized_callback) {
  (*nearby_presence_)
      ->UpdateLocalDeviceMetadata(proto::MetadataToMojom(
          local_device_data_provider_->GetDeviceMetadata()));
  std::move(on_metadata_initialized_callback).Run();
}

void NearbyPresenceCredentialManagerImpl::StartFirstTimeRegistration() {
  // The flow for first time registration is as follows:
  //      1. Register this device with the server.
  //      2. Generate this device's credentials.
  //      3. Upload this device's credentials.
  //      4. Download other devices' credentials.
  //      5. Save other devices' credentials.

  first_time_server_registration_attempts_needed_count_++;

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": Beginning first time registration.";

  // Construct a request for first time registration to let the server know
  // to return the user's name and image url.
  ash::nearby::proto::UpdateDeviceRequest request;
  request.mutable_device()->set_name(
      kDeviceIdPrefix + local_device_data_provider_->GetDeviceId());
  request.mutable_update_mask()->add_paths(kFirstTimeRegistrationFieldMaskPath);

  server_response_timer_.Start(
      FROM_HERE, kServerResponseTimeout,
      base::BindOnce(&NearbyPresenceCredentialManagerImpl::
                         HandleFirstTimeRegistrationTimeout,
                     weak_ptr_factory_.GetWeakPtr()));

  // Construct a HTTP client for the request. The HTTP client lifetime is
  // tied to a single request.
  server_client_ = NearbyPresenceServerClientImpl::Factory::Create(
      std::make_unique<ash::nearby::NearbyApiCallFlowImpl>(), identity_manager_,
      url_loader_factory_);
  server_client_->UpdateDevice(
      request,
      base::BindOnce(
          &NearbyPresenceCredentialManagerImpl::OnRegistrationRpcSuccess,
          weak_ptr_factory_.GetWeakPtr(),
          /*registration_request_start_time=*/base::TimeTicks::Now()),
      base::BindOnce(
          &NearbyPresenceCredentialManagerImpl::OnRegistrationRpcFailure,
          weak_ptr_factory_.GetWeakPtr()));
}

void NearbyPresenceCredentialManagerImpl::OnFirstTimeRegistrationComplete(
    metrics::FirstTimeRegistrationResult result) {
  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": First time registration completed with result: ["
      << ((result == metrics::FirstTimeRegistrationResult::kSuccess)
              ? "success"
              : "failure")
      << "]";
  metrics::RecordFirstTimeRegistrationFlowResult(result);
  CHECK(on_registered_callback_);
  std::move(on_registered_callback_)
      .Run(/*success=*/(result ==
                        metrics::FirstTimeRegistrationResult::kSuccess));
}

void NearbyPresenceCredentialManagerImpl::HandleFirstTimeRegistrationTimeout() {
  HandleFirstTimeRegistrationFailure(
      /*result=*/ash::nearby::NearbyHttpResult::kTimeout);
}

void NearbyPresenceCredentialManagerImpl::HandleFirstTimeRegistrationFailure(
    ash::nearby::NearbyHttpResult result) {
  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": Failed first time registration with result: " << result;

  server_client_.reset();
  metrics::RecordFirstTimeServerRegistrationFailureReason(result);

  // Allow the scheduler to exponentially attempt first time registration
  // until the max. Once it reaches the max attempts, notify consumers of
  // failure.
  if (first_time_registration_on_demand_scheduler_
          ->GetNumConsecutiveFailures() < kServerCommunicationMaxAttempts) {
    first_time_registration_on_demand_scheduler_->HandleResult(
        /*success=*/false);
    return;
  }

  // We've exceeded the max attempts; registration has failed.
  first_time_registration_on_demand_scheduler_->Stop();
  first_time_registration_on_demand_scheduler_.reset();
  first_time_server_registration_attempts_needed_count_ = 0;
  OnFirstTimeRegistrationComplete(
      metrics::FirstTimeRegistrationResult::kRegistrationWithServerFailure);
}

void NearbyPresenceCredentialManagerImpl::OnRegistrationRpcSuccess(
    base::TimeTicks registration_request_start_time,
    const ash::nearby::proto::UpdateDeviceResponse& response) {
  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__
      << ": Successfully registered device with the Nearby Presence server.";

  server_response_timer_.Stop();
  first_time_registration_on_demand_scheduler_->HandleResult(/*success=*/true);
  metrics::RecordFirstTimeServerRegistrationTotalAttemptsNeededCount(
      /*attempt_count=*/
      first_time_server_registration_attempts_needed_count_);
  first_time_server_registration_attempts_needed_count_ = 0;

  base::TimeDelta registration_duration =
      base::TimeTicks::Now() - registration_request_start_time;
  metrics::RecordFirstTimeServerRegistrationDuration(registration_duration);
  server_client_.reset();

  // Persist responses to be used to generate credentials.
  local_device_data_provider_->SaveUserRegistrationInfo(
      /*display_name=*/response.person_name(),
      /*image_url=*/response.image_url());

  // We've completed the 1st of 5 steps of first time registration:
  //   -> 1. Register this device with the server.
  //      2. Generate this device's credentials.
  //      3. Upload this device's credentials.
  //      4. Download other devices' credentials.
  //      5. Save other devices' credentials.
  // Next, kick off Step 2.
  (*nearby_presence_)
      ->UpdateLocalDeviceMetadataAndGenerateCredentials(
          proto::MetadataToMojom(
              local_device_data_provider_->GetDeviceMetadata()),
          base::BindOnce(&NearbyPresenceCredentialManagerImpl::
                             OnFirstTimeCredentialsGenerated,
                         weak_ptr_factory_.GetWeakPtr()));
}

void NearbyPresenceCredentialManagerImpl::OnRegistrationRpcFailure(
    ash::nearby::NearbyHttpError error) {
  server_response_timer_.Stop();
  HandleFirstTimeRegistrationFailure(
      /*result=*/ash::nearby::NearbyHttpErrorToResult(error));
}

void NearbyPresenceCredentialManagerImpl::OnFirstTimeCredentialsGenerated(
    std::vector<mojom::SharedCredentialPtr> shared_credentials,
    mojo_base::mojom::AbslStatusCode status) {
  if (status != mojo_base::mojom::AbslStatusCode::kOk) {
    CD_LOG(ERROR, Feature::NEARBY_INFRA)
        << __func__ << ": First time credentials failed to generate.";
    OnFirstTimeRegistrationComplete(metrics::FirstTimeRegistrationResult::
                                        kLocalCredentialGenerationFailure);
    return;
  }

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": First time credentials successfully generated.";

  // With generated credentials, the CredentialManager needs to upload the
  // credentials to the server, and persist them to disk in order to detect
  // changes.
  std::vector<::nearby::internal::SharedCredential> proto_shared_credentials;
  for (const auto& cred : shared_credentials) {
    proto_shared_credentials.push_back(
        proto::SharedCredentialFromMojom(cred.get()));
  }

  local_device_data_provider_->UpdatePersistedSharedCredentials(
      proto_shared_credentials);

  // We've completed the 2nd of 5 steps of first time registration:
  //      1. Register this device with the server.
  //   -> 2. Generate this device's credentials.
  //      3. Upload this device's credentials.
  //      4. Download other devices' credentials.
  //      5. Save other devices' credentials.
  // Next, kick off Step 3.
  ScheduleUploadCredentials(
      proto_shared_credentials,
      base::BindRepeating(
          &NearbyPresenceCredentialManagerImpl::OnFirstTimeCredentialsUpload,
          weak_ptr_factory_.GetWeakPtr()));
}

void NearbyPresenceCredentialManagerImpl::OnFirstTimeCredentialsUpload(
    bool success) {
  if (!success) {
    CD_LOG(ERROR, Feature::NEARBY_INFRA)
        << ": First time credential upload failed.";
    OnFirstTimeRegistrationComplete(
        metrics::FirstTimeRegistrationResult::kUploadLocalCredentialsFailure);
    return;
  }

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": First time credential upload succeeded.";

  // We've completed the 3rd of 5 steps of first time registration:
  //      1. Register this device with the server.
  //      2. Generate this device's credentials.
  //   -> 3. Upload this device's credentials.
  //      4. Download other devices' credentials.
  //      5. Save other devices' credentials.
  // Next, kick off Step 4.
  ScheduleDownloadCredentials(base::BindRepeating(
      &NearbyPresenceCredentialManagerImpl::OnFirstTimeCredentialsDownload,
      weak_ptr_factory_.GetWeakPtr()));
}

void NearbyPresenceCredentialManagerImpl::OnFirstTimeCredentialsDownload(
    std::vector<::nearby::internal::SharedCredential> credentials,
    bool success) {
  if (!success) {
    CD_LOG(ERROR, Feature::NEARBY_INFRA)
        << __func__ << ": First time credential download failed.";
    OnFirstTimeRegistrationComplete(metrics::FirstTimeRegistrationResult::
                                        kDownloadRemoteCredentialsFailure);
    return;
  }

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": First time credential download completed successfully.";

  // We've completed the 4th of 5 steps for first time registration.
  //      1. Register this device with the server.
  //      2. Generate this device's credentials.
  //      3. Upload this device's credentials.
  //   -> 4. Download other devices' credentials.
  //      5. Save other devices' credentials.
  // Next, kick off Step 5: save the remote shared credentials to the NP library
  // over mojo pipe.
  std::vector<mojom::SharedCredentialPtr> mojo_credentials;
  for (auto cred : credentials) {
    mojo_credentials.push_back(proto::SharedCredentialToMojom(cred));
  }

  (*nearby_presence_)
      ->UpdateRemoteSharedCredentials(
          std::move(mojo_credentials),
          local_device_data_provider_->GetAccountName(),
          base::BindOnce(&NearbyPresenceCredentialManagerImpl::
                             OnFirstTimeRemoteCredentialsSaved,
                         weak_ptr_factory_.GetWeakPtr()));
}

void NearbyPresenceCredentialManagerImpl::OnFirstTimeRemoteCredentialsSaved(
    mojo_base::mojom::AbslStatusCode status) {
  if (status != mojo_base::mojom::AbslStatusCode::kOk) {
    CD_LOG(ERROR, Feature::NEARBY_INFRA)
        << __func__ << ": First time credential save failed.";
    OnFirstTimeRegistrationComplete(
        metrics::FirstTimeRegistrationResult::kSaveRemoteCredentialsFailure);
    return;
  }

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": First time credential save succeeded.";

  local_device_data_provider_->SetRegistrationComplete(/*complete=*/true);
  OnFirstTimeRegistrationComplete(
      metrics::FirstTimeRegistrationResult::kSuccess);
}

void NearbyPresenceCredentialManagerImpl::StartDailySync() {
  // If the device has not been registered yet, this is a no-op, and is
  // considered a success, which reschedules the daily sync. This happens when
  // the First Time Registration flow is kicked off, and the daily sync event
  // fires.
  if (!IsLocalDeviceRegistered()) {
    daily_credential_sync_scheduler_->HandleResult(/*success=*/true);
    return;
  }

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": Beginning daily credential sync.";

  is_daily_sync_in_progress_ = true;

  // The flow for first time registration is as follows:
  //      1. Fetch this device's credentials.
  //      2. Upload this device's credentials if they have changed.
  //      3. Download other devices' credentials.
  //      4. Save other devices' credentials.
  //
  // Next, kick off Step 1.
  (*nearby_presence_)
      ->GetLocalSharedCredentials(
          local_device_data_provider_->GetAccountName(),
          base::BindOnce(
              &NearbyPresenceCredentialManagerImpl::OnGetLocalSharedCredentials,
              weak_ptr_factory_.GetWeakPtr()));
}

void NearbyPresenceCredentialManagerImpl::OnGetLocalSharedCredentials(
    std::vector<mojom::SharedCredentialPtr> shared_credentials,
    mojo_base::mojom::AbslStatusCode status) {
  // On failures, exponentially retry the daily sync flow.
  if (status != mojo_base::mojom::AbslStatusCode::kOk) {
    CD_LOG(ERROR, Feature::NEARBY_INFRA)
        << __func__ << ": Failed to retrieve local shared credentials.";
    daily_credential_sync_scheduler_->HandleResult(/*success=*/false);
    return;
  }

  // We've completed the 1st of 4 steps for daily credential sync.
  //   -> 1. Fetch this device's credentials.
  //      2. Upload this device's credentials if they have changed.
  //      3. Download other devices' credentials.
  //      4. Save other devices' credentials.
  //
  // Next, kick off Step 2.

  // Convert from mojo to proto and check for changes for the credentials.
  // Only upload if they have changed, otherwise proceed to the next step.
  std::vector<::nearby::internal::SharedCredential> proto_shared_credentials;
  for (const auto& cred : shared_credentials) {
    proto_shared_credentials.push_back(
        proto::SharedCredentialFromMojom(cred.get()));
  }

  // Update the credentials persisted to disk if they have changed, and
  // schedule an upload of the credentials.
  if (local_device_data_provider_->HaveSharedCredentialsChanged(
          proto_shared_credentials)) {
    CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
        << __func__
        << ": Persisted credentials have changed; scheduling upload of local "
           "updated credentials.";
    local_device_data_provider_->UpdatePersistedSharedCredentials(
        proto_shared_credentials);
    ScheduleUploadCredentials(
        proto_shared_credentials,
        base::BindRepeating(
            &NearbyPresenceCredentialManagerImpl::OnDailySyncCredentialUpload,
            weak_ptr_factory_.GetWeakPtr()));
    return;
  }

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__
      << ": Persisted credentials have not changed; "
         "scheduling download of remote credentials.";

  // If the local credentials haven't changed, don't upload them to the server.
  // We've completed the 2nd of 4 steps for daily credential sync.
  //      1. Fetch this device's credentials.
  //  ->  2. Upload this device's credentials if they have changed.
  //      3. Download other devices' credentials.
  //      4. Save other devices' credentials.
  //
  // Next, kick off Step 3.
  ScheduleDownloadCredentials(base::BindRepeating(
      &NearbyPresenceCredentialManagerImpl::OnDailySyncCredentialDownload,
      weak_ptr_factory_.GetWeakPtr()));
}

void NearbyPresenceCredentialManagerImpl::OnDailySyncCredentialUpload(
    bool success) {
  // On failures, exponentially retry the daily sync flow.
  if (!success) {
    CD_LOG(ERROR, Feature::NEARBY_INFRA)
        << __func__ << ": Failed to upload credentials.";
    daily_credential_sync_scheduler_->HandleResult(/*success=*/false);
    return;
  }

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": Scheduling download of remote credentials.";

  // We've completed the 2nd of 4 steps for daily credential sync.
  //      1. Fetch this device's credentials.
  //  ->  2. Upload this device's credentials if they have changed.
  //      3. Download other devices' credentials.
  //      4. Save other devices' credentials.
  //
  // Next, kick off Step 3.
  ScheduleDownloadCredentials(base::BindRepeating(
      &NearbyPresenceCredentialManagerImpl::OnDailySyncCredentialDownload,
      weak_ptr_factory_.GetWeakPtr()));
}

void NearbyPresenceCredentialManagerImpl::OnDailySyncCredentialDownload(
    std::vector<::nearby::internal::SharedCredential> credentials,
    bool success) {
  // On failures, exponentially retry the daily sync flow.
  if (!success) {
    CD_LOG(ERROR, Feature::NEARBY_INFRA)
        << __func__ << ": Failed to download remote credentials.";
    daily_credential_sync_scheduler_->HandleResult(/*success=*/false);
    return;
  }

  // We've completed the 3rd of 4 steps for daily credential sync.
  //      1. Fetch this device's credentials.
  //      2. Upload this device's credentials if they have changed.
  //  ->  3. Download other devices' credentials.
  //      4. Save other devices' credentials.
  //
  // Next, kick off Step 4.
  //
  // Convert the credentials and send them over the mojo pipe to the NP library.
  std::vector<mojom::SharedCredentialPtr> mojo_credentials;
  for (auto cred : credentials) {
    mojo_credentials.push_back(proto::SharedCredentialToMojom(cred));
  }

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__
      << ": Beginning attempt to save remote credentials "
         "to credential storage.";

  (*nearby_presence_)
      ->UpdateRemoteSharedCredentials(
          std::move(mojo_credentials),
          local_device_data_provider_->GetAccountName(),
          base::BindOnce(&NearbyPresenceCredentialManagerImpl::
                             OnDailySyncRemoteCredentialsSaved,
                         weak_ptr_factory_.GetWeakPtr()));
}

void NearbyPresenceCredentialManagerImpl::OnDailySyncRemoteCredentialsSaved(
    mojo_base::mojom::AbslStatusCode status) {
  // On failures, exponentially retry the daily sync flow.
  if (status != mojo_base::mojom::AbslStatusCode::kOk) {
    CD_LOG(ERROR, Feature::NEARBY_INFRA)
        << __func__
        << ": Failed to save remote credentials to credential storage.";
    daily_credential_sync_scheduler_->HandleResult(/*success=*/false);
    return;
  }

  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__
      << ": Successfully stored remote credentials to credential storage.";

  // We've completed the last of 4 steps for daily credential sync.
  //      1. Fetch this device's credentials.
  //      2. Upload this device's credentials if they have changed.
  //      3. Download other devices' credentials.
  //   -> 4. Save other devices' credentials.
  // Signal success to the scheduler, which causes it to reschedule for the
  // next daily sync.
  daily_credential_sync_scheduler_->HandleResult(/*success=*/true);

  is_daily_sync_in_progress_ = false;
  last_daily_sync_success_time_ = base::Time::Now();
}

void NearbyPresenceCredentialManagerImpl::ScheduleUploadCredentials(
    std::vector<::nearby::internal::SharedCredential> proto_shared_credentials,
    base::RepeatingCallback<void(bool)> on_upload) {
  upload_on_demand_scheduler_ =
      ash::nearby::NearbySchedulerFactory::CreateOnDemandScheduler(
          /*retry_failures=*/true,
          /*require_connectivity=*/true,
          prefs::kNearbyPresenceSchedulingUploadPrefName, pref_service_,
          base::BindRepeating(
              &NearbyPresenceCredentialManagerImpl::UploadCredentials,
              weak_ptr_factory_.GetWeakPtr(), proto_shared_credentials,
              std::move(on_upload)),
          Feature::NEARBY_INFRA, base::DefaultClock::GetInstance());
  upload_on_demand_scheduler_->Start();
  upload_on_demand_scheduler_->MakeImmediateRequest();
}

void NearbyPresenceCredentialManagerImpl::ScheduleDownloadCredentials(
    base::RepeatingCallback<
        void(std::vector<::nearby::internal::SharedCredential>, bool)>
        on_download) {
  download_on_demand_scheduler_ =
      ash::nearby::NearbySchedulerFactory::CreateOnDemandScheduler(
          /*retry_failures=*/true,
          /*require_connectivity=*/true,
          prefs::kNearbyPresenceSchedulingDownloadPrefName, pref_service_,
          base::BindRepeating(
              &NearbyPresenceCredentialManagerImpl::DownloadCredentials,
              weak_ptr_factory_.GetWeakPtr(), std::move(on_download)),
          Feature::NEARBY_INFRA, base::DefaultClock::GetInstance());
  download_on_demand_scheduler_->Start();
  download_on_demand_scheduler_->MakeImmediateRequest();
}

void NearbyPresenceCredentialManagerImpl::UploadCredentials(
    std::vector<::nearby::internal::SharedCredential> credentials,
    base::RepeatingCallback<void(bool)> upload_credentials_result_callback) {
  upload_credentials_attempts_needed_count_++;
  ash::nearby::proto::UpdateDeviceRequest request;
  request.mutable_device()->set_name(
      kDeviceIdPrefix + local_device_data_provider_->GetDeviceId());
  request.mutable_update_mask()->add_paths(kFirstTimeRegistrationFieldMaskPath);
  request.mutable_update_mask()->add_paths(kUploadCredentialsFieldMaskPath);

  std::vector<ash::nearby::proto::PublicCertificate> public_certificates;
  for (auto cred : credentials) {
    public_certificates.push_back(
        proto::PublicCertificateFromSharedCredential(cred));
  }
  *(request.mutable_device()->mutable_public_certificates()) = {
      public_certificates.begin(), public_certificates.end()};

  server_response_timer_.Start(
      FROM_HERE, kServerResponseTimeout,
      base::BindOnce(
          &NearbyPresenceCredentialManagerImpl::OnUploadCredentialsTimeout,
          weak_ptr_factory_.GetWeakPtr(), upload_credentials_result_callback));

  // Construct a HTTP client for the request. The HTTP client lifetime is
  // tied to a single request.
  CHECK(!server_client_);
  server_client_ = NearbyPresenceServerClientImpl::Factory::Create(
      std::make_unique<ash::nearby::NearbyApiCallFlowImpl>(), identity_manager_,
      url_loader_factory_);
  server_client_->UpdateDevice(
      request,
      base::BindOnce(
          &NearbyPresenceCredentialManagerImpl::OnUploadCredentialsSuccess,
          weak_ptr_factory_.GetWeakPtr(), upload_credentials_result_callback,
          /*upload_request_start_time=*/base::TimeTicks::Now()),
      base::BindOnce(
          &NearbyPresenceCredentialManagerImpl::OnUploadCredentialsFailure,
          weak_ptr_factory_.GetWeakPtr(), upload_credentials_result_callback));
}

void NearbyPresenceCredentialManagerImpl::HandleUploadCredentialsResult(
    base::RepeatingCallback<void(bool)> upload_credentials_callback,
    ash::nearby::NearbyHttpResult result) {
  server_client_.reset();

  CHECK(upload_on_demand_scheduler_);
  if (result != ash::nearby::NearbyHttpResult::kSuccess) {
    metrics::RecordSharedCredentialUploadAttemptFailureReason(result);

    // Allow the scheduler to exponentially attempt uploading credentials
    // until the max. Once it reaches the max attempts, notify consumers of
    // failure.
    if (upload_on_demand_scheduler_->GetNumConsecutiveFailures() <
        kServerCommunicationMaxAttempts) {
      upload_on_demand_scheduler_->HandleResult(
          /*success=*/false);
      return;
    }

    // We've exceeded the max attempts; registration has failed.
    upload_on_demand_scheduler_->Stop();
    metrics::RecordSharedCredentialUploadResult(/*success=*/false);
    upload_on_demand_scheduler_.reset();
    CHECK(upload_credentials_callback);
    upload_credentials_callback.Run(/*success=*/false);
    upload_credentials_attempts_needed_count_ = 0;
    return;
  }

  upload_on_demand_scheduler_->HandleResult(/*success=*/true);
  metrics::RecordSharedCredentialUploadResult(/*success=*/true);
  metrics::RecordSharedCredentialUploadTotalAttemptsNeededCount(
      upload_credentials_attempts_needed_count_);
  upload_on_demand_scheduler_.reset();
  upload_credentials_attempts_needed_count_ = 0;
  CHECK(upload_credentials_callback);
  upload_credentials_callback.Run(/*success=*/true);
}

void NearbyPresenceCredentialManagerImpl::OnUploadCredentialsTimeout(
    base::RepeatingCallback<void(bool)> upload_credentials_callback) {
  HandleUploadCredentialsResult(
      upload_credentials_callback,
      /*result=*/ash::nearby::NearbyHttpResult::kTimeout);
}

void NearbyPresenceCredentialManagerImpl::OnUploadCredentialsSuccess(
    base::RepeatingCallback<void(bool)> upload_credentials_callback,
    base::TimeTicks upload_request_start_time,
    const ash::nearby::proto::UpdateDeviceResponse& response) {
  // TODO(b/276307539): Log response and check for changes in user name and
  // image url returned from the server.

  server_response_timer_.Stop();
  base::TimeDelta upload_request_duration =
      base::TimeTicks::Now() - upload_request_start_time;
  metrics::RecordSharedCredentialUploadDuration(upload_request_duration);
  HandleUploadCredentialsResult(
      upload_credentials_callback,
      /*result=*/ash::nearby::NearbyHttpResult::kSuccess);
}

void NearbyPresenceCredentialManagerImpl::OnUploadCredentialsFailure(
    base::RepeatingCallback<void(bool)> upload_credentials_callback,
    ash::nearby::NearbyHttpError error) {
  server_response_timer_.Stop();
  HandleUploadCredentialsResult(
      upload_credentials_callback,
      /*result=*/ash::nearby::NearbyHttpErrorToResult(error));
}

void NearbyPresenceCredentialManagerImpl::DownloadCredentials(
    base::RepeatingCallback<
        void(std::vector<::nearby::internal::SharedCredential>, bool)>
        download_credentials_result_callback) {
  download_credentials_attempts_needed_count_++;
  ash::nearby::proto::ListSharedCredentialsRequest request;

  // TODO(hansberry): Populate with actual DUSI.
  request.set_dusi("test_dusi");
  request.set_identity_type(
      ash::nearby::proto::IdentityType::IDENTITY_TYPE_PRIVATE_GROUP);

  server_response_timer_.Start(
      FROM_HERE, kServerResponseTimeout,
      base::BindOnce(
          &NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsTimeout,
          weak_ptr_factory_.GetWeakPtr(),
          download_credentials_result_callback));

  // Construct a HTTP client for the request. The HTTP client lifetime is
  // tied to a single request.
  CHECK(!server_client_);
  server_client_ = NearbyPresenceServerClientImpl::Factory::Create(
      std::make_unique<ash::nearby::NearbyApiCallFlowImpl>(), identity_manager_,
      url_loader_factory_);
  server_client_->ListSharedCredentials(
      request,
      base::BindOnce(
          &NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsSuccess,
          weak_ptr_factory_.GetWeakPtr(), download_credentials_result_callback,
          /*download_request_start_time=*/base::TimeTicks::Now()),
      base::BindOnce(
          &NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsFailure,
          weak_ptr_factory_.GetWeakPtr(),
          download_credentials_result_callback));
}

void NearbyPresenceCredentialManagerImpl::HandleDownloadCredentialsResult(
    base::RepeatingCallback<
        void(std::vector<::nearby::internal::SharedCredential>, bool)>
        download_credentials_result_callback,
    ash::nearby::NearbyHttpResult result,
    std::vector<::nearby::internal::SharedCredential> credentials) {
  server_client_.reset();

  CHECK(download_on_demand_scheduler_);
  if (result != ash::nearby::NearbyHttpResult::kSuccess) {
    metrics::RecordSharedCredentialDownloadFailureReason(result);

    // Allow the scheduler to exponentially attempt downloading credentials
    // until the max. Once it reaches the max attempts, notify consumers of
    // failure.
    if (download_on_demand_scheduler_->GetNumConsecutiveFailures() <
        kServerCommunicationMaxAttempts) {
      download_on_demand_scheduler_->HandleResult(
          /*success=*/false);
      return;
    }

    // We've exceeded the max attempts; registration has failed.
    download_on_demand_scheduler_->Stop();
    metrics::RecordSharedCredentialDownloadResult(/*success=*/false);
    download_on_demand_scheduler_.reset();
    CHECK(download_credentials_result_callback);
    download_credentials_result_callback.Run(/*credentials=*/{},
                                             /*success=*/false);
    download_credentials_attempts_needed_count_ = 0;
    return;
  }

  download_on_demand_scheduler_->HandleResult(/*success=*/true);
  metrics::RecordSharedCredentialDownloadResult(/*success=*/true);
  metrics::RecordSharedCredentialDownloadTotalAttemptsNeededCount(
      download_credentials_attempts_needed_count_);
  download_on_demand_scheduler_.reset();
  download_credentials_attempts_needed_count_ = 0;
  CHECK(download_credentials_result_callback);
  download_credentials_result_callback.Run(/*credentials=*/credentials,
                                           /*success=*/true);
}

void NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsTimeout(
    base::RepeatingCallback<
        void(std::vector<::nearby::internal::SharedCredential>, bool)>
        download_credentials_result_callback) {
  HandleDownloadCredentialsResult(
      download_credentials_result_callback,
      /*result=*/ash::nearby::NearbyHttpResult::kTimeout, /*credentials=*/{});
}

void NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsSuccess(
    base::RepeatingCallback<
        void(std::vector<::nearby::internal::SharedCredential>, bool)>
        download_credentials_result_callback,
    base::TimeTicks download_request_start_time,
    const ash::nearby::proto::ListSharedCredentialsResponse& response) {
  server_response_timer_.Stop();
  base::TimeDelta download_request_duration =
      base::TimeTicks::Now() - download_request_start_time;
  metrics::RecordSharedCredentialDownloadDuration(download_request_duration);

  std::vector<::nearby::internal::SharedCredential> shared_credentials;
  for (auto remote_shared_credential : response.shared_credentials()) {
    shared_credentials.push_back(
        proto::RemoteSharedCredentialToThirdPartySharedCredential(
            remote_shared_credential));
  }

  HandleDownloadCredentialsResult(
      download_credentials_result_callback,
      /*result=*/ash::nearby::NearbyHttpResult::kSuccess,
      /*credentials=*/shared_credentials);
}

void NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsFailure(
    base::RepeatingCallback<
        void(std::vector<::nearby::internal::SharedCredential>, bool)>
        download_credentials_result_callback,
    ash::nearby::NearbyHttpError error) {
  server_response_timer_.Stop();
  HandleDownloadCredentialsResult(
      download_credentials_result_callback,
      /*result=*/ash::nearby::NearbyHttpErrorToResult(error),
      /*credentials=*/{});
}

}  // namespace ash::nearby::presence