diff --git a/fastpair/crypto/BUILD b/fastpair/crypto/BUILD index dc9b48b6..cac861f5 100644 --- a/fastpair/crypto/BUILD +++ b/fastpair/crypto/BUILD @@ -3,6 +3,7 @@ cc_library( srcs = [ "fast_pair_decryption.cc", "fast_pair_encryption.cc", + "fast_pair_message_type.cc", ], hdrs = [ "decrypted_passkey.h", @@ -102,3 +103,18 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "fast_pair_message_type_test", + size = "small", + srcs = [ + "fast_pair_message_type_test.cc", + ], + shard_count = 1, + deps = [ + ":crypto", + "//internal/platform/implementation/g3", # build_cleaner: keep + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/fastpair/crypto/fast_pair_message_type.cc b/fastpair/crypto/fast_pair_message_type.cc new file mode 100644 index 00000000..8fd6b932 --- /dev/null +++ b/fastpair/crypto/fast_pair_message_type.cc @@ -0,0 +1,45 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "fastpair/crypto/fast_pair_message_type.h" + +#include + +namespace nearby { +namespace fastpair { + +std::ostream& operator<<(std::ostream& stream, + FastPairMessageType message_type) { + switch (message_type) { + case FastPairMessageType::kKeyBasedPairingRequest: + stream << "[Key-Based Pairing Request]"; + break; + case FastPairMessageType::kKeyBasedPairingResponse: + stream << "[Key-Based Pairing Response]"; + break; + case FastPairMessageType::kSeekersPasskey: + stream << "[Seeker's Passkey]"; + break; + case FastPairMessageType::kProvidersPasskey: + stream << "[Providers' Passkey]"; + break; + default: + stream << "[Unknown]"; + } + + return stream; +} + +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/crypto/fast_pair_message_type.h b/fastpair/crypto/fast_pair_message_type.h index 087f280a..fc0491ef 100644 --- a/fastpair/crypto/fast_pair_message_type.h +++ b/fastpair/crypto/fast_pair_message_type.h @@ -15,23 +15,28 @@ #ifndef THIRD_PARTY_NEARBY_FASTPAIR_CRYPTO_FAST_PAIR_MESSAGE_TYPE_H_ #define THIRD_PARTY_NEARBY_FASTPAIR_CRYPTO_FAST_PAIR_MESSAGE_TYPE_H_ +#include + namespace nearby { namespace fastpair { // Type values for Fast Pair messages. enum class FastPairMessageType { - // Key-based Pairing Request. - kKeyBasedPairingRequest, - // Key-based Pairing Response. - kKeyBasedPairingResponse, - // Seeker's passkey. - kSeekersPasskey, - // Provider's passkey. - kProvidersPasskey, // Unknown message type. - kUnknown, + kUnknown = 0, + // Key-based Pairing Request. + kKeyBasedPairingRequest = 1, + // Key-based Pairing Response. + kKeyBasedPairingResponse = 2, + // Seeker's passkey. + kSeekersPasskey = 3, + // Provider's passkey. + kProvidersPasskey = 4, }; +std::ostream& operator<<(std::ostream& stream, + FastPairMessageType message_type); + } // namespace fastpair } // namespace nearby diff --git a/fastpair/crypto/fast_pair_message_type_test.cc b/fastpair/crypto/fast_pair_message_type_test.cc new file mode 100644 index 00000000..ce99b8ad --- /dev/null +++ b/fastpair/crypto/fast_pair_message_type_test.cc @@ -0,0 +1,32 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "fastpair/crypto/fast_pair_message_type.h" + +#include "gtest/gtest.h" + +namespace nearby { +namespace fastpair { +namespace { + +TEST(FastPairMessageTypeTest, FastPairMessageTypeValue) { + EXPECT_EQ(static_cast(FastPairMessageType::kUnknown), 0); + EXPECT_EQ(static_cast(FastPairMessageType::kKeyBasedPairingRequest), 1); + EXPECT_EQ(static_cast(FastPairMessageType::kKeyBasedPairingResponse), 2); + EXPECT_EQ(static_cast(FastPairMessageType::kSeekersPasskey), 3); + EXPECT_EQ(static_cast(FastPairMessageType::kProvidersPasskey), 4); +} +} // namespace +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/handshake/fast_pair_handshake_lookup.cc b/fastpair/handshake/fast_pair_handshake_lookup.cc index 4b748fe3..6a65036a 100644 --- a/fastpair/handshake/fast_pair_handshake_lookup.cc +++ b/fastpair/handshake/fast_pair_handshake_lookup.cc @@ -15,18 +15,22 @@ #include "fastpair/handshake/fast_pair_handshake_lookup.h" #include +#include #include #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" #include "fastpair/handshake/fast_pair_handshake_impl.h" -#include "internal/platform/logging.h" namespace nearby { namespace fastpair { FastPairHandshakeLookup* FastPairHandshakeLookup::instance_ = nullptr; absl::Mutex FastPairHandshakeLookup::mutex_(absl::kConstInit); +namespace { +absl::optional g_test_create_function = + std::nullopt; +} // static FastPairHandshakeLookup* FastPairHandshakeLookup::GetInstance() { @@ -37,6 +41,12 @@ FastPairHandshakeLookup* FastPairHandshakeLookup::GetInstance() { return instance_; } +// static Create function override which can be set by tests. +void FastPairHandshakeLookup::SetCreateFunctionForTesting( + CreateFunction create_function) { + g_test_create_function = std::move(create_function); +} + FastPairHandshake* FastPairHandshakeLookup::Get(FastPairDevice* device) { absl::MutexLock lock(&mutex_); auto it = fast_pair_handshakes_.find(device); @@ -80,8 +90,11 @@ FastPairHandshake* FastPairHandshakeLookup::Create( FastPairDevice& device, Mediums& mediums, OnCompleteCallback on_complete) { absl::MutexLock lock(&mutex_); auto it = fast_pair_handshakes_.emplace( - &device, std::make_unique(device, mediums, - std::move(on_complete))); + &device, g_test_create_function.has_value() + ? g_test_create_function.value()(device, mediums, + std::move(on_complete)) + : std::make_unique( + device, mediums, std::move(on_complete))); DCHECK(it.second); return it.first->second.get(); } diff --git a/fastpair/handshake/fast_pair_handshake_lookup.h b/fastpair/handshake/fast_pair_handshake_lookup.h index 2b459077..a75e7fad 100644 --- a/fastpair/handshake/fast_pair_handshake_lookup.h +++ b/fastpair/handshake/fast_pair_handshake_lookup.h @@ -15,6 +15,7 @@ #ifndef THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_HANDSHAKE_LOOKUP_H_ #define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_HANDSHAKE_LOOKUP_H_ +#include #include #include @@ -35,12 +36,17 @@ class FastPairHandshakeLookup { using OnCompleteCallback = absl::AnyInvocable failure)>; + using CreateFunction = absl::AnyInvocable( + FastPairDevice& device, Mediums& mediums, OnCompleteCallback callback)>; + // This is the static method that controls the access to the singleton // instance. On the first run, it creates a singleton object and places it // into the static field. On subsequent runs, it returns the existing object // stored in the static field. static FastPairHandshakeLookup* GetInstance(); + static void SetCreateFunctionForTesting(CreateFunction create_function); + // Singletons should not be cloneable. FastPairHandshakeLookup(const FastPairHandshakeLookup&) = delete; // Singletons should not be assignable. diff --git a/fastpair/pairing/fastpair/BUILD b/fastpair/pairing/fastpair/BUILD new file mode 100644 index 00000000..4434ba89 --- /dev/null +++ b/fastpair/pairing/fastpair/BUILD @@ -0,0 +1,68 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +licenses(["notice"]) + +cc_library( + name = "pairing", + srcs = [ + "fast_pair_pairer_impl.cc", + ], + hdrs = [ + "fast_pair_pairer.h", + "fast_pair_pairer_impl.h", + ], + compatible_with = ["//buildenv/target:non_prod"], + visibility = [ + "//fastpair:__subpackages__", + "//internal:__subpackages__", + ], + deps = [ + "//fastpair/common", + "//fastpair/crypto", + "//fastpair/handshake", + "//fastpair/internal/mediums", + "//internal/platform:comm", + "//internal/platform:types", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/time", + ], +) + +cc_test( + name = "fast_pair_pairer_impl_test", + size = "small", + srcs = [ + "fast_pair_pairer_impl_test.cc", + ], + shard_count = 16, + deps = [ + ":pairing", + "//fastpair/common", + "//fastpair/handshake", + "//fastpair/server_access:test_support", + "//internal/base:bluetooth_address", + "//internal/platform:comm", + "//internal/platform:test_util", + "//internal/platform:types", + "//internal/platform/implementation/g3", # build_cleaner: keep + "@boringssl//:crypto", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/functional:bind_front", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/fastpair/pairing/fastpair/fast_pair_pairer.h b/fastpair/pairing/fastpair/fast_pair_pairer.h new file mode 100644 index 00000000..bd5de0a3 --- /dev/null +++ b/fastpair/pairing/fastpair/fast_pair_pairer.h @@ -0,0 +1,51 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_NEARBY_FASTPAIR_PAIRING_FASTPAIR_FAST_PAIR_PAIRER_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_PAIRING_FASTPAIR_FAST_PAIR_PAIRER_H_ + +#include "absl/functional/any_invocable.h" +#include "fastpair/common/fast_pair_device.h" +#include "fastpair/common/pair_failure.h" +namespace nearby { +namespace fastpair { + +// A FastPairPairer instance is responsible for the pairing procedure to a +// single device. Pairing begins on instantiation. +class FastPairPairer { + public: + // Triggered when paired with the remote device. + using OnPairedCallback = absl::AnyInvocable; + // Triggered when failed to pair with the remote device. + using OnPairingFailedCallback = + absl::AnyInvocable; + // Triggered when completed the whole pairing process, + // including pairing with the remote device and writing the accountkey to it. + using OnPairingCompletedCallback = + absl::AnyInvocable; + // Triggered when failed to write accountkey to the remote device. + using OnAccountKeyFailureCallback = + absl::AnyInvocable; + + virtual ~FastPairPairer() = default; + + virtual void StartPairing() = 0; + virtual bool IsPaired() = 0; + virtual bool CancelPairing() = 0; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_PAIRING_FASTPAIR_FAST_PAIR_PAIRER_H_ diff --git a/fastpair/pairing/fastpair/fast_pair_pairer_impl.cc b/fastpair/pairing/fastpair/fast_pair_pairer_impl.cc new file mode 100644 index 00000000..d7068d4e --- /dev/null +++ b/fastpair/pairing/fastpair/fast_pair_pairer_impl.cc @@ -0,0 +1,350 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "fastpair/pairing/fastpair/fast_pair_pairer_impl.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/time/time.h" +#include "fastpair/common/fast_pair_device.h" +#include "fastpair/common/pair_failure.h" +#include "fastpair/crypto/fast_pair_message_type.h" +#include "fastpair/handshake/fast_pair_handshake_lookup.h" +#include "fastpair/internal/mediums/mediums.h" +#include "internal/platform/bluetooth_classic.h" +#include "internal/platform/single_thread_executor.h" + +namespace nearby { +namespace fastpair { +namespace { +constexpr absl::Duration kInitiatePairingTimeout = absl::Seconds(20); +} // namespace + +// static +FastPairPairerImpl::Factory* FastPairPairerImpl::Factory::g_test_factory_ = + nullptr; + +// static +std::unique_ptr FastPairPairerImpl::Factory::Create( + FastPairDevice& device, Mediums& medium, SingleThreadExecutor* executor, + OnPairedCallback on_paired_cb, OnPairingFailedCallback on_pair_failed_cb, + OnAccountKeyFailureCallback on_account_failure_cb, + OnPairingCompletedCallback on_pairing_completed_cb) { + if (g_test_factory_) { + return g_test_factory_->CreateInstance( + device, medium, executor, std::move(on_paired_cb), + std::move(on_pair_failed_cb), std::move(on_account_failure_cb), + std::move(on_pairing_completed_cb)); + } + return std::make_unique( + device, medium, executor, std::move(on_paired_cb), + std::move(on_pair_failed_cb), std::move(on_account_failure_cb), + std::move(on_pairing_completed_cb)); +} + +// static +void FastPairPairerImpl::Factory::SetFactoryForTesting( + Factory* g_test_factory) { + g_test_factory_ = g_test_factory; +} + +FastPairPairerImpl::FastPairPairerImpl( + FastPairDevice& device, Mediums& medium, SingleThreadExecutor* executor, + OnPairedCallback on_paired_cb, OnPairingFailedCallback on_pair_failed_cb, + OnAccountKeyFailureCallback on_account_failure_cb, + OnPairingCompletedCallback on_pairing_completed_cb) + : device_(device), + mediums_(medium), + executor_(executor), + on_paired_cb_(std::move(on_paired_cb)), + on_pair_failed_cb_(std::move(on_pair_failed_cb)), + on_account_key_failure_cb_(std::move(on_account_failure_cb)), + on_pairing_completed_cb_(std::move(on_pairing_completed_cb)) { + if (device_.GetVersion().value() == DeviceFastPairVersion::kHigherThanV1) { + // Obtains the established GATT connection for use in the pairing process: + // confirm the passkey, write the account key, etc. + fast_pair_handshake_ = + FastPairHandshakeLookup::GetInstance()->Get(&device_); + CHECK(fast_pair_handshake_); + CHECK(fast_pair_handshake_->completed_successfully()); + fast_pair_gatt_service_client_ = + fast_pair_handshake_->fast_pair_gatt_service_client(); + } +} + +void FastPairPairerImpl::StartPairing() { + executor_->Execute( + "Start Pairing", [&]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(*executor_) { + NEARBY_LOGS(INFO) << __func__ << device_; + switch (device_.GetProtocol()) { + case Protocol::kFastPairInitialPairing: + case Protocol::kFastPairSubsequentPairing: + // This timer captures a pairing timeout. + initiate_pairing_timer_.Start( + kInitiatePairingTimeout / absl::Milliseconds(1), 0, [&]() { + NEARBY_LOGS(WARNING) + << __func__ + << ": Timeout while attempting to initiate " + "pairing with device."; + NotifyPairingFailed(PairFailure::kPairingTimeout); + }); + InitiatePairing(); + break; + case Protocol::kFastPairRetroactivePairing: + // Because the devices are already paired, we will directly write an + // account key to the Provider after a shared secret is established. + AttemptSendAccountKey(); + break; + } + }); +} + +// Blocking functions +void FastPairPairerImpl::InitiatePairing() { + NEARBY_LOGS(INFO) << __func__; + // TODO(b/278810942) : Check if device lost first + if (mediums_.GetBluetoothRadio().Enable() && + mediums_.GetBluetoothClassic().IsAvailable()) { + bluetooth_pairing_ = mediums_.GetBluetoothClassic().CreatePairing( + device_.GetPublicAddress().value()); + } + if (!bluetooth_pairing_) { + NotifyPairingFailed(PairFailure::kPairingAndConnect); + return; + } + // Unpair with the remote device before initializing a new pairing request + if (!bluetooth_pairing_->Unpair()) { + NotifyPairingFailed(PairFailure::kPairingAndConnect); + return; + } + if (!bluetooth_pairing_->InitiatePairing({ + .on_paired_cb = + [&]() { + if (!initiate_pairing_timer_.IsRunning()) { + NEARBY_LOGS(INFO) + << __func__ << " Initiating pairing has timed out."; + return; + } + initiate_pairing_timer_.Stop(); + NEARBY_LOGS(INFO) << __func__ << " Paired with " << device_; + // On Windows, Pair is exactly the same as Connect. + NotifyPaired(); + executor_->Execute( + "Write Accountkey", + [&]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(*executor_) { + AttemptSendAccountKey(); + }); + }, + .on_pairing_error_cb = + [&](api::BluetoothPairingCallback::PairingError error) { + NEARBY_LOGS(INFO) + << __func__ << "Failed to pair with device due to error " + << static_cast(error); + NotifyPairingFailed(PairFailure::kPairingAndConnect); + }, + .on_pairing_initiated_cb = + [&](api::PairingParams pairingParams) { + if (!initiate_pairing_timer_.IsRunning()) { + NEARBY_LOGS(INFO) + << __func__ << " Initiating pairing has timed out."; + return; + } + NEARBY_LOGS(INFO) << __func__ << "Initiated pairing request."; + if (device_.GetVersion().value() == + DeviceFastPairVersion::kV1) { + NEARBY_LOGS(INFO) + << __func__ + << ": For v1 headset, skip passkey confirmation."; + bluetooth_pairing_->FinishPairing(std::nullopt); + } else { + NEARBY_LOGS(INFO) + << __func__ << ": For headsets higher than v1, " + << "confirm passkey before accepting pairing."; + ConfirmPasskey(pairingParams); + } + }, + })) { + NotifyPairingFailed(PairFailure::kPairingAndConnect); + } +} + +void FastPairPairerImpl::ConfirmPasskey(api::PairingParams pairingParams) { + if (!FastPairHandshakeLookup::GetInstance()->Get(&device_)) { + NEARBY_LOGS(ERROR) << __func__ + << ": BLE device instance lost during passkey exchange"; + bluetooth_pairing_->CancelPairing(); + NotifyPairingFailed(PairFailure::kDeviceLostMidPairing); + return; + } + expected_passkey_ = pairingParams.passkey; + NEARBY_LOGS(INFO) << __func__ + << " Star to confirm passkey: " << expected_passkey_; + fast_pair_gatt_service_client_->WritePasskeyAsync( + /*message_type=*/0x02, std::stoi(expected_passkey_), + *fast_pair_handshake_->fast_pair_data_encryptor(), + [&](absl::string_view response, + std::optional failure) { + OnPasskeyResponse(response, failure); + }); +} + +void FastPairPairerImpl::OnPasskeyResponse(absl::string_view response, + std::optional failure) { + NEARBY_LOGS(INFO) << __func__; + if (failure.has_value()) { + NotifyPairingFailed(failure.value()); + return; + } + std::vector response_bytes(response.begin(), response.end()); + fast_pair_handshake_->fast_pair_data_encryptor()->ParseDecryptPasskey( + response_bytes, [&](const std::optional passkey) { + OnParseDecryptedPasskey(passkey); + }); +} + +void FastPairPairerImpl::OnParseDecryptedPasskey( + std::optional passkey) { + if (!passkey.has_value()) { + NotifyPairingFailed(PairFailure::kPasskeyDecryptFailure); + return; + } + if (passkey->message_type != FastPairMessageType::kProvidersPasskey) { + NEARBY_LOGS(WARNING) + << "Incorrect message type from decrypted passkey. Expected: " + << FastPairMessageType::kProvidersPasskey + << ". Actual: " << passkey->message_type; + NotifyPairingFailed(PairFailure::kIncorrectPasskeyResponseType); + return; + } + + if (passkey->passkey != std::stoi(expected_passkey_)) { + NEARBY_LOGS(ERROR) << "Passkeys do not match. " + << "Expected: " << expected_passkey_ + << ". Actual: " << std::to_string(passkey->passkey); + NotifyPairingFailed(PairFailure::kPasskeyMismatch); + return; + } + // TODO(b/278810942) : Check if device lost + NEARBY_LOGS(INFO) << __func__ << ": Passkeys match, confirming pairing"; + bluetooth_pairing_->FinishPairing(std::nullopt); +} + +bool FastPairPairerImpl::CancelPairing() { + if (!bluetooth_pairing_) { + NEARBY_LOGS(WARNING) << __func__ << ": No on-going pairing process."; + return false; + } + return bluetooth_pairing_->CancelPairing(); +} + +bool FastPairPairerImpl::IsPaired() { + if (!bluetooth_pairing_) { + return false; + } + return bluetooth_pairing_->IsPaired(); +} + +void FastPairPairerImpl::AttemptSendAccountKey() { + if (device_.GetVersion().value() == DeviceFastPairVersion::kV1) { + NotifyPairingCompleted(); + return; + } + + // We only send the account key if we're doing an initial or retroactive + // pairing. For subsequent pairing, we have to save the account key + // locally so that we can refer to it in API calls to the server. + if (device_.GetProtocol() == Protocol::kFastPairSubsequentPairing) { + NEARBY_LOGS(VERBOSE) << __func__ + << ": Saving Account Key locally for subsequent pair"; + // TODO(b/278807993): Saving Account Key locally for subsequent pair + NotifyPairingCompleted(); + return; + } + + // TODO(b/281781730) : Check if we need to send account key + // TODO(b/281782018) : Handle BLE address rotation + fast_pair_gatt_service_client_->WriteAccountKey( + *fast_pair_handshake_->fast_pair_data_encryptor(), + [&](const std::optional account_key, + const std::optional failure) { + OnWriteAccountKey(account_key, failure); + }); +} + +void FastPairPairerImpl::OnWriteAccountKey( + std::optional account_key, std::optional failure) { + if (failure.has_value()) { + NEARBY_LOGS(WARNING) + << __func__ << "Failed to write account key to device due to error: " + << failure.value(); + NotifyAccountKeyFailure(failure.value()); + return; + } + if (!account_key.has_value()) { + NotifyAccountKeyFailure(PairFailure::kAccountKeyCharacteristicWrite); + return; + } + device_.SetAccountKey(account_key.value()); + // // TODO(b/281785681): Write account association to footprints + NotifyPairingCompleted(); +} + +void FastPairPairerImpl::NotifyPaired() { + NEARBY_LOGS(INFO) << __func__ << device_; + executor_->Execute("NotifyPaired", + [&, on_paired_cb = std::move(on_paired_cb_)]() mutable { + on_paired_cb(device_); + }); +} + +void FastPairPairerImpl::NotifyPairingFailed(PairFailure failure) { + NEARBY_LOGS(WARNING) << __func__ << failure; + // Stop initiate pairing timer as piaring is terminate this time. + initiate_pairing_timer_.Stop(); + executor_->Execute("NotifyPairingFailed", + [&, on_pair_failed_cb = std::move(on_pair_failed_cb_), + failure = std::move(failure)]() mutable { + on_pair_failed_cb(device_, failure); + }); +} + +void FastPairPairerImpl::NotifyPairingCompleted() { + NEARBY_LOGS(INFO) + << __func__ + << "Account key written to device. Pairing procedure complete."; + executor_->Execute("Notify Pairing Completed", + [&, on_pairing_completed_cb = + std::move(on_pairing_completed_cb_)]() mutable { + on_pairing_completed_cb(device_); + }); +} + +void FastPairPairerImpl::NotifyAccountKeyFailure(PairFailure failure) { + NEARBY_LOGS(WARNING) << __func__ + << "Failed to write account key to device due to error: " + << failure; + executor_->Execute( + "Notify AccountKey Failure", + [&, on_account_key_failure_cb = std::move(on_account_key_failure_cb_), + failure = std::move(failure)]() mutable { + on_account_key_failure_cb(device_, failure); + }); +} +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/pairing/fastpair/fast_pair_pairer_impl.h b/fastpair/pairing/fastpair/fast_pair_pairer_impl.h new file mode 100644 index 00000000..6ea5c8fb --- /dev/null +++ b/fastpair/pairing/fastpair/fast_pair_pairer_impl.h @@ -0,0 +1,119 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_NEARBY_FASTPAIR_PAIRING_FASTPAIR_FAST_PAIR_PAIRER_IMPL_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_PAIRING_FASTPAIR_FAST_PAIR_PAIRER_IMPL_H_ + +#include +#include +#include + +#include "fastpair/common/fast_pair_device.h" +#include "fastpair/common/pair_failure.h" +#include "fastpair/crypto/decrypted_passkey.h" +#include "fastpair/handshake/fast_pair_gatt_service_client.h" +#include "fastpair/handshake/fast_pair_handshake.h" +#include "fastpair/internal/mediums/mediums.h" +#include "fastpair/pairing/fastpair/fast_pair_pairer.h" +#include "internal/platform/bluetooth_classic.h" +#include "internal/platform/single_thread_executor.h" +#include "internal/platform/timer_impl.h" + +namespace nearby { +namespace fastpair { + +class FastPairPairerImpl : public FastPairPairer { + public: + class Factory { + public: + static std::unique_ptr Create( + FastPairDevice& device, Mediums& medium, SingleThreadExecutor* executor, + OnPairedCallback on_paired_cb, + OnPairingFailedCallback on_pair_failed_cb, + OnAccountKeyFailureCallback on_account_failure_cb, + OnPairingCompletedCallback on_pairing_completed_cb); + + static void SetFactoryForTesting(Factory* test_factory); + + protected: + virtual ~Factory() = default; + + virtual std::unique_ptr CreateInstance( + FastPairDevice& device, Mediums& medium, SingleThreadExecutor* executor, + OnPairedCallback on_paired_cb, + OnPairingFailedCallback on_pair_failed_cb, + OnAccountKeyFailureCallback on_account_failure_cb, + OnPairingCompletedCallback on_pairing_completed_cb) = 0; + + private: + static Factory* g_test_factory_; + }; + + FastPairPairerImpl(FastPairDevice& device, Mediums& medium, + SingleThreadExecutor* executor, + OnPairedCallback on_paired_cb, + OnPairingFailedCallback on_pair_failed_cb, + OnAccountKeyFailureCallback on_account_failure_cb, + OnPairingCompletedCallback on_pairing_completed_cb); + FastPairPairerImpl(const FastPairPairerImpl&) = delete; + FastPairPairerImpl& operator=(const FastPairPairerImpl&) = delete; + FastPairPairerImpl(FastPairPairerImpl&&) = delete; + FastPairPairerImpl& operator=(FastPairPairerImpl&&) = delete; + bool CancelPairing() override; + bool IsPaired() override; + void StartPairing() ABSL_EXCLUSIVE_LOCKS_REQUIRED(*executor_) override; + + private: + void InitiatePairing(); + + void ConfirmPasskey(api::PairingParams pairingParams); + // FastPairGattServiceClient::WritePasskey callback + void OnPasskeyResponse(absl::string_view response, + std::optional failure); + // FastPairDataEncryptor::ParseDecryptedPasskey callback + void OnParseDecryptedPasskey(std::optional passkey); + + // Attempts to write account key to remote device + void AttemptSendAccountKey() ABSL_EXCLUSIVE_LOCKS_REQUIRED(*executor_); + // FastPairDataEncryptor::WriteAccountKey callback + void OnWriteAccountKey(std::optional account_key, + std::optional failure); + + // Notify the result of pairing and writing accoutkey. + void NotifyPaired(); + void NotifyPairingFailed(PairFailure failure); + void NotifyPairingCompleted(); + void NotifyAccountKeyFailure(PairFailure failure); + + std::string expected_passkey_; + FastPairHandshake* fast_pair_handshake_; + FastPairGattServiceClient* fast_pair_gatt_service_client_; + std::unique_ptr bluetooth_pairing_; + FastPairDevice& device_; + Mediums& mediums_; + SingleThreadExecutor* executor_; + OnPairedCallback on_paired_cb_ ABSL_EXCLUSIVE_LOCKS_REQUIRED(*executor_); + OnPairingFailedCallback on_pair_failed_cb_ + ABSL_EXCLUSIVE_LOCKS_REQUIRED(*executor_); + OnAccountKeyFailureCallback on_account_key_failure_cb_ + ABSL_EXCLUSIVE_LOCKS_REQUIRED(*executor_); + OnPairingCompletedCallback on_pairing_completed_cb_ + ABSL_EXCLUSIVE_LOCKS_REQUIRED(*executor_); + TimerImpl initiate_pairing_timer_; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_PAIRING_FASTPAIR_FAST_PAIR_PAIRER_IMPL_H_ diff --git a/fastpair/pairing/fastpair/fast_pair_pairer_impl_test.cc b/fastpair/pairing/fastpair/fast_pair_pairer_impl_test.cc new file mode 100644 index 00000000..6600e188 --- /dev/null +++ b/fastpair/pairing/fastpair/fast_pair_pairer_impl_test.cc @@ -0,0 +1,937 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "fastpair/pairing/fastpair/fast_pair_pairer_impl.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "protobuf-matchers/protocol-buffer-matchers.h" +#include "gtest/gtest.h" +#include "absl/functional/any_invocable.h" +#include "absl/functional/bind_front.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "fastpair//handshake/fast_pair_handshake_lookup.h" +#include "fastpair/common/account_key.h" +#include "fastpair/common/constant.h" +#include "fastpair/common/fast_pair_device.h" +#include "fastpair/common/protocol.h" +#include "fastpair/handshake/fast_pair_data_encryptor_impl.h" +#include "fastpair/handshake/fast_pair_handshake_impl.h" +#include "fastpair/pairing/fastpair/fast_pair_pairer.h" +#include "fastpair/server_access/fake_fast_pair_repository.h" +#include "internal/base/bluetooth_address.h" +#include "internal/platform/ble_v2.h" +#include "internal/platform/bluetooth_adapter.h" +#include "internal/platform/count_down_latch.h" +#include "internal/platform/medium_environment.h" +#include "internal/platform/single_thread_executor.h" +#include + +namespace nearby { +namespace fastpair { +namespace { +using Property = nearby::api::ble_v2::GattCharacteristic::Property; +using Permission = nearby::api::ble_v2::GattCharacteristic::Permission; +using ::nearby::api::ble_v2::GattCharacteristic; +using DiscoveryCallback = BluetoothClassicMedium::DiscoveryCallback; + +constexpr absl::string_view kMetadataId("718c17"); +constexpr absl::string_view kPublicAntiSpoof = + "Wuyr48lD3txnUhGiMF1IfzlTwRxxe+wMB1HLzP+" + "0wVcljfT3XPoiy1fntlneziyLD5knDVAJSE+RM/zlPRP/Jg=="; +constexpr std::array kRawResponseBytes = { + 0x01, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D, + 0xA0, 0xBA, 0xF0, 0xBB, 0x95, 0x1F, 0xF7, 0xB6}; +constexpr Uuid kFastPairServiceUuid(0x0000FE2C00001000, 0x800000805F9B34FB); +constexpr Uuid kKeyBasedCharacteristicUuidV2(0xFE2C123483664814, + 0x8EB001DE32100BEA); +constexpr Uuid kPasskeyCharacteristicUuidV2(0xFE2C123583664814, + 0x8EB001DE32100BEA); +constexpr Uuid kAccountKeyCharacteristicUuidV2(0xFE2C123683664814, + 0x8EB001DE32100BEA); +constexpr absl::string_view kPasskey("123456"); +constexpr absl::string_view kWrongResponse("wrongresponse"); +constexpr absl::Duration kWaitTimeout = absl::Milliseconds(200); + +struct CharacteristicData { + // Write result returned to the gatt client. + absl::Status write_result = absl::OkStatus(); +}; +} // namespace + +class FastPairPairerImplTest : public testing::Test { + public: + void SetUp() override { + env_.Start(); + // Setups seeker device. + mediums_ = std::make_unique(); + + // Setups provider device. + adapter_provider_ = std::make_unique(); + adapter_provider_->SetStatus(BluetoothAdapter::Status::kEnabled); + adapter_provider_->SetName("Device-Provider"); + adapter_provider_->SetScanMode( + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + bt_provider_ = std::make_unique(*adapter_provider_); + + // Discovering provider device. + CountDownLatch found_latch(1); + mediums_->GetBluetoothClassic().GetMedium().StartDiscovery( + DiscoveryCallback{.device_discovered_cb = [&](BluetoothDevice& device) { + remote_device_ = &device; + found_latch.CountDown(); + }}); + found_latch.Await(); + env_.Sync(); + } + + void TearDown() override { + env_.Sync(false); + executor_.Shutdown(); + mediums_.reset(); + device_.reset(); + repository_.reset(); + handshake_ = nullptr; + remote_device_ = nullptr; + key_based_characteristic_ = std::nullopt; + passkey_characteristic_ = std::nullopt; + + adapter_provider_->SetStatus(BluetoothAdapter::Status::kDisabled); + gatt_server_->Stop(); + gatt_server_.reset(); + bt_provider_.reset(); + ble_provider_.reset(); + env_.Sync(false); + adapter_provider_.reset(); + env_.Stop(); + } + + void CreateMockDevice(DeviceFastPairVersion version, Protocol protocol) { + device_ = std::make_unique( + kMetadataId, remote_device_->GetMacAddress(), protocol); + device_->SetVersion(version); + if (version == DeviceFastPairVersion::kV1) { + device_->SetPublicAddress(remote_device_->GetMacAddress()); + } + if (protocol == Protocol::kFastPairSubsequentPairing) { + device_->SetAccountKey(AccountKey(account_key_)); + } + } + + void ConfigurePairingContext() { + api::PairingParams pairing_params; + pairing_params.pairing_type = + api::PairingParams::PairingType::kConfirmPasskey; + pairing_params.passkey = kPasskey; + env_.ConfigBluetoothPairingContext(&remote_device_->GetImpl(), + pairing_params); + } + + void CreateFastPairHandshakeInstanceForDevice() { + FastPairHandshakeLookup::SetCreateFunctionForTesting(absl::bind_front( + &FastPairPairerImplTest::CreateConnectedHandshake, this)); + + CountDownLatch latch(1); + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Create( + *device_, *mediums_, + [&](FastPairDevice& cb_device, std::optional failure) { + EXPECT_EQ(device_.get(), &cb_device); + EXPECT_EQ(failure, std::nullopt); + latch.CountDown(); + })); + latch.Await(); + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Get(device_.get())); + EXPECT_TRUE(handshake_->completed_successfully()); + EXPECT_EQ( + device_->GetPublicAddress().value(), + device::CanonicalizeBluetoothAddress(remote_device_->GetMacAddress())); + } + + std::unique_ptr CreateConnectedHandshake( + FastPairDevice& device, Mediums& mediums, + FastPairHandshake::OnCompleteCallback callback) { + CountDownLatch latch(1); + auto handshake = std::make_unique( + device, mediums, + [&](FastPairDevice& callback_device, + std::optional failure) { + callback(callback_device, failure); + latch.CountDown(); + }); + handshake_ = handshake.get(); + latch.Await(); + return handshake; + } + + // Sets up provider's metadata information. + void SetUpFastPairRepository() { + repository_ = FakeFastPairRepository::Create(kMetadataId, kPublicAntiSpoof); + } + + // Sets upprovider's gatt_server. + void SetupProviderGattServer( + absl::AnyInvocable trigger_keybase_value_change, + absl::AnyInvocable trigger_passkey_value_change) { + ble_provider_ = std::make_unique(*adapter_provider_); + gatt_server_ = ble_provider_->StartGattServer( + /*ServerGattConnectionCallback=*/{ + .on_characteristic_write_cb = + [&](const api::ble_v2::BlePeripheral& remote_device, + const api::ble_v2::GattCharacteristic& characteristic, + int offset, absl::string_view data, + BleV2Medium::ServerGattConnectionCallback:: + WriteValueCallback callback) { + auto it = characteristics_.find(characteristic); + if (it == characteristics_.end()) { + callback(absl::NotFoundError("characteristic not found")); + return; + } + if (characteristic == *key_based_characteristic_) { + trigger_keybase_value_change(); + } else if (characteristic == *passkey_characteristic_) { + trigger_passkey_value_change(); + } + callback(it->second.write_result); + }}); + // Insert fast pair related gatt characteristics + key_based_characteristic_ = gatt_server_->CreateCharacteristic( + kFastPairServiceUuid, kKeyBasedCharacteristicUuidV2, permissions_, + properties_); + characteristics_[*key_based_characteristic_].write_result = + absl::OkStatus(); + + passkey_characteristic_ = gatt_server_->CreateCharacteristic( + kFastPairServiceUuid, kPasskeyCharacteristicUuidV2, permissions_, + properties_); + characteristics_[*passkey_characteristic_].write_result = absl::OkStatus(); + + accountkey_characteristic_ = gatt_server_->CreateCharacteristic( + kFastPairServiceUuid, kAccountKeyCharacteristicUuidV2, permissions_, + properties_); + characteristics_[*accountkey_characteristic_].write_result = + absl::OkStatus(); + } + + // Triggers provider's gatt_server to response with public address + absl::Status TriggerKeyBasedGattChanged() { + std::unique_ptr fast_pair_data_encryptor_unique_ptr; + FastPairDataEncryptor* fast_pair_data_encryptor_; + if (device_->GetAccountKey().Ok()) { + CountDownLatch latch(1); + FastPairDataEncryptorImpl::Factory::CreateAsync( + *device_, + [&](std::unique_ptr fast_pair_data_encryptor) { + fast_pair_data_encryptor_unique_ptr = + std::move(fast_pair_data_encryptor); + + latch.CountDown(); + }); + latch.Await(); + fast_pair_data_encryptor_ = fast_pair_data_encryptor_unique_ptr.get(); + } else { + fast_pair_data_encryptor_ = handshake_->fast_pair_data_encryptor(); + } + std::array raw_response = kRawResponseBytes; + std::array provider_address_bytes; + device::ParseBluetoothAddress( + device_->GetBleAddress(), + absl::MakeSpan(provider_address_bytes.data(), + provider_address_bytes.size())); + std::copy(provider_address_bytes.begin(), provider_address_bytes.end(), + std::begin(raw_response) + 1); + std::array encryptedResponse = + fast_pair_data_encryptor_->EncryptBytes(raw_response); + std::array response; + std::copy(encryptedResponse.begin(), encryptedResponse.end(), + response.begin()); + return gatt_server_->NotifyCharacteristicChanged( + key_based_characteristic_.value(), false, ByteArray(response)); + } + + // Triggers provider's gatt_server to response with passkey + // success == true, response with correct passkey, + // otherwise, response with wrong passkey. + absl::Status TriggerPasskeyGattChanged(absl::string_view pin_code, + uint8_t fast_pair_message_type) { + FastPairDataEncryptor* fast_pair_data_encryptor_ = + handshake_->fast_pair_data_encryptor(); + std::array raw_response; + RAND_bytes(raw_response.data(), kAesBlockByteSize); + raw_response[0] = fast_pair_message_type; + uint32_t passkey = 0; + passkey = std::stoi(std::string(pin_code)); + + // Need to convert the uint_32 to uint_8 to use in our data vector. + raw_response[1] = (passkey & 0x00ff0000) >> 16; + raw_response[2] = (passkey & 0x0000ff00) >> 8; + raw_response[3] = passkey & 0x000000ff; + + std::array encryptedResponse = + fast_pair_data_encryptor_->EncryptBytes(raw_response); + std::array response; + std::copy(encryptedResponse.begin(), encryptedResponse.end(), + response.begin()); + return gatt_server_->NotifyCharacteristicChanged( + passkey_characteristic_.value(), false, ByteArray(response)); + } + + absl::Status TriggerPasskeyGattChangedWithWrongResponse() { + return gatt_server_->NotifyCharacteristicChanged( + passkey_characteristic_.value(), false, + ByteArray(std::string(kWrongResponse))); + } + + bool SetPairingResult( + std::optional error) { + return env_.SetPairingResult(&remote_device_->GetImpl(), error); + } + + void SetPasskeyCharacteristicsWriteResultToFailure() { + auto it = characteristics_.find(*passkey_characteristic_); + it->second.write_result = absl::UnknownError("Failed to write account key"); + } + + void SetAccountkeyCharacteristicsWriteResultToFailure() { + auto it = characteristics_.find(*accountkey_characteristic_); + it->second.write_result = absl::UnknownError("Failed to write account key"); + } + + protected: + const std::vector account_key_{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0x00, 0xAA, 0xBB, + 0xCC, 0xDD, 0xEE, 0xFF}; + std::unique_ptr mediums_; + std::unique_ptr device_; + BluetoothDevice* remote_device_ = nullptr; + std::unique_ptr fast_pair_pairer_; + + SingleThreadExecutor executor_; + + private: + MediumEnvironment& env_{MediumEnvironment::Instance()}; + std::unique_ptr repository_; + std::unique_ptr bt_provider_; + std::unique_ptr adapter_provider_; + std::unique_ptr gatt_server_; + std::unique_ptr ble_provider_; + FastPairHandshake* handshake_ = nullptr; + std::optional key_based_characteristic_; + std::optional passkey_characteristic_; + std::optional accountkey_characteristic_; + absl::flat_hash_map characteristics_; + Property properties_ = Property::kWrite | Property::kNotify; + Permission permissions_ = Permission::kWrite; +}; + +TEST_F(FastPairPairerImplTest, + SuccessInitialPairingWithDeviceVersionHigherThanV1) { + ConfigurePairingContext(); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairInitialPairing); + + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { + triggered_passkey_value_change = true; + EXPECT_OK(TriggerPasskeyGattChanged(kPasskey, kProviderPasskeyType)); + }); + CreateFastPairHandshakeInstanceForDevice(); + SetPairingResult(std::nullopt); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + EXPECT_FALSE(device_->GetAccountKey().Ok()); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { + EXPECT_TRUE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + paired_latch.Await(); + EXPECT_FALSE(failure_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(account_failure_latch.Await(kWaitTimeout).result()); + complete_latch.Await(); + + EXPECT_TRUE(triggered_keybase_value_change); + EXPECT_TRUE(triggered_passkey_value_change); + EXPECT_TRUE(fast_pair_pairer_->IsPaired()); + EXPECT_TRUE(device_->GetAccountKey().Ok()); +} + +TEST_F(FastPairPairerImplTest, SuccessInitialPairingWithDeviceV1) { + ConfigurePairingContext(); + CreateMockDevice(DeviceFastPairVersion::kV1, + Protocol::kFastPairInitialPairing); + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { + triggered_passkey_value_change = true; + EXPECT_OK(TriggerPasskeyGattChanged(kPasskey, kProviderPasskeyType)); + }); + SetPairingResult(std::nullopt); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + EXPECT_FALSE(device_->GetAccountKey().Ok()); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { + EXPECT_FALSE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + paired_latch.Await(); + EXPECT_FALSE(failure_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(account_failure_latch.Await(kWaitTimeout).result()); + complete_latch.Await(); + EXPECT_FALSE(triggered_keybase_value_change); + EXPECT_FALSE(triggered_passkey_value_change); + EXPECT_TRUE(fast_pair_pairer_->IsPaired()); + EXPECT_FALSE(device_->GetAccountKey().Ok()); +} + +TEST_F(FastPairPairerImplTest, SuccessSubsequentPairingWithDevice) { + ConfigurePairingContext(); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairSubsequentPairing); + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { + triggered_passkey_value_change = true; + EXPECT_OK(TriggerPasskeyGattChanged(kPasskey, kProviderPasskeyType)); + }); + CreateFastPairHandshakeInstanceForDevice(); + SetPairingResult(std::nullopt); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { + EXPECT_TRUE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + paired_latch.Await(); + EXPECT_FALSE(failure_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(account_failure_latch.Await(kWaitTimeout).result()); + complete_latch.Await(); + + EXPECT_TRUE(triggered_keybase_value_change); + EXPECT_TRUE(triggered_passkey_value_change); + EXPECT_TRUE(fast_pair_pairer_->IsPaired()); +} + +TEST_F(FastPairPairerImplTest, SuccessRetroactivePairingWithDevice) { + ConfigurePairingContext(); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairRetroactivePairing); + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { + triggered_passkey_value_change = true; + EXPECT_OK(TriggerPasskeyGattChanged(kPasskey, kProviderPasskeyType)); + }); + CreateFastPairHandshakeInstanceForDevice(); + SetPairingResult(std::nullopt); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + EXPECT_FALSE(device_->GetAccountKey().Ok()); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { + EXPECT_TRUE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + EXPECT_FALSE(paired_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(failure_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(account_failure_latch.Await(kWaitTimeout).result()); + complete_latch.Await(); + + EXPECT_TRUE(triggered_keybase_value_change); + EXPECT_FALSE(triggered_passkey_value_change); + EXPECT_TRUE(device_->GetAccountKey().Ok()); +} + +TEST_F(FastPairPairerImplTest, FailedToUnPair) { + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairInitialPairing); + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { + triggered_passkey_value_change = true; + EXPECT_OK(TriggerPasskeyGattChanged(kPasskey, kProviderPasskeyType)); + }); + CreateFastPairHandshakeInstanceForDevice(); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + EXPECT_FALSE(device_->GetAccountKey().Ok()); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + EXPECT_EQ(failure, PairFailure::kPairingAndConnect); + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { + EXPECT_TRUE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + EXPECT_FALSE(paired_latch.Await(kWaitTimeout).result()); + failure_latch.Await(); + EXPECT_FALSE(complete_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(account_failure_latch.Await(kWaitTimeout).result()); + EXPECT_TRUE(triggered_keybase_value_change); + EXPECT_FALSE(triggered_passkey_value_change); + EXPECT_FALSE(fast_pair_pairer_->IsPaired()); + EXPECT_FALSE(device_->GetAccountKey().Ok()); +} + +TEST_F(FastPairPairerImplTest, FailedToPairingWithAuthTimeout) { + ConfigurePairingContext(); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairInitialPairing); + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { + triggered_passkey_value_change = true; + EXPECT_OK(TriggerPasskeyGattChanged(kPasskey, kProviderPasskeyType)); + }); + CreateFastPairHandshakeInstanceForDevice(); + SetPairingResult(api::BluetoothPairingCallback::PairingError::kAuthTimeout); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + EXPECT_FALSE(device_->GetAccountKey().Ok()); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + EXPECT_EQ(failure, PairFailure::kPairingAndConnect); + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { + EXPECT_TRUE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + EXPECT_FALSE(paired_latch.Await(kWaitTimeout).result()); + failure_latch.Await(); + EXPECT_FALSE(complete_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(account_failure_latch.Await(kWaitTimeout).result()); + EXPECT_TRUE(triggered_keybase_value_change); + EXPECT_TRUE(triggered_passkey_value_change); + EXPECT_FALSE(fast_pair_pairer_->IsPaired()); + EXPECT_FALSE(device_->GetAccountKey().Ok()); +} + +TEST_F(FastPairPairerImplTest, NoPasskeyResponse) { + ConfigurePairingContext(); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairInitialPairing); + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { triggered_passkey_value_change = true; }); + CreateFastPairHandshakeInstanceForDevice(); + SetPairingResult(std::nullopt); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + EXPECT_FALSE(device_->GetAccountKey().Ok()); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + EXPECT_EQ(failure, PairFailure::kPasskeyResponseTimeout); + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { + EXPECT_TRUE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + EXPECT_FALSE(paired_latch.Await(kWaitTimeout).result()); + failure_latch.Await(); + EXPECT_FALSE(complete_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(account_failure_latch.Await(kWaitTimeout).result()); + EXPECT_TRUE(triggered_keybase_value_change); + EXPECT_TRUE(triggered_passkey_value_change); + EXPECT_FALSE(fast_pair_pairer_->IsPaired()); + EXPECT_FALSE(device_->GetAccountKey().Ok()); +} + +TEST_F(FastPairPairerImplTest, PasskeyMismatch) { + ConfigurePairingContext(); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairInitialPairing); + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { + triggered_passkey_value_change = true; + EXPECT_OK(TriggerPasskeyGattChanged("654321", kProviderPasskeyType)); + }); + CreateFastPairHandshakeInstanceForDevice(); + SetPairingResult(std::nullopt); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + EXPECT_FALSE(device_->GetAccountKey().Ok()); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + EXPECT_EQ(failure, PairFailure::kPasskeyMismatch); + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { + EXPECT_TRUE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + EXPECT_FALSE(paired_latch.Await(kWaitTimeout).result()); + failure_latch.Await(); + EXPECT_FALSE(complete_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(account_failure_latch.Await(kWaitTimeout).result()); + EXPECT_TRUE(triggered_keybase_value_change); + EXPECT_TRUE(triggered_passkey_value_change); + EXPECT_FALSE(fast_pair_pairer_->IsPaired()); + EXPECT_FALSE(device_->GetAccountKey().Ok()); +} + +TEST_F(FastPairPairerImplTest, ReceiveWithWrongPasskeyResponse) { + ConfigurePairingContext(); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairInitialPairing); + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { + triggered_passkey_value_change = true; + EXPECT_OK(TriggerPasskeyGattChangedWithWrongResponse()); + }); + CreateFastPairHandshakeInstanceForDevice(); + SetPairingResult(std::nullopt); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + EXPECT_FALSE(device_->GetAccountKey().Ok()); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + EXPECT_EQ(failure, PairFailure::kPasskeyDecryptFailure); + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { + EXPECT_TRUE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + EXPECT_FALSE(paired_latch.Await(kWaitTimeout).result()); + failure_latch.Await(); + EXPECT_FALSE(complete_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(account_failure_latch.Await(kWaitTimeout).result()); + EXPECT_TRUE(triggered_keybase_value_change); + EXPECT_TRUE(triggered_passkey_value_change); + EXPECT_FALSE(fast_pair_pairer_->IsPaired()); + EXPECT_FALSE(device_->GetAccountKey().Ok()); +} + +TEST_F(FastPairPairerImplTest, ReceiveWithWrongPasskeyMessageType) { + ConfigurePairingContext(); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairInitialPairing); + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { + triggered_passkey_value_change = true; + EXPECT_OK(TriggerPasskeyGattChanged(kPasskey, kSeekerPasskeyType)); + }); + CreateFastPairHandshakeInstanceForDevice(); + SetPairingResult(std::nullopt); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + EXPECT_FALSE(device_->GetAccountKey().Ok()); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + EXPECT_EQ(failure, PairFailure::kIncorrectPasskeyResponseType); + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { + EXPECT_TRUE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + EXPECT_FALSE(paired_latch.Await(kWaitTimeout).result()); + failure_latch.Await(); + EXPECT_FALSE(complete_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(account_failure_latch.Await(kWaitTimeout).result()); + EXPECT_TRUE(triggered_keybase_value_change); + EXPECT_TRUE(triggered_passkey_value_change); + EXPECT_FALSE(fast_pair_pairer_->IsPaired()); + EXPECT_FALSE(device_->GetAccountKey().Ok()); +} + +TEST_F(FastPairPairerImplTest, + SuccessPairingWithDeviceButFailedToWriteAccountkey) { + ConfigurePairingContext(); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairInitialPairing); + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { + triggered_passkey_value_change = true; + EXPECT_OK(TriggerPasskeyGattChanged(kPasskey, kProviderPasskeyType)); + }); + CreateFastPairHandshakeInstanceForDevice(); + SetPairingResult(std::nullopt); + SetAccountkeyCharacteristicsWriteResultToFailure(); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + EXPECT_FALSE(device_->GetAccountKey().Ok()); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { + EXPECT_TRUE(device.GetAccountKey().Ok()); + complete_latch.CountDown(); + }); + fast_pair_pairer_->StartPairing(); + paired_latch.Await(); + EXPECT_FALSE(failure_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(complete_latch.Await(kWaitTimeout).result()); + account_failure_latch.Await(); + EXPECT_TRUE(triggered_keybase_value_change); + EXPECT_TRUE(triggered_passkey_value_change); + EXPECT_TRUE(fast_pair_pairer_->IsPaired()); + EXPECT_FALSE(device_->GetAccountKey().Ok()); +} + +TEST_F(FastPairPairerImplTest, TestCancelPairing) { + ConfigurePairingContext(); + CreateMockDevice(DeviceFastPairVersion::kHigherThanV1, + Protocol::kFastPairInitialPairing); + bool triggered_keybase_value_change = false; + bool triggered_passkey_value_change = false; + SetUpFastPairRepository(); + SetupProviderGattServer( + [&]() { + triggered_keybase_value_change = true; + EXPECT_OK(TriggerKeyBasedGattChanged()); + }, + [&]() { + triggered_passkey_value_change = true; + fast_pair_pairer_->CancelPairing(); + }); + CreateFastPairHandshakeInstanceForDevice(); + SetPairingResult(std::nullopt); + CountDownLatch paired_latch(1); + CountDownLatch complete_latch(1); + CountDownLatch failure_latch(1); + CountDownLatch account_failure_latch(1); + + EXPECT_FALSE(device_->GetAccountKey().Ok()); + + fast_pair_pairer_ = FastPairPairerImpl::Factory::Create( + *device_, *mediums_, &executor_, + [&](FastPairDevice& cb_device) { paired_latch.CountDown(); }, + [&](FastPairDevice& device, PairFailure failure) { + EXPECT_EQ(failure, PairFailure::kPairingAndConnect); + failure_latch.CountDown(); + }, + [&](FastPairDevice& device, PairFailure failure) { + account_failure_latch.CountDown(); + }, + [&](FastPairDevice& device) { complete_latch.CountDown(); }); + fast_pair_pairer_->StartPairing(); + EXPECT_FALSE(paired_latch.Await(kWaitTimeout).result()); + failure_latch.Await(); + EXPECT_FALSE(account_failure_latch.Await(kWaitTimeout).result()); + EXPECT_FALSE(complete_latch.Await(kWaitTimeout).result()); + + EXPECT_TRUE(triggered_keybase_value_change); + EXPECT_TRUE(triggered_passkey_value_change); + EXPECT_FALSE(device_->GetAccountKey().Ok()); +} +} // namespace fastpair +} // namespace nearby diff --git a/internal/platform/implementation/windows/bluetooth_pairing.cc b/internal/platform/implementation/windows/bluetooth_pairing.cc index f43d1193..0dc75c0b 100644 --- a/internal/platform/implementation/windows/bluetooth_pairing.cc +++ b/internal/platform/implementation/windows/bluetooth_pairing.cc @@ -143,7 +143,7 @@ bool BluetoothPairing::FinishPairing( bool BluetoothPairing::CancelPairing() { NEARBY_LOGS(VERBOSE) << __func__ - << "Start to cancel ongoing pairing process."; + << " Start to cancel ongoing pairing process."; try { if (!pairing_deferral_) { NEARBY_LOGS(VERBOSE) << __func__ << "No ongoing pairing process."; @@ -156,7 +156,7 @@ bool BluetoothPairing::CancelPairing() { // |was_cancelled_| is set so that OnPair(), which is called when the // deferral is completed, will know that cancellation was the actual result. was_cancelled_ = true; - pairing_deferral_.Complete(); + pairing_deferral_.Close(); NEARBY_LOGS(VERBOSE) << __func__ << "Canceled ongoing pairing process."; return true; } catch (std::exception exception) { @@ -202,7 +202,7 @@ bool BluetoothPairing::Unpair() { bool BluetoothPairing::IsPaired() { try { bool is_paired = bluetooth_device_.DeviceInformation().Pairing().IsPaired(); - NEARBY_LOGS(INFO) << __func__ << (is_paired ? "True" : "False"); + NEARBY_LOGS(INFO) << __func__ << (is_paired ? " True" : " False"); return is_paired; } catch (std::exception exception) { NEARBY_LOGS(ERROR) << __func__ << ": Failed to get IsPaired. exception: "