// 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/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/gtest_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/time/clock.h"
#include "chromeos/ash/components/nearby/common/scheduling/fake_nearby_scheduler_factory.h"
#include "chromeos/ash/components/nearby/presence/conversions/proto_conversions.h"
#include "chromeos/ash/components/nearby/presence/credentials/fake_local_device_data_provider.h"
#include "chromeos/ash/components/nearby/presence/credentials/fake_nearby_presence_server_client.h"
#include "chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager.h"
#include "chromeos/ash/components/nearby/presence/credentials/prefs.h"
#include "chromeos/ash/services/nearby/public/cpp/fake_nearby_presence.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/bindings/shared_remote.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const std::string kDeviceName = "Test's Chromebook";
const std::string kUserName = "Test Tester";
const std::string kProfileUrl = "https://example.com";
const std::string kDeviceId = "0123456789";
const base::TimeDelta kServerResponseTimeout = base::Seconds(5);
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)};
constexpr int kServerCommunicationMaxAttempts = 5;
const std::vector<uint8_t> kBluetoothMacAddress = {0x12, 0x34, 0x56,
0x78, 0x9a, 0xbc};
const std::vector<uint8_t> kMetadataDeviceId = {
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
const long kId1 = 111;
const long kId2 = 222;
const long kId3 = 333;
const std::vector<uint8_t> kKeySeed = {
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44};
ash::nearby::presence::mojom::SharedCredentialPtr BuildSharedCredential(
long id) {
ash::nearby::presence::mojom::SharedCredentialPtr cred =
ash::nearby::presence::mojom::SharedCredential::New();
cred->id = id;
// To communicate across the wire this field on the mojo struct needs to be
// set since the mojo wire checks for this array to be size 32.
cred->key_seed = kKeySeed;
return cred;
}
std::vector<ash::nearby::presence::mojom::SharedCredentialPtr>
BuildSharedCredentials() {
std::vector<ash::nearby::presence::mojom::SharedCredentialPtr>
shared_credentials;
shared_credentials.push_back(BuildSharedCredential(kId1));
shared_credentials.push_back(BuildSharedCredential(kId2));
shared_credentials.push_back(BuildSharedCredential(kId3));
return shared_credentials;
}
::nearby::internal::DeviceIdentityMetaData BuildTestMetadata() {
return ash::nearby::presence::proto::BuildMetadata(
/*device_type=*/::nearby::internal::DeviceType::DEVICE_TYPE_CHROMEOS,
/*device_name=*/kDeviceName,
/*bluetooth_mac_address=*/
std::string(kBluetoothMacAddress.begin(), kBluetoothMacAddress.end()),
/*device_id=*/
std::string(kMetadataDeviceId.begin(), kMetadataDeviceId.end()));
}
} // namespace
namespace ash::nearby::presence {
class TestCreator final : public NearbyPresenceCredentialManagerImpl::Creator {
public:
~TestCreator() override = default;
void 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) override {
NearbyPresenceCredentialManagerImpl::Creator::Create(
pref_service, identity_manager, url_loader_factory, nearby_presence,
std::move(local_device_data_provider), std::move(on_created));
}
void ResetHasCredentialManagerBeenCreated() {
has_credential_manager_been_created_ = false;
}
};
class NearbyPresenceCredentialManagerImplTest : public testing::Test {
protected:
NearbyPresenceCredentialManagerImplTest() {
shared_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
base::BindOnce([]() -> network::mojom::URLLoaderFactory* {
ADD_FAILURE() << "Did not expect this to actually be used";
return nullptr;
}));
}
void SetUp() override {
ash::nearby::NearbySchedulerFactory::SetFactoryForTesting(
&scheduler_factory_);
NearbyPresenceServerClientImpl::Factory::SetFactoryForTesting(
&server_client_factory_);
local_device_data_provider_ =
std::make_unique<FakeLocalDeviceDataProvider>();
fake_local_device_data_provider_ =
static_cast<FakeLocalDeviceDataProvider*>(
local_device_data_provider_.get());
// Simulate first time registration flow.
fake_local_device_data_provider_->SetRegistrationComplete(false);
// Even before registration, the LocalDeviceDataProvider can provide
// all fields in its Metadata. Simulate that.
fake_local_device_data_provider_->SetDeviceMetadata(BuildTestMetadata());
// Simulate the device id which will be generated in a call to
// |GetDeviceId|.
fake_local_device_data_provider_->SetDeviceId(kDeviceId);
// Simulate the credentials being generated in the NP library.
fake_nearby_presence_.SetGenerateCredentialsResponse(
BuildSharedCredentials(), mojo_base::mojom::AbslStatusCode::kOk);
}
void TearDown() override {
ash::nearby::NearbySchedulerFactory::SetFactoryForTesting(nullptr);
NearbyPresenceServerClientImpl::Factory::SetFactoryForTesting(nullptr);
credential_manager_creator_.ResetHasCredentialManagerBeenCreated();
fake_local_device_data_provider_ = nullptr;
}
void TriggerFirstTimeRegistrationSuccess() {
// Simulate the scheduler notifying the CredentialManager that the task is
// ready when it has network connectivity.
const raw_ptr<FakeNearbyScheduler, DanglingUntriaged>
first_time_registration_scheduler =
scheduler_factory_.pref_name_to_on_demand_instance()
.find(
prefs::
kNearbyPresenceSchedulingFirstTimeRegistrationPrefName)
->second.fake_scheduler;
first_time_registration_scheduler->InvokeRequestCallback();
// Mock and return the server response.
ash::nearby::proto::UpdateDeviceResponse response;
response.set_person_name(kUserName);
response.set_image_url(kProfileUrl);
server_client_factory_.fake_server_client()
->InvokeUpdateDeviceSuccessCallback(response);
}
void TriggerLocalCredentialUploadSuccess() {
// Simulate the scheduler notifying the CredentialManager that the upload
// task is ready when it has network connectivity.
const raw_ptr<FakeNearbyScheduler, DanglingUntriaged> upload_scheduler =
scheduler_factory_.pref_name_to_on_demand_instance()
.find(prefs::kNearbyPresenceSchedulingUploadPrefName)
->second.fake_scheduler;
upload_scheduler->InvokeRequestCallback();
// Mock and return the server response.
ash::nearby::proto::UpdateDeviceResponse update_credentials_response;
server_client_factory_.fake_server_client()
->InvokeUpdateDeviceSuccessCallback(update_credentials_response);
}
void TriggerDownloadRemoteCredentialSuccess() {
// Simulate the scheduler notifying the CredentialManager that the download
// task is ready when it has network connectivity.
const raw_ptr<FakeNearbyScheduler, DanglingUntriaged> download_scheduler =
scheduler_factory_.pref_name_to_on_demand_instance()
.find(prefs::kNearbyPresenceSchedulingDownloadPrefName)
->second.fake_scheduler;
download_scheduler->InvokeRequestCallback();
// Next, mock and return the server response for fetching remote device
// public certificates.
ash::nearby::proto::ListSharedCredentialsResponse certificate_response;
server_client_factory_.fake_server_client()
->InvokeListSharedCredentialsSuccessCallback({certificate_response});
}
void CreateCredentialManager(base::OnceClosure on_created) {
credential_manager_creator_.Create(
&pref_service_, identity_test_env_.identity_manager(),
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_),
fake_nearby_presence_.shared_remote(),
std::move(local_device_data_provider_),
base::BindLambdaForTesting(
[&, callback = base::BindOnce(std::move(on_created))](
std::unique_ptr<NearbyPresenceCredentialManager>
credential_manager) {
credential_manager_ = std::move(credential_manager);
// OnceCallback::Run() may only be invoked on a non-const rvalue,
// so we convert it here to allow the execution.
std::move(const_cast<base::OnceClosure&>(callback)).Run();
}));
}
void SimulateDeviceAlreadyRegistered() {
// Simulate that it is not first time registration flow.
fake_local_device_data_provider_->SetRegistrationComplete(true);
fake_local_device_data_provider_->SaveUserRegistrationInfo(
/*display_name=*/kUserName, /*image_url=*/kProfileUrl);
}
void SetUpDailySync(bool have_credentials_changed,
bool get_local_shared_credentials_success) {
SimulateDeviceAlreadyRegistered();
// Simulate that the local credentials have not changed, which is expected
// to not trigger a local credential upload to the server.
fake_local_device_data_provider_->SetHaveSharedCredentialsChanged(
have_credentials_changed);
if (get_local_shared_credentials_success) {
// Simulate the local device credentials stored in the NP library and
// retrieved successfully.
fake_nearby_presence_.SetLocalSharedCredentialsResponse(
BuildSharedCredentials(), mojo_base::mojom::AbslStatusCode::kOk);
} else {
// Simulate the local device credentials retrieved unsuccessfully.
fake_nearby_presence_.SetLocalSharedCredentialsResponse(
/*credentials=*/{}, mojo_base::mojom::AbslStatusCode::kUnknown);
}
// Simulate the remote device credentials being successfully set in the
// NP library.
fake_nearby_presence_.SetUpdateRemoteCredentialsStatus(
mojo_base::mojom::AbslStatusCode::kOk);
base::RunLoop update_local_device_metadata_run_loop;
fake_nearby_presence_.SetUpdateLocalDeviceMetadataCallback(
update_local_device_metadata_run_loop.QuitClosure());
base::RunLoop create_credential_run_loop;
CreateCredentialManager(create_credential_run_loop.QuitClosure());
create_credential_run_loop.Run();
EXPECT_TRUE(credential_manager_);
update_local_device_metadata_run_loop.Run();
daily_sync_scheduler_ =
scheduler_factory_.pref_name_to_periodic_instance()
.find(prefs::kNearbyPresenceSchedulingCredentialDailySyncPrefName)
->second.fake_scheduler;
}
void TriggerDailySync(bool have_credentials_changed,
bool get_local_shared_credentials_success) {
SetUpDailySync(have_credentials_changed,
get_local_shared_credentials_success);
// Simulate the scheduler notifying the CredentialManager that the task is
// ready when it has network connectivity.
daily_sync_scheduler_->InvokeRequestCallback();
}
void UpdateCredentialsDailySync() {
base::RunLoop get_local_creds_run_loop;
fake_local_device_data_provider_->SetHaveSharedCredentialsChangedCallback(
get_local_creds_run_loop.QuitClosure());
credential_manager_->UpdateCredentials();
daily_sync_scheduler_->InvokeRequestCallback();
// A second call to `UpdateCredentials()` is ignored since the first one is
// in progress.
credential_manager_->UpdateCredentials();
// Required to send messages across mojo pipe for saving remote device
// credentials.
base::RunLoop save_remote_creds_run_loop;
daily_sync_scheduler_->SetHandleResultCallback(
save_remote_creds_run_loop.QuitClosure());
get_local_creds_run_loop.Run();
// Expect no calls to trigger a credential upload, which is indicated by the
// creation of an on demand upload scheduler.
EXPECT_EQ(scheduler_factory_.pref_name_to_on_demand_instance().find(
prefs::kNearbyPresenceSchedulingUploadPrefName),
scheduler_factory_.pref_name_to_on_demand_instance().end());
// Simulate a successful download of credentials from the server.
TriggerDownloadRemoteCredentialSuccess();
save_remote_creds_run_loop.Run();
}
protected:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
FakeNearbyPresence fake_nearby_presence_;
TestingPrefServiceSimple pref_service_;
signin::IdentityTestEnvironment identity_test_env_;
raw_ptr<FakeLocalDeviceDataProvider> fake_local_device_data_provider_ =
nullptr;
raw_ptr<FakeNearbyScheduler, DanglingUntriaged> daily_sync_scheduler_ =
nullptr;
scoped_refptr<network::SharedURLLoaderFactory> shared_factory_;
std::unique_ptr<LocalDeviceDataProvider> local_device_data_provider_;
std::unique_ptr<NearbyPresenceCredentialManager> credential_manager_;
TestCreator credential_manager_creator_;
FakeNearbyPresenceServerClient::Factory server_client_factory_;
ash::nearby::FakeNearbySchedulerFactory scheduler_factory_;
network::TestURLLoaderFactory test_url_loader_factory_;
base::HistogramTester histogram_tester_;
};
TEST_F(NearbyPresenceCredentialManagerImplTest, SetDeviceMetadata) {
SimulateDeviceAlreadyRegistered();
base::RunLoop update_local_device_metadata_run_loop;
fake_nearby_presence_.SetUpdateLocalDeviceMetadataCallback(
update_local_device_metadata_run_loop.QuitClosure());
base::RunLoop create_credential_run_loop;
CreateCredentialManager(create_credential_run_loop.QuitClosure());
create_credential_run_loop.Run();
EXPECT_TRUE(credential_manager_);
update_local_device_metadata_run_loop.Run();
auto* local_device_metadata = fake_nearby_presence_.GetLocalDeviceMetadata();
EXPECT_TRUE(local_device_metadata);
EXPECT_EQ(mojom::PresenceDeviceType::kChromeos,
local_device_metadata->device_type);
EXPECT_EQ(kDeviceName, local_device_metadata->device_name);
EXPECT_EQ(kBluetoothMacAddress, local_device_metadata->bluetooth_mac_address);
EXPECT_EQ(kMetadataDeviceId, local_device_metadata->device_id);
}
TEST_F(NearbyPresenceCredentialManagerImplTest, RegistrationSuccess) {
// Wait until after the generated credentials are saved to continue the test.
base::RunLoop generate_creds_run_loop;
fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
generate_creds_run_loop.QuitClosure());
base::RunLoop create_credential_manager_run_loop;
CreateCredentialManager(create_credential_manager_run_loop.QuitClosure());
TriggerFirstTimeRegistrationSuccess();
// Any requests to daily sync are expected to be ignored. If they are not
// ignored, a crash occurs due to the `server_cliet`_ already existing. Test
// this by ensuring no crash.
daily_sync_scheduler_ =
scheduler_factory_.pref_name_to_periodic_instance()
.find(prefs::kNearbyPresenceSchedulingCredentialDailySyncPrefName)
->second.fake_scheduler;
daily_sync_scheduler_->InvokeRequestCallback();
generate_creds_run_loop.Run();
TriggerLocalCredentialUploadSuccess();
TriggerDownloadRemoteCredentialSuccess();
create_credential_manager_run_loop.Run();
EXPECT_TRUE(credential_manager_);
EXPECT_TRUE(credential_manager_->IsLocalDeviceRegistered());
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.FirstTimeRegistration.Result",
/*bucket: kSuccess=*/0, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.FirstTimeServerRegistration.FailureReason",
0);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.FirstTimeServerRegistration."
"AttemptsNeededCount",
/*bucket: attempt_count=*/1, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.FirstTimeServerRegistration."
"ServerRequestDuration",
1);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Upload.Result", /*bucket: success=*/true, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.FailureReason", 0);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Upload.AttemptsNeededCount",
/*bucket: attempt_count=*/1, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.ServerRequestDuration", 1);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.Result", /*bucket: success=*/true,
1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Download.FailureReason", 0);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.AttemptsNeededCount",
/*bucket: attempt_count=*/1, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Download.ServerRequestDuration", 1);
}
TEST_F(NearbyPresenceCredentialManagerImplTest, ServerRegistrationTimeout) {
base::RunLoop create_credential_manager_run_loop;
CreateCredentialManager(create_credential_manager_run_loop.QuitClosure());
// Simulate the max number of failures caused by a server response timeout.
const raw_ptr<FakeNearbyScheduler, DanglingUntriaged>
first_time_registration_scheduler =
scheduler_factory_.pref_name_to_on_demand_instance()
.find(
prefs::kNearbyPresenceSchedulingFirstTimeRegistrationPrefName)
->second.fake_scheduler;
first_time_registration_scheduler->SetNumConsecutiveFailures(
kServerCommunicationMaxAttempts);
first_time_registration_scheduler->InvokeRequestCallback();
task_environment_.FastForwardBy(kServerResponseTimeout);
create_credential_manager_run_loop.Run();
EXPECT_FALSE(credential_manager_);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.FirstTimeRegistration.Result",
/*bucket: kRegistrationWithServerFailure=*/1, 1);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.FirstTimeServerRegistration.FailureReason",
/*bucket: NearbyHttpResult::kTimeout*/
ash::nearby::NearbyHttpResult::kTimeout, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.FirstTimeServerRegistration."
"ServerRequestDuration",
0);
}
TEST_F(NearbyPresenceCredentialManagerImplTest, ServerRegistrationFailure) {
base::RunLoop create_credential_manager_run_loop;
CreateCredentialManager(create_credential_manager_run_loop.QuitClosure());
// Simulate the max number of failures caused by a RPC failure.
const raw_ptr<FakeNearbyScheduler, DanglingUntriaged>
first_time_registration_scheduler =
scheduler_factory_.pref_name_to_on_demand_instance()
.find(
prefs::kNearbyPresenceSchedulingFirstTimeRegistrationPrefName)
->second.fake_scheduler;
first_time_registration_scheduler->SetNumConsecutiveFailures(
kServerCommunicationMaxAttempts);
first_time_registration_scheduler->InvokeRequestCallback();
server_client_factory_.fake_server_client()->InvokeUpdateDeviceErrorCallback(
ash::nearby::NearbyHttpError::kInternalServerError);
create_credential_manager_run_loop.Run();
EXPECT_FALSE(credential_manager_);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.FirstTimeRegistration.Result",
/*bucket: kRegistrationWithServerFailure=*/1, 1);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.FirstTimeServerRegistration.FailureReason",
/*bucket: NearbyHttpResult::kHttpErrorInternalServerError*/
ash::nearby::NearbyHttpResult::kHttpErrorInternalServerError, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.FirstTimeServerRegistration."
"ServerRequestDuration",
0);
}
TEST_F(NearbyPresenceCredentialManagerImplTest, CredentialGenerationFailure) {
// Simulate the credentials being failed to be generated in the NP library.
fake_nearby_presence_.SetGenerateCredentialsResponse(
{}, mojo_base::mojom::AbslStatusCode::kFailedPrecondition);
base::RunLoop create_credential_manager_run_loop;
CreateCredentialManager(create_credential_manager_run_loop.QuitClosure());
TriggerFirstTimeRegistrationSuccess();
create_credential_manager_run_loop.Run();
EXPECT_FALSE(credential_manager_);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.FirstTimeRegistration.Result",
/*bucket: kLocalCredentialGenerationFailure=*/2, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.FirstTimeServerRegistration."
"ServerRequestDuration",
1);
}
TEST_F(NearbyPresenceCredentialManagerImplTest,
UploadCredentialsServerTimeout) {
// Wait until after the generated credentials are saved to continue the test.
base::RunLoop generate_creds_run_loop;
fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
generate_creds_run_loop.QuitClosure());
base::RunLoop create_credential_manager_run_loop;
CreateCredentialManager(create_credential_manager_run_loop.QuitClosure());
TriggerFirstTimeRegistrationSuccess();
// Required for credentials to be generated and passed over the mojo pipe
generate_creds_run_loop.Run();
// Simulate the scheduler notifying the CredentialManager that the task is
// ready when it has network connectivity.
const raw_ptr<FakeNearbyScheduler, DanglingUntriaged>
first_time_upload_scheduler =
scheduler_factory_.pref_name_to_on_demand_instance()
.find(prefs::kNearbyPresenceSchedulingUploadPrefName)
->second.fake_scheduler;
first_time_upload_scheduler->InvokeRequestCallback();
// Simulate the max number of failures caused by a server response timeout.
first_time_upload_scheduler->SetNumConsecutiveFailures(
kServerCommunicationMaxAttempts);
task_environment_.FastForwardBy(kServerResponseTimeout);
create_credential_manager_run_loop.Run();
EXPECT_FALSE(credential_manager_);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Upload.Result", /*bucket: success=*/false,
1);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Upload.FailureReason",
/*bucket: NearbyHttpResult::kTimeout*/
ash::nearby::NearbyHttpResult::kTimeout, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.ServerRequestDuration", 0);
}
TEST_F(NearbyPresenceCredentialManagerImplTest, UploadCredentialsFailure) {
// Wait until after the generated credentials are saved to continue the test.
base::RunLoop generate_creds_run_loop;
fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
generate_creds_run_loop.QuitClosure());
base::RunLoop create_credential_manager_run_loop;
CreateCredentialManager(create_credential_manager_run_loop.QuitClosure());
TriggerFirstTimeRegistrationSuccess();
// Required for credentials to be generated and passed over the mojo pipe.
generate_creds_run_loop.Run();
// Simulate the scheduler notifying the CredentialManager that the upload
// task is ready when it has network connectivity.
const raw_ptr<FakeNearbyScheduler, DanglingUntriaged>
first_time_upload_scheduler =
scheduler_factory_.pref_name_to_on_demand_instance()
.find(prefs::kNearbyPresenceSchedulingUploadPrefName)
->second.fake_scheduler;
// Simulate the max number of failures caused by a RPC failure.
first_time_upload_scheduler->SetNumConsecutiveFailures(
kServerCommunicationMaxAttempts);
first_time_upload_scheduler->InvokeRequestCallback();
server_client_factory_.fake_server_client()->InvokeUpdateDeviceErrorCallback(
ash::nearby::NearbyHttpError::kInternalServerError);
create_credential_manager_run_loop.Run();
EXPECT_FALSE(credential_manager_);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Upload.Result", /*bucket: success=*/false,
1);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Upload.FailureReason",
/*bucket: NearbyHttpResult::kHttpErrorInternalServerError*/
ash::nearby::NearbyHttpResult::kHttpErrorInternalServerError, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.ServerRequestDuration", 0);
}
TEST_F(NearbyPresenceCredentialManagerImplTest, DownloadCredentialsFailure) {
// Wait until after the generated credentials are saved to continue the test.
base::RunLoop generate_creds_run_loop;
fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
generate_creds_run_loop.QuitClosure());
base::RunLoop create_credential_manager_run_loop;
CreateCredentialManager(create_credential_manager_run_loop.QuitClosure());
TriggerFirstTimeRegistrationSuccess();
// Required for credentials to be generated and passed over the mojo pipe.
generate_creds_run_loop.Run();
TriggerLocalCredentialUploadSuccess();
// Simulate the scheduler notifying the CredentialManager that the download
// task is ready when it has network connectivity.
const raw_ptr<FakeNearbyScheduler, DanglingUntriaged>
first_time_download_scheduler =
scheduler_factory_.pref_name_to_on_demand_instance()
.find(prefs::kNearbyPresenceSchedulingDownloadPrefName)
->second.fake_scheduler;
// Simulate the max number of failures caused by a RPC failure.
first_time_download_scheduler->SetNumConsecutiveFailures(
kServerCommunicationMaxAttempts);
first_time_download_scheduler->InvokeRequestCallback();
ash::nearby::proto::ListSharedCredentialsResponse certificate_response;
server_client_factory_.fake_server_client()
->InvokeListSharedCredentialsErrorCallback(
ash::nearby::NearbyHttpError::kInternalServerError);
create_credential_manager_run_loop.Run();
EXPECT_FALSE(credential_manager_);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.Result", /*bucket: success=*/false,
1);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.FailureReason",
/*bucket: NearbyHttpResult::kHttpErrorInternalServerError*/
ash::nearby::NearbyHttpResult::kHttpErrorInternalServerError, 1);
}
TEST_F(NearbyPresenceCredentialManagerImplTest, DownloadCredentialsTimeout) {
// Wait until after the generated credentials are saved to continue the test.
base::RunLoop generate_creds_run_loop;
fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
generate_creds_run_loop.QuitClosure());
base::RunLoop create_credential_manager_run_loop;
CreateCredentialManager(create_credential_manager_run_loop.QuitClosure());
TriggerFirstTimeRegistrationSuccess();
// Required for credentials to be generated and passed over the mojo pipe.
generate_creds_run_loop.Run();
TriggerLocalCredentialUploadSuccess();
// Simulate the scheduler notifying the CredentialManager that the download
// task is ready when it has network connectivity.
const raw_ptr<FakeNearbyScheduler, DanglingUntriaged>
first_time_download_scheduler =
scheduler_factory_.pref_name_to_on_demand_instance()
.find(prefs::kNearbyPresenceSchedulingDownloadPrefName)
->second.fake_scheduler;
first_time_download_scheduler->InvokeRequestCallback();
// Simulate the max number of failures caused by a server response timeout.
first_time_download_scheduler->SetNumConsecutiveFailures(
kServerCommunicationMaxAttempts);
task_environment_.FastForwardBy(kServerResponseTimeout);
create_credential_manager_run_loop.Run();
EXPECT_FALSE(credential_manager_);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.Result", /*bucket: success=*/false,
1);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.FailureReason",
/*bucket: NearbyHttpResult::kTimeout*/
ash::nearby::NearbyHttpResult::kTimeout, 1);
}
TEST_F(NearbyPresenceCredentialManagerImplTest,
UpdateRemoteCredentialsFailure) {
// Wait until after the generated credentials are saved to continue the test.
base::RunLoop generate_creds_run_loop;
fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
generate_creds_run_loop.QuitClosure());
// Simulate the remote device credentials being unsuccessfully set in the
// NP library.
fake_nearby_presence_.SetUpdateRemoteCredentialsStatus(
mojo_base::mojom::AbslStatusCode::kDeadlineExceeded);
base::RunLoop create_credential_manager_run_loop;
CreateCredentialManager(create_credential_manager_run_loop.QuitClosure());
TriggerFirstTimeRegistrationSuccess();
// Required for credentials to be generated and passed over the mojo pipe.
generate_creds_run_loop.Run();
TriggerLocalCredentialUploadSuccess();
TriggerDownloadRemoteCredentialSuccess();
create_credential_manager_run_loop.Run();
EXPECT_FALSE(credential_manager_);
}
TEST_F(NearbyPresenceCredentialManagerImplTest,
DailySyncSuccess_LocalCredentialsChanged) {
// Required to send messages across mojo pipe for retrieving local device
// credentials.
base::RunLoop get_local_creds_run_loop;
fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
get_local_creds_run_loop.QuitClosure());
// Simulate that the local credentials have changed, which is expected to
// trigger a local credential upload to the server.
TriggerDailySync(/*have_credentials_changed=*/true,
/*get_local_shared_credentials_success=*/true);
// Required to send messages across mojo pipe for saving remote device
// credentials.
base::RunLoop save_remote_creds_run_loop;
daily_sync_scheduler_->SetHandleResultCallback(
save_remote_creds_run_loop.QuitClosure());
get_local_creds_run_loop.Run();
// Simulate a successful result from the server. This call also enforces
// that an upload request has been made.
TriggerLocalCredentialUploadSuccess();
// Simulate a successful download of credentials from the server.
TriggerDownloadRemoteCredentialSuccess();
save_remote_creds_run_loop.Run();
// Expect daily sync success, which only happens after credentials are
// saved to the NP library.
EXPECT_TRUE(daily_sync_scheduler_->handled_results().front());
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Upload.Result", /*bucket: success=*/true, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.FailureReason", 0);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Upload.AttemptsNeededCount",
/*bucket: attempt_count=*/1, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.ServerRequestDuration", 1);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.Result", /*bucket: success=*/true,
1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Download.FailureReason", 0);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.AttemptsNeededCount",
/*bucket: attempt_count=*/1, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Download.ServerRequestDuration", 1);
}
TEST_F(NearbyPresenceCredentialManagerImplTest,
DailySyncSuccess_LocalCredentialsDidntChanged) {
base::RunLoop get_local_creds_run_loop;
fake_local_device_data_provider_->SetHaveSharedCredentialsChangedCallback(
get_local_creds_run_loop.QuitClosure());
TriggerDailySync(/*have_credentials_changed=*/false,
/*get_local_shared_credentials_success=*/true);
// Required to send messages across mojo pipe for saving remote device
// credentials.
base::RunLoop save_remote_creds_run_loop;
daily_sync_scheduler_->SetHandleResultCallback(
save_remote_creds_run_loop.QuitClosure());
get_local_creds_run_loop.Run();
// Expect no calls to trigger a credential upload, which is indicated by the
// creation of an on demand upload scheduler.
EXPECT_EQ(scheduler_factory_.pref_name_to_on_demand_instance().find(
prefs::kNearbyPresenceSchedulingUploadPrefName),
scheduler_factory_.pref_name_to_on_demand_instance().end());
// Simulate a successful download of credentials from the server.
TriggerDownloadRemoteCredentialSuccess();
save_remote_creds_run_loop.Run();
// Expect daily sync success, which only happens after credentials are
// saved to the NP library.
EXPECT_TRUE(daily_sync_scheduler_->handled_results().front());
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.Result", 0);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.FailureReason", 0);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.AttemptsNeededCount", 0);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.ServerRequestDuration", 0);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.Result", /*bucket: success=*/true,
1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Download.FailureReason", 0);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.AttemptsNeededCount",
/*bucket: attempt_count=*/1, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Download.ServerRequestDuration", 1);
}
TEST_F(NearbyPresenceCredentialManagerImplTest,
DailySyncFailure_GetLocalCredentialFailure) {
TriggerDailySync(/*have_credentials_changed=*/false,
/*get_local_shared_credentials_success=*/false);
// Required because no way to inject a QuitClosure() into the
// FakeLocalDeviceProvider, since it is unused in this flow.
base::RunLoop().RunUntilIdle();
// Expect no calls to trigger a credential upload, which is indicated by the
// creation of an on demand upload scheduler.
EXPECT_EQ(scheduler_factory_.pref_name_to_on_demand_instance().find(
prefs::kNearbyPresenceSchedulingUploadPrefName),
scheduler_factory_.pref_name_to_on_demand_instance().end());
// Expect daily sync failure, which also indicates exponential retries.
EXPECT_FALSE(daily_sync_scheduler_->handled_results().front());
}
TEST_F(NearbyPresenceCredentialManagerImplTest,
DailySyncFailure_UploadCredentialsFailure) {
base::RunLoop get_local_creds_run_loop;
fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
get_local_creds_run_loop.QuitClosure());
TriggerDailySync(/*have_credentials_changed=*/true,
/*get_local_shared_credentials_success=*/true);
get_local_creds_run_loop.Run();
// Simulate failure to upload credentials to the server.
const raw_ptr<FakeNearbyScheduler, DanglingUntriaged> upload_scheduler =
scheduler_factory_.pref_name_to_on_demand_instance()
.find(prefs::kNearbyPresenceSchedulingUploadPrefName)
->second.fake_scheduler;
upload_scheduler->SetNumConsecutiveFailures(kServerCommunicationMaxAttempts);
upload_scheduler->InvokeRequestCallback();
server_client_factory_.fake_server_client()->InvokeUpdateDeviceErrorCallback(
ash::nearby::NearbyHttpError::kInternalServerError);
// Expect daily sync failure, which also indicates exponential retries.
EXPECT_FALSE(daily_sync_scheduler_->handled_results().front());
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Upload.Result", /*bucket: success=*/false,
1);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Upload.FailureReason",
/*bucket: NearbyHttpResult::kHttpErrorInternalServerError*/
ash::nearby::NearbyHttpResult::kHttpErrorInternalServerError, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.AttemptsNeededCount", 0);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.ServerRequestDuration", 0);
}
TEST_F(NearbyPresenceCredentialManagerImplTest,
DailySyncFailure_DownloadCredentialFailure) {
base::RunLoop get_local_creds_run_loop;
fake_local_device_data_provider_->SetHaveSharedCredentialsChangedCallback(
get_local_creds_run_loop.QuitClosure());
TriggerDailySync(/*have_credentials_changed=*/false,
/*get_local_shared_credentials_success=*/true);
get_local_creds_run_loop.Run();
// Simulate failure to download credentials from the server.
const raw_ptr<FakeNearbyScheduler, DanglingUntriaged> upload_scheduler =
scheduler_factory_.pref_name_to_on_demand_instance()
.find(prefs::kNearbyPresenceSchedulingDownloadPrefName)
->second.fake_scheduler;
upload_scheduler->SetNumConsecutiveFailures(kServerCommunicationMaxAttempts);
upload_scheduler->InvokeRequestCallback();
server_client_factory_.fake_server_client()
->InvokeListSharedCredentialsErrorCallback(
ash::nearby::NearbyHttpError::kInternalServerError);
EXPECT_FALSE(daily_sync_scheduler_->handled_results().front());
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.Result", /*bucket: success=*/false,
1);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.FailureReason",
/*bucket: NearbyHttpResult::kHttpErrorInternalServerError*/
ash::nearby::NearbyHttpResult::kHttpErrorInternalServerError, 1);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Download.AttemptsNeededCount", 0);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Download.ServerRequestDuration", 0);
}
TEST_F(NearbyPresenceCredentialManagerImplTest,
DailySyncFailure_SaveRemoteCredentialFailure) {
base::RunLoop get_local_creds_run_loop;
fake_local_device_data_provider_->SetHaveSharedCredentialsChangedCallback(
get_local_creds_run_loop.QuitClosure());
TriggerDailySync(/*have_credentials_changed=*/false,
/*get_local_shared_credentials_success=*/true);
// Required to send messages across mojo pipe for saving remote device
// credentials.
base::RunLoop save_remote_creds_run_loop;
daily_sync_scheduler_->SetHandleResultCallback(
save_remote_creds_run_loop.QuitClosure());
get_local_creds_run_loop.Run();
// Simulate the remote device credentials being unsuccessfully set in the
// NP library.
fake_nearby_presence_.SetUpdateRemoteCredentialsStatus(
mojo_base::mojom::AbslStatusCode::kUnknown);
// Simulate a successful download of credentials from the server.
TriggerDownloadRemoteCredentialSuccess();
save_remote_creds_run_loop.Run();
EXPECT_FALSE(daily_sync_scheduler_->handled_results().front());
}
TEST_F(NearbyPresenceCredentialManagerImplTest,
DailySyncSuccess_TriggeredByUpdateCredentials) {
SetUpDailySync(/*have_credentials_changed=*/false,
/*get_local_shared_credentials_success=*/true);
UpdateCredentialsDailySync();
EXPECT_TRUE(daily_sync_scheduler_->handled_results().front());
size_t expected_num_requests = 1;
for (int i = 1; i <= kMaxUpdateCredentialRequestCount; ++i) {
EXPECT_EQ(expected_num_requests,
daily_sync_scheduler_->num_immediate_requests());
// Once the cooloff period has passed, expect the number of requests to
// increase since the call to `UpdateCredentials()` is not ignored.
expected_num_requests++;
task_environment_.FastForwardBy(kUpdateCredentialCoolDownPeriods[i]);
UpdateCredentialsDailySync();
EXPECT_EQ(expected_num_requests,
daily_sync_scheduler_->num_immediate_requests());
EXPECT_TRUE(daily_sync_scheduler_->handled_results().front());
}
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.Result", 0);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.FailureReason", 0);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.AttemptsNeededCount", 0);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Upload.ServerRequestDuration", 0);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.Result", /*bucket: success=*/true,
7);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Download.FailureReason", 0);
histogram_tester_.ExpectBucketCount(
"Nearby.Presence.Credentials.Download.AttemptsNeededCount",
/*bucket: attempt_count=*/1, 7);
histogram_tester_.ExpectTotalCount(
"Nearby.Presence.Credentials.Download.ServerRequestDuration", 7);
}
} // namespace ash::nearby::presence