From bccdf6543e10ab78e291e6fa4867a126f06ff499 Mon Sep 17 00:00:00 2001 From: Qin Wang Date: Tue, 25 Jul 2023 17:32:02 -0700 Subject: [PATCH] Checks if a device is already saved before writing accountkey PiperOrigin-RevId: 551045458 --- .../pairing/fastpair/fast_pair_pairer_impl.cc | 25 ++++++- .../pairing/fastpair/fast_pair_pairer_impl.h | 1 + .../fastpair/fast_pair_pairer_impl_test.cc | 46 ++++++++++++ fastpair/pairing/pairer_broker_impl_test.cc | 41 ++++++++++ .../repository/fake_fast_pair_repository.cc | 16 +++- .../repository/fake_fast_pair_repository.h | 15 ++-- fastpair/repository/fast_pair_repository.h | 14 ++-- .../repository/fast_pair_repository_impl.cc | 51 ++++++++++++- .../repository/fast_pair_repository_impl.h | 12 +-- .../fast_pair_repository_impl_test.cc | 75 +++++++++++++++++++ 10 files changed, 276 insertions(+), 20 deletions(-) diff --git a/fastpair/pairing/fastpair/fast_pair_pairer_impl.cc b/fastpair/pairing/fastpair/fast_pair_pairer_impl.cc index 5283de18..e2c21138 100644 --- a/fastpair/pairing/fastpair/fast_pair_pairer_impl.cc +++ b/fastpair/pairing/fastpair/fast_pair_pairer_impl.cc @@ -289,7 +289,30 @@ void FastPairPairerImpl::AttemptSendAccountKey() { NotifyPairingCompleted(); return; } - // TODO(b/281782018) : Handle BLE address rotation + + // It's possible that the user has opted to initial pair to a device that + // already has an account key saved. We check to see if this is the case + // before writing a new account key. + if (device_.GetProtocol() == Protocol::kFastPairInitialPairing) { + FastPairRepository::Get()->IsDeviceSavedToAccount( + device_.GetPublicAddress().value(), [this](absl::Status status) { + if (status.ok()) { + NEARBY_LOGS(VERBOSE) + << __func__ + << ": Device is already saved, skipping write account key. " + "Pairing procedure complete."; + NotifyPairingCompleted(); + return; + } + WriteAccountKey(); + }); + } else { + // TODO(b/281782018) : Handle BLE address rotation + WriteAccountKey(); + } +} + +void FastPairPairerImpl::WriteAccountKey() { fast_pair_gatt_service_client_->WriteAccountKey( *fast_pair_handshake_->fast_pair_data_encryptor(), [&](const std::optional account_key, diff --git a/fastpair/pairing/fastpair/fast_pair_pairer_impl.h b/fastpair/pairing/fastpair/fast_pair_pairer_impl.h index c7d3e55c..9024e636 100644 --- a/fastpair/pairing/fastpair/fast_pair_pairer_impl.h +++ b/fastpair/pairing/fastpair/fast_pair_pairer_impl.h @@ -88,6 +88,7 @@ class FastPairPairerImpl : public FastPairPairer { // Attempts to write account key to remote device void AttemptSendAccountKey() ABSL_EXCLUSIVE_LOCKS_REQUIRED(*executor_); + void WriteAccountKey(); // FastPairDataEncryptor::WriteAccountKey callback void OnWriteAccountKey(std::optional account_key, std::optional failure); diff --git a/fastpair/pairing/fastpair/fast_pair_pairer_impl_test.cc b/fastpair/pairing/fastpair/fast_pair_pairer_impl_test.cc index ccc740b9..d2eac0e5 100644 --- a/fastpair/pairing/fastpair/fast_pair_pairer_impl_test.cc +++ b/fastpair/pairing/fastpair/fast_pair_pairer_impl_test.cc @@ -402,6 +402,8 @@ TEST_F(FastPairPairerImplTest, SuccessInitialPairingWithDeviceVersionHigherThanV1) { LogInAccount(); auto repository = std::make_unique(); + repository->SetResultOfIsDeviceSavedToAccount( + absl::NotFoundError("not found")); repository->SetResultOfWriteAccountAssociationToFootprints(absl::OkStatus()); ConfigurePairingContext(); SetPairingResult(std::nullopt); @@ -786,9 +788,51 @@ TEST_F(FastPairPairerImplTest, SkipWriteAccountKeyBecauseNoLoggedInUser) { EXPECT_FALSE(device_->GetAccountKey().Ok()); } +TEST_F(FastPairPairerImplTest, + SkipWriteAccountKeyBecauseDeviceAlreadySavedToAccount) { + LogInAccount(); + auto repository = std::make_unique(); + repository->SetResultOfIsDeviceSavedToAccount(absl::OkStatus()); + ConfigurePairingContext(); + SetPairingResult(std::nullopt); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairInitialPairing); + SetupProviderGattServer(); + SetNotifyResponse(*key_based_characteristic_, kKeyBasedResponse); + SetNotifyResponse(*passkey_characteristic_, kPasskeyResponse); + SetDecryptedResponse(); + SetDecryptedPasskey(); + CreateFastPairHandshakeInstanceForDevice(); + + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, account_manager_.get(), + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + FAIL() << "Unexpected pairing failure " << failure; + }, + [&](FastPairDevice& device, PairFailure failure) { + FAIL() << "Unexpected pairing failure " << failure; + }, + [&](FastPairDevice& device) { + EXPECT_FALSE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + paired_latch.Await(); + complete_latch.Await(); + EXPECT_TRUE(fast_pair_pairer_->IsPaired()); + EXPECT_FALSE(device_->GetAccountKey().Ok()); +} + TEST_F(FastPairPairerImplTest, SuccessPairingWithDeviceButFailedToWriteAccountkeyToRemoteDevice) { LogInAccount(); + auto repository = std::make_unique(); + repository->SetResultOfIsDeviceSavedToAccount( + absl::NotFoundError("not found")); ConfigurePairingContext(); SetPairingResult(std::nullopt); CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, @@ -835,6 +879,8 @@ TEST_F(FastPairPairerImplTest, SuccessPairingWithDeviceButFailedToWriteAccountkeyToFootprints) { LogInAccount(); auto repository = std::make_unique(); + repository->SetResultOfIsDeviceSavedToAccount( + absl::NotFoundError("not found")); repository->SetResultOfWriteAccountAssociationToFootprints( absl::InternalError("Failed to write account key to foot prints")); ConfigurePairingContext(); diff --git a/fastpair/pairing/pairer_broker_impl_test.cc b/fastpair/pairing/pairer_broker_impl_test.cc index e8688d97..d1d98648 100644 --- a/fastpair/pairing/pairer_broker_impl_test.cc +++ b/fastpair/pairing/pairer_broker_impl_test.cc @@ -457,6 +457,8 @@ TEST_F(PairerBrokerImplTest, SuccessInitialPairingWithDeviceV1) { TEST_F(PairerBrokerImplTest, SuccessInitialPairingWithDevice) { LogInAccount(); auto repository = std::make_unique(); + repository->SetResultOfIsDeviceSavedToAccount( + absl::NotFoundError("not found")); repository->SetResultOfWriteAccountAssociationToFootprints(absl::OkStatus()); ConfigurePairingContext(); SetPairingResult(std::nullopt); @@ -616,8 +618,45 @@ TEST_F(PairerBrokerImplTest, SkipWriteAccountKeyBecauseNoLoggedInUser) { EXPECT_FALSE(device_->GetAccountKey().Ok()); } +TEST_F(PairerBrokerImplTest, + SkipWriteAccountKeyBecauseDeviceAlreadySavedToAccount) { + LogInAccount(); + auto repository = std::make_unique(); + repository->SetResultOfIsDeviceSavedToAccount(absl::OkStatus()); + ConfigurePairingContext(); + SetPairingResult(std::nullopt); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairInitialPairing); + SetupProviderGattServer(); + SetNotifyResponse(*key_based_characteristic_, kKeyBasedResponse); + SetNotifyResponse(*passkey_characteristic_, kPasskeyResponse); + SetDecryptedResponse(); + SetDecryptedPasskey(); + CreateFastPairHandshakeInstanceForDevice(); + + CountDownLatch device_paired_latch(1); + CountDownLatch account_key_writed_latch(1); + CountDownLatch pairing_completed_latch(1); + CountDownLatch pairing_failure_latch(1); + + pairer_broker_ = std::make_unique(*mediums_, &executor_, + account_manager_.get()); + PairerBrokerObserver pairer_broker_observer( + pairer_broker_.get(), &device_paired_latch, &account_key_writed_latch, + &pairing_completed_latch, &pairing_failure_latch); + pairer_broker_->PairDevice(*device_); + + device_paired_latch.Await(); + pairing_completed_latch.Await(); + EXPECT_FALSE(account_key_writed_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(pairing_failure_latch.Await(kWaitTimeout).result()); +} + TEST_F(PairerBrokerImplTest, FaileToWriteAccountkeyToRemoteDevice) { LogInAccount(); + auto repository = std::make_unique(); + repository->SetResultOfIsDeviceSavedToAccount( + absl::NotFoundError("not found")); ConfigurePairingContext(); SetPairingResult(std::nullopt); CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, @@ -656,6 +695,8 @@ TEST_F(PairerBrokerImplTest, FaileToWriteAccountkeyToRemoteDevice) { TEST_F(PairerBrokerImplTest, FaileToWriteAccountkeyToFootprints) { LogInAccount(); auto repository = std::make_unique(); + repository->SetResultOfIsDeviceSavedToAccount( + absl::NotFoundError("not found")); repository->SetResultOfWriteAccountAssociationToFootprints( absl::InternalError("Failed to write account key to foot prints")); ConfigurePairingContext(); diff --git a/fastpair/repository/fake_fast_pair_repository.cc b/fastpair/repository/fake_fast_pair_repository.cc index 31da9be8..4c0491d7 100644 --- a/fastpair/repository/fake_fast_pair_repository.cc +++ b/fastpair/repository/fake_fast_pair_repository.cc @@ -56,6 +56,11 @@ void FakeFastPairRepository::SetResultOfDeleteAssociatedDeviceByAccountKey( deleted_associated_device_ = status; } +void FakeFastPairRepository::SetResultOfIsDeviceSavedToAccount( + absl::Status status) { + is_device_saved_to_account_ = status; +} + void FakeFastPairRepository::GetDeviceMetadata( absl::string_view hex_model_id, DeviceMetadataCallback callback) { executor_.Execute([this, callback = std::move(callback), @@ -69,14 +74,14 @@ void FakeFastPairRepository::GetDeviceMetadata( } void FakeFastPairRepository::WriteAccountAssociationToFootprints( - FastPairDevice& device, OperationToFootprintsCallback callback) { + FastPairDevice& device, OperationCallback callback) { executor_.Execute([callback = std::move(callback), this]() mutable { callback(write_account_association_to_footprints_); }); } void FakeFastPairRepository::DeleteAssociatedDeviceByAccountKey( - const AccountKey& account_key, OperationToFootprintsCallback callback) { + const AccountKey& account_key, OperationCallback callback) { executor_.Execute([callback = std::move(callback), this]() mutable { callback(deleted_associated_device_); }); @@ -89,6 +94,13 @@ void FakeFastPairRepository::CheckIfAssociatedWithCurrentAccount( }); } +void FakeFastPairRepository::IsDeviceSavedToAccount( + absl::string_view mac_address, OperationCallback callback) { + executor_.Execute([callback = std::move(callback), this]() mutable { + callback(is_device_saved_to_account_); + }); +} + std::unique_ptr FakeFastPairRepository::Create( absl::string_view model_id, absl::string_view public_anti_spoof_key) { proto::Device metadata; diff --git a/fastpair/repository/fake_fast_pair_repository.h b/fastpair/repository/fake_fast_pair_repository.h index a1ca8428..5afb3d24 100644 --- a/fastpair/repository/fake_fast_pair_repository.h +++ b/fastpair/repository/fake_fast_pair_repository.h @@ -46,6 +46,7 @@ class FakeFastPairRepository : public FastPairRepository { void SetResultOfCheckIfAssociatedWithCurrentAccount( std::optional account_key, std::optional model_id); + void SetResultOfIsDeviceSavedToAccount(absl::Status status); // FastPairRepository:: void AddObserver(Observer* observer) override{}; @@ -56,17 +57,19 @@ class FakeFastPairRepository : public FastPairRepository { void GetUserSavedDevices() override{}; - void WriteAccountAssociationToFootprints( - FastPairDevice& device, OperationToFootprintsCallback callback) override; + void WriteAccountAssociationToFootprints(FastPairDevice& device, + OperationCallback callback) override; - void DeleteAssociatedDeviceByAccountKey( - const AccountKey& account_key, - OperationToFootprintsCallback callback) override; + void DeleteAssociatedDeviceByAccountKey(const AccountKey& account_key, + OperationCallback callback) override; void CheckIfAssociatedWithCurrentAccount( AccountKeyFilter& account_key_filter, CheckAccountKeysCallback callback) override; + void IsDeviceSavedToAccount(absl::string_view mac_address, + OperationCallback callback) override; + private: absl::flat_hash_map> data_; @@ -77,6 +80,8 @@ class FakeFastPairRepository : public FastPairRepository { absl::Status write_account_association_to_footprints_; // Results of DeleteAssociatedDeviceByAccountKey absl::Status deleted_associated_device_; + // Results of IsDeviceSavedToAccount + absl::Status is_device_saved_to_account_; SingleThreadExecutor executor_; }; } // namespace fastpair diff --git a/fastpair/repository/fast_pair_repository.h b/fastpair/repository/fast_pair_repository.h index 7134c0fe..1f1ea902 100644 --- a/fastpair/repository/fast_pair_repository.h +++ b/fastpair/repository/fast_pair_repository.h @@ -36,8 +36,7 @@ using DeviceMetadataCallback = using CheckAccountKeysCallback = absl::AnyInvocable account_key, std::optional model_id)>; -using OperationToFootprintsCallback = - absl::AnyInvocable; +using OperationCallback = absl::AnyInvocable; class FastPairRepository { public: @@ -72,12 +71,11 @@ class FastPairRepository { // Stores the given |account_key| for a |device| on the Footprints server. virtual void WriteAccountAssociationToFootprints( - FastPairDevice& device, OperationToFootprintsCallback callback) = 0; + FastPairDevice& device, OperationCallback callback) = 0; // Deletes the associated data for a given |account_key|. virtual void DeleteAssociatedDeviceByAccountKey( - const AccountKey& account_key, - OperationToFootprintsCallback callback) = 0; + const AccountKey& account_key, OperationCallback callback) = 0; // Checks all account keys associated with current user's account against the // given filter. If a match is found, return the account key. @@ -85,6 +83,12 @@ class FastPairRepository { AccountKeyFilter& account_key_filter, CheckAccountKeysCallback callback) = 0; + // Checks if a device with an address |mac_address| is already saved to + // the user's account by cross referencing the |mac_address| with any + // associated account keys. + virtual void IsDeviceSavedToAccount(absl::string_view mac_address, + OperationCallback callback) = 0; + protected: static void SetInstance(FastPairRepository* instance); }; diff --git a/fastpair/repository/fast_pair_repository_impl.cc b/fastpair/repository/fast_pair_repository_impl.cc index 2f51044b..8dea41cd 100644 --- a/fastpair/repository/fast_pair_repository_impl.cc +++ b/fastpair/repository/fast_pair_repository_impl.cc @@ -56,6 +56,20 @@ bool DoesDeviceHaveForgetPattern(const proto::FastPairDevice& device) { return (device.sha256_account_key_public_address().compare( 0, kForgetPattern.length(), kForgetPattern) == 0); } + +// Checks if the mac address of a FastPairDevice is the same as the given +// |mac_address| by checking if the SHA256 from the given |device| equals to +// SHA256(concat(account_key of |device|, |mac_address|)). +bool IsDeviceSha256Matched(const proto::FastPairDevice& device, + absl::string_view mac_address) { + if (DoesDeviceHaveForgetPattern(device)) { + return false; + } + + return device.sha256_account_key_public_address() == + FastPairRepository::GenerateSha256OfAccountKeyAndMacAddress( + AccountKey(device.account_key()), mac_address); +} } // namespace FastPairRepositoryImpl::FastPairRepositoryImpl(FastPairClient* fast_pair_client) @@ -98,7 +112,7 @@ void FastPairRepositoryImpl::GetDeviceMetadata( } void FastPairRepositoryImpl::WriteAccountAssociationToFootprints( - FastPairDevice& device, OperationToFootprintsCallback callback) { + FastPairDevice& device, OperationCallback callback) { proto::UserWriteDeviceRequest request; auto* fast_pair_info = request.mutable_fast_pair_info(); BuildFastPairInfo(fast_pair_info, device); @@ -124,7 +138,7 @@ void FastPairRepositoryImpl::WriteAccountAssociationToFootprints( } void FastPairRepositoryImpl::DeleteAssociatedDeviceByAccountKey( - const AccountKey& account_key, OperationToFootprintsCallback callback) { + const AccountKey& account_key, OperationCallback callback) { std::string hex_string = absl::BytesToHexString(account_key.GetAsBytes()); absl::AsciiStrToUpper(&hex_string); executor_.Execute( @@ -230,5 +244,38 @@ void FastPairRepositoryImpl::CheckIfAssociatedWithCurrentAccount( }); } +void FastPairRepositoryImpl::IsDeviceSavedToAccount( + absl::string_view mac_address, OperationCallback callback) { + executor_.Execute( + "Check is device saved to account.", + [this, mac_address = std::string(mac_address), + callback = std::move(callback)]() mutable { + NEARBY_LOGS(INFO) << __func__ + << ": Start to check is device saved to account."; + proto::UserReadDevicesRequest request; + absl::StatusOr response = + fast_pair_client_->UserReadDevices(request); + if (!response.ok()) { + NEARBY_LOGS(WARNING) + << __func__ + << "Failed to get UserDeleteDeviceResponse from backend."; + std::move(callback)(response.status()); + return; + } + for (const auto& info : response->fast_pair_info()) { + if (info.has_device() && + IsDeviceSha256Matched(info.device(), mac_address)) { + NEARBY_LOGS(VERBOSE) + << __func__ << ": found a SHA256 match for device at address = " + << mac_address; + std::move(callback)(absl::OkStatus()); + return; + } + } + std::move(callback)(absl::NotFoundError("Device " + mac_address + + " is not saved to account.")); + }); +} + } // namespace fastpair } // namespace nearby diff --git a/fastpair/repository/fast_pair_repository_impl.h b/fastpair/repository/fast_pair_repository_impl.h index 74fb4abc..6e9327c5 100644 --- a/fastpair/repository/fast_pair_repository_impl.h +++ b/fastpair/repository/fast_pair_repository_impl.h @@ -44,17 +44,19 @@ class FastPairRepositoryImpl : public FastPairRepository { void GetUserSavedDevices() override; - void WriteAccountAssociationToFootprints( - FastPairDevice& device, OperationToFootprintsCallback callback) override; + void WriteAccountAssociationToFootprints(FastPairDevice& device, + OperationCallback callback) override; - void DeleteAssociatedDeviceByAccountKey( - const AccountKey& account_key, - OperationToFootprintsCallback callback) override; + void DeleteAssociatedDeviceByAccountKey(const AccountKey& account_key, + OperationCallback callback) override; void CheckIfAssociatedWithCurrentAccount( AccountKeyFilter& account_key_filter, CheckAccountKeysCallback callback) override; + void IsDeviceSavedToAccount(absl::string_view mac_address, + OperationCallback callback) override; + private: // A thread for running blocking tasks. SingleThreadExecutor executor_; diff --git a/fastpair/repository/fast_pair_repository_impl_test.cc b/fastpair/repository/fast_pair_repository_impl_test.cc index a40f4162..a0374410 100644 --- a/fastpair/repository/fast_pair_repository_impl_test.cc +++ b/fastpair/repository/fast_pair_repository_impl_test.cc @@ -468,6 +468,81 @@ TEST(FastPairRepositoryImplTest, DeviceNotAssociatedWithCurrentAccount) { }); latch.Await(); } + +TEST(FastPairRepositoryImplTest, DeviceIsSavedToCurrentAccount) { + FakeFastPairClient fake_fast_pair_client; + auto fast_pair_repository = + std::make_unique(&fake_fast_pair_client); + + // Sets up two devices to proto::UserReadDevicesResponse. + proto::UserReadDevicesResponse response_proto; + // Device 1 + FastPairDevice device_1(kHexModelId, kBleAddress, + Protocol::kFastPairInitialPairing); + AccountKey account_key_1(absl::HexStringToBytes(kAccountKey)); + device_1.SetAccountKey(account_key_1); + device_1.SetPublicAddress(kPublicAddress); + proto::GetObservedDeviceResponse get_observed_device_response; + DeviceMetadata device_metadata_1(get_observed_device_response); + device_1.SetMetadata(device_metadata_1); + auto* fast_pair_info_1 = response_proto.add_fast_pair_info(); + BuildFastPairInfo(fast_pair_info_1, device_1); + // Device 2 + auto* fast_pair_info_2 = response_proto.add_fast_pair_info(); + fast_pair_info_2->set_opt_in_status( + proto::OptInStatus::OPT_IN_STATUS_OPTED_IN); + fake_fast_pair_client.SetUserReadDevicesResponse(response_proto); + + CountDownLatch latch(1); + fast_pair_repository->IsDeviceSavedToAccount(kPublicAddress, + [&](absl::Status status) { + EXPECT_OK(status); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairRepositoryImplTest, DeviceIsNotSavedToCurrentAccount) { + FakeFastPairClient fake_fast_pair_client; + auto fast_pair_repository = + std::make_unique(&fake_fast_pair_client); + + // Sets up two devices to proto::UserReadDevicesResponse. + proto::UserReadDevicesResponse response_proto; + FastPairDevice device(kHexModelId, kBleAddress, + Protocol::kFastPairInitialPairing); + AccountKey account_key(absl::HexStringToBytes(kAccountKey)); + device.SetAccountKey(account_key); + device.SetPublicAddress(kPublicAddress); + proto::GetObservedDeviceResponse get_observed_device_response; + DeviceMetadata device_metadata(get_observed_device_response); + device.SetMetadata(device_metadata); + auto* fast_pair_info = response_proto.add_fast_pair_info(); + BuildFastPairInfo(fast_pair_info, device); + fake_fast_pair_client.SetUserReadDevicesResponse(response_proto); + + CountDownLatch latch(1); + fast_pair_repository->IsDeviceSavedToAccount( + "11:22:33:44:55:66", [&](absl::Status status) { + EXPECT_EQ(status.code(), absl::StatusCode::kNotFound); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairRepositoryImplTest, FailedToCheckDeviceIsSavedToCurrentAccount) { + FakeFastPairClient fake_fast_pair_client; + auto fast_pair_repository = + std::make_unique(&fake_fast_pair_client); + + CountDownLatch latch(1); + fast_pair_repository->IsDeviceSavedToAccount(kPublicAddress, + [&](absl::Status status) { + EXPECT_FALSE(status.ok()); + latch.CountDown(); + }); + latch.Await(); +} } // namespace } // namespace fastpair } // namespace nearby