From c1835fcfa13e11d3db800f54cec39ac029674678 Mon Sep 17 00:00:00 2001 From: Qin Wang Date: Fri, 31 Mar 2023 13:51:41 -0700 Subject: [PATCH] Implement FastPairGattServiceClient for fast pair windows PiperOrigin-RevId: 521005152 --- fastpair/common/pair_failure.cc | 4 + fastpair/common/pair_failure.h | 4 +- fastpair/dataparser/BUILD | 1 + fastpair/handshake/BUILD | 34 ++ .../handshake/fast_pair_gatt_service_client.h | 67 +++ .../fast_pair_gatt_service_client_impl.cc | 456 ++++++++++++++++++ .../fast_pair_gatt_service_client_impl.h | 152 ++++++ ...fast_pair_gatt_service_client_impl_test.cc | 347 +++++++++++++ internal/platform/BUILD | 1 + 9 files changed, 1065 insertions(+), 1 deletion(-) create mode 100644 fastpair/handshake/fast_pair_gatt_service_client.h create mode 100644 fastpair/handshake/fast_pair_gatt_service_client_impl.cc create mode 100644 fastpair/handshake/fast_pair_gatt_service_client_impl.h create mode 100644 fastpair/handshake/fast_pair_gatt_service_client_impl_test.cc diff --git a/fastpair/common/pair_failure.cc b/fastpair/common/pair_failure.cc index b7ed8146..bd32d6a6 100644 --- a/fastpair/common/pair_failure.cc +++ b/fastpair/common/pair_failure.cc @@ -86,6 +86,10 @@ std::ostream& operator<<(std::ostream& stream, PairFailure failure) { case PairFailure::kPasskeyMismatch: stream << "[Passkeys did not match]"; break; + case PairFailure::kPairingDeviceLostBetweenGattConnectionAttempts: + stream + << "[Potential pairing device lost between GATT connection attempts]"; + break; } return stream; diff --git a/fastpair/common/pair_failure.h b/fastpair/common/pair_failure.h index dd010340..c69b7bb6 100644 --- a/fastpair/common/pair_failure.h +++ b/fastpair/common/pair_failure.h @@ -66,7 +66,9 @@ enum class PairFailure { kIncorrectPasskeyResponseType = 18, // Passkeys did not match. kPasskeyMismatch = 19, - kMaxValue = kPasskeyMismatch, + // Potential pairing device lost between GATT connection attempts. + kPairingDeviceLostBetweenGattConnectionAttempts = 20, + kMaxValue = kPairingDeviceLostBetweenGattConnectionAttempts, }; std::ostream& operator<<(std::ostream& stream, PairFailure failure); diff --git a/fastpair/dataparser/BUILD b/fastpair/dataparser/BUILD index 8e0a35b7..4b3eff0a 100644 --- a/fastpair/dataparser/BUILD +++ b/fastpair/dataparser/BUILD @@ -71,6 +71,7 @@ cc_test( deps = [ ":decoder", "//fastpair/testing", + "//internal/platform/implementation/g3", # build_cleaner: keep "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_absl//absl/strings", "@com_google_googletest//:gtest_main", diff --git a/fastpair/handshake/BUILD b/fastpair/handshake/BUILD index f964f3b4..488bf176 100644 --- a/fastpair/handshake/BUILD +++ b/fastpair/handshake/BUILD @@ -18,10 +18,13 @@ cc_library( name = "handshake", srcs = [ "fast_pair_data_encryptor_impl.cc", + "fast_pair_gatt_service_client_impl.cc", ], hdrs = [ "fast_pair_data_encryptor.h", "fast_pair_data_encryptor_impl.h", + "fast_pair_gatt_service_client.h", + "fast_pair_gatt_service_client_impl.h", ], visibility = [ "//:__subpackages__", @@ -31,11 +34,15 @@ cc_library( "//fastpair/common", "//fastpair/crypto", "//fastpair/dataparser", + "//fastpair/internal/ble", "//fastpair/repository", "//fastpair/server_access", "//internal/base:bluetooth_address", "//internal/platform:base", + "//internal/platform:comm", "//internal/platform:logging", + "//internal/platform:types", + "//internal/platform:uuid", "@boringssl//:crypto", "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/functional:bind_front", @@ -71,6 +78,7 @@ cc_test( "//fastpair/common", "//fastpair/crypto", "//fastpair/dataparser", + "//fastpair/internal/ble", "//fastpair/server_access:test_support", "//fastpair/testing", "//internal/platform:logging", @@ -83,3 +91,29 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "fast_pair_gatt_service_client_impl_test", + size = "small", + srcs = [ + "fast_pair_gatt_service_client_impl_test.cc", + ], + shard_count = 16, + deps = [ + ":handshake", + ":test_support", + "//fastpair/common", + "//fastpair/testing", + "//internal/platform:base", + "//internal/platform:comm", + "//internal/platform:test_util", + "//internal/platform:types", + "//internal/platform/implementation:types", + "//internal/platform/implementation/g3", # build_cleaner: keep + "//internal/test", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/fastpair/handshake/fast_pair_gatt_service_client.h b/fastpair/handshake/fast_pair_gatt_service_client.h new file mode 100644 index 00000000..2ab01c51 --- /dev/null +++ b/fastpair/handshake/fast_pair_gatt_service_client.h @@ -0,0 +1,67 @@ +// 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_HANDSHAKE_FAST_PAIR_GATT_SERVICE_CLIENT_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_GATT_SERVICE_CLIENT_H_ + +#include + +#include "absl/functional/any_invocable.h" +#include "absl/strings/string_view.h" +#include "fastpair/common/constant.h" +#include "fastpair/common/pair_failure.h" +#include "fastpair/handshake/fast_pair_data_encryptor.h" +#include "internal/platform/ble_v2.h" + +namespace nearby { +namespace fastpair { +using WriteResponseCallback = absl::AnyInvocable failure)>; + +// This class is responsible for connecting to the Fast Pair GATT service for a +// device and invoking a callback when ready, or when an error is discovered +// during initialization. +class FastPairGattServiceClient { + public: + virtual ~FastPairGattServiceClient() = default; + + virtual void InitializeGattConnection( + absl::AnyInvocable)> + on_gatt_initialized_callback) = 0; + + // Constructs a data vector based on the message type, flags, provider + // address, and seekers address. + // Subscribe a notification for key based Pairing. + // Once the notification subscribed successfully, the message data will be + // written to the key based characteristic. + virtual void WriteRequestAsync( + uint8_t message_type, uint8_t flags, absl::string_view provider_address, + absl::string_view seekers_address, + const FastPairDataEncryptor& fast_pair_data_encryptor, + WriteResponseCallback write_response_callback) = 0; + + // Constructs a data vector based on the message type and passkey. + // Subscribe a notification for the passkey. + // Once the notification subscribed successfully, the passkey data will be + // written to the passkey characteristic. + virtual void WritePasskeyAsync( + uint8_t message_type, uint32_t passkey, + const FastPairDataEncryptor& fast_pair_data_encryptor, + WriteResponseCallback write_response_callback) = 0; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_GATT_SERVICE_CLIENT_H_ diff --git a/fastpair/handshake/fast_pair_gatt_service_client_impl.cc b/fastpair/handshake/fast_pair_gatt_service_client_impl.cc new file mode 100644 index 00000000..bb0685f5 --- /dev/null +++ b/fastpair/handshake/fast_pair_gatt_service_client_impl.cc @@ -0,0 +1,456 @@ +// 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/handshake/fast_pair_gatt_service_client_impl.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/functional/any_invocable.h" +#include "absl/functional/bind_front.h" +#include "absl/strings/string_view.h" +#include "fastpair/common/constant.h" +#include "fastpair/handshake/fast_pair_data_encryptor.h" +#include "fastpair/handshake/fast_pair_gatt_service_client.h" +#include "internal/base/bluetooth_address.h" +#include "internal/platform/ble_v2.h" +#include "internal/platform/logging.h" +#include "internal/platform/uuid.h" +#include + +namespace nearby { +namespace fastpair { + +namespace { +// We have two UUID possibilities for each characteristic because they changed +// across different Fast Pair versions. +constexpr Uuid kFastPairServiceUuid(0x0000FE2C00001000, 0x800000805F9B34FB); +constexpr Uuid kKeyBasedCharacteristicUuidV1(0x0000123400001000, + 0x800000805F9B34FB); +constexpr Uuid kKeyBasedCharacteristicUuidV2(0xFE2C123483664814, + 0x8EB001DE32100BEA); +constexpr Uuid kPasskeyCharacteristicUuidV1(0x0000123500001000, + 0x800000805F9B34FB); +constexpr Uuid kPasskeyCharacteristicUuidV2(0xFE2C123583664814, + 0x8EB001DE32100BEA); + +constexpr absl::Duration kGattOperationTimeout = absl::Seconds(15); +constexpr int kMaxNumGattConnectionAttempts = 3; +} // namespace + +// FastPairGattServiceClientImpl::Factory +// static +FastPairGattServiceClientImpl::Factory* + FastPairGattServiceClientImpl::Factory::g_test_factory_ = nullptr; + +// static +std::unique_ptr +FastPairGattServiceClientImpl::Factory::Create(const FastPairDevice& device) { + if (g_test_factory_) { + return g_test_factory_->CreateInstance(); + } + + return std::make_unique(device); +} + +// static +void FastPairGattServiceClientImpl::Factory::SetFactoryForTesting( + Factory* g_test_factory) { + g_test_factory_ = g_test_factory; +} + +FastPairGattServiceClientImpl::Factory::~Factory() = default; + +FastPairGattServiceClientImpl::FastPairGattServiceClientImpl( + const FastPairDevice& device) + : device_address_(device.GetBleAddress()) {} + +void FastPairGattServiceClientImpl::InitializeGattConnection( + absl::AnyInvocable)> + on_gatt_initialized_callback) { + NEARBY_LOGS(INFO) << __func__ + << ": Starting the GATT connection to the device."; + on_gatt_initialized_callback_ = std::move(on_gatt_initialized_callback); + AttemptGattConnection(); +} + +void FastPairGattServiceClientImpl::AttemptGattConnection() { + NEARBY_LOGS(INFO) << __func__ << ": Attempt to connect to the device."; + if (num_gatt_connection_attempts_ == kMaxNumGattConnectionAttempts) { + NotifyInitializedError(PairFailure::kCreateGattConnection); + return; + } + num_gatt_connection_attempts_++; + NEARBY_LOGS(INFO) << __func__ << ": Starting GATT connection attempt #" + << num_gatt_connection_attempts_ << " to device"; + + if (gatt_client_) { + NEARBY_LOGS(INFO) << __func__ + << ": Disconnecting previous connections before attempt"; + gatt_client_->Disconnect(); + gatt_client_ = nullptr; + } + CreateGattConnection(); +} + +void FastPairGattServiceClientImpl::CreateGattConnection() { + NEARBY_LOGS(INFO) << __func__ << " : Create Gatt Connection to the device."; + gatt_client_ = ble_.ConnectToGattServer(device_address_); + if (!gatt_client_) { + // The device must have been lost between connection attempts. + NotifyInitializedError( + PairFailure::kPairingDeviceLostBetweenGattConnectionAttempts); + return; + } + DiscoverServiceAndCharacteristics(); +} + +void FastPairGattServiceClientImpl::DiscoverServiceAndCharacteristics() { + NEARBY_LOGS(INFO) << __func__ + << " : Start to discovery servie and characteristic."; + gatt_service_discovery_timer_.Start( + kGattOperationTimeout / absl::Milliseconds(1), 0, + [&]() { OnGattServiceDiscoveryTimeout(); }); + + if (gatt_client_->DiscoverServiceAndCharacteristics( + kFastPairServiceUuid, + {kKeyBasedCharacteristicUuidV2, kPasskeyCharacteristicUuidV2}) || + gatt_client_->DiscoverServiceAndCharacteristics( + kFastPairServiceUuid, + {kKeyBasedCharacteristicUuidV1, kPasskeyCharacteristicUuidV1})) { + gatt_service_discovery_timer_.Stop(); + NEARBY_LOGS(INFO) << __func__ + << ": Completed discovery for Fast Pair GATT service and " + "characterisitc."; + GetFastPairGattCharacteristics(); + return; + } + NEARBY_LOGS(INFO) << __func__ + << ": Failed to discovery for Fast Pair GATT service and " + "characterisitc." + << PairFailure::kGattServiceDiscovery; + AttemptGattConnection(); +} + +void FastPairGattServiceClientImpl::GetFastPairGattCharacteristics() { + NEARBY_LOGS(INFO) << __func__ << " :Start to get Fast Pair characteristic."; + key_based_characteristic_ = GetCharacteristicsByUUIDs( + kKeyBasedCharacteristicUuidV1, kKeyBasedCharacteristicUuidV2); + if (!key_based_characteristic_.has_value()) { + NotifyInitializedError( + PairFailure::kKeyBasedPairingCharacteristicDiscovery); + return; + } + + passkey_characteristic_ = GetCharacteristicsByUUIDs( + kPasskeyCharacteristicUuidV1, kPasskeyCharacteristicUuidV2); + if (!passkey_characteristic_.has_value()) { + NotifyInitializedError(PairFailure::kPasskeyCharacteristicDiscovery); + return; + } + + is_initialized_ = true; + std::move(on_gatt_initialized_callback_)(absl::nullopt); +} + +std::optional +FastPairGattServiceClientImpl::GetCharacteristicsByUUIDs(const Uuid& uuidV1, + const Uuid& uuidV2) { + // Default to V2 device to match Android implementation. + std::optional characteristics = + gatt_client_->GetCharacteristic(kFastPairServiceUuid, uuidV2); + if (characteristics.has_value()) { + return characteristics; + } + return gatt_client_->GetCharacteristic(kFastPairServiceUuid, uuidV1); +} + +void FastPairGattServiceClientImpl::OnGattServiceDiscoveryTimeout() { + NEARBY_LOGS(INFO) << __func__ + << ": reattempting from previous GATT connection failure: " + << PairFailure::kGattServiceDiscoveryTimeout; + AttemptGattConnection(); +} + +std::array +FastPairGattServiceClientImpl::CreateRequest( + uint8_t message_type, uint8_t flags, absl::string_view provider_address, + absl::string_view seekers_address) { + std::array data_to_write; + RAND_bytes(data_to_write.data(), kAesBlockByteSize); + + data_to_write[0] = message_type; + data_to_write[1] = flags; + + std::array provider_address_bytes; + + device::ParseBluetoothAddress(provider_address, + absl::MakeSpan(provider_address_bytes.data(), + provider_address_bytes.size())); + std::copy(provider_address_bytes.begin(), provider_address_bytes.end(), + std::begin(data_to_write) + kProviderAddressStartIndex); + + // Seekers address can be empty, in which we would just have the bytes be + // the salt. + if (!seekers_address.empty()) { + std::array seeker_address_bytes; + device::ParseBluetoothAddress(seekers_address, + absl::MakeSpan(seeker_address_bytes.data(), + seeker_address_bytes.size())); + std::copy(seeker_address_bytes.begin(), seeker_address_bytes.end(), + std::begin(data_to_write) + kSeekerAddressStartIndex); + } + + return data_to_write; +} + +std::array +FastPairGattServiceClientImpl::CreatePasskeyBlock(uint8_t message_type, + uint32_t passkey) { + std::array data_to_write; + RAND_bytes(data_to_write.data(), kAesBlockByteSize); + + data_to_write[0] = message_type; + + // Need to convert the uint_32 to uint_8 to use in our data vector. + data_to_write[1] = (passkey & 0x00ff0000) >> 16; + data_to_write[2] = (passkey & 0x0000ff00) >> 8; + data_to_write[3] = passkey & 0x000000ff; + return data_to_write; +} + +void FastPairGattServiceClientImpl::WriteRequestAsync( + uint8_t message_type, uint8_t flags, absl::string_view provider_address, + absl::string_view seekers_address, + const FastPairDataEncryptor& fast_pair_data_encryptor, + WriteResponseCallback callback) { + DCHECK(is_initialized_); + DCHECK(!key_based_write_response_callback_); + // The key based request should only ever be written once + DCHECK(!is_key_based_notification_subscribed_); + + key_based_write_response_callback_ = std::move(callback); + + const std::array data_to_write = + fast_pair_data_encryptor.EncryptBytes(CreateRequest( + message_type, flags, provider_address, seekers_address)); + std::vector data_to_write_vec(data_to_write.begin(), + data_to_write.end()); + + // Append the public version of the private key to the message so thedevice + // can generate the shared secret to decrypt the message. + const std::optional> public_key = + fast_pair_data_encryptor.GetPublicKey(); + + if (public_key) { + const std::vector public_key_vec = std::vector( + public_key.value().begin(), public_key.value().end()); + data_to_write_vec.insert(data_to_write_vec.end(), public_key_vec.begin(), + public_key_vec.end()); + } + // Subscribe the notification once the keybased characteristic's value changed + if (SubscribeKeyBasedCharacteristic()) { + is_key_based_notification_subscribed_ = true; + // Write public address request to the keybased characteristic + WriteKeyBasedCharacteristic( + std::string(data_to_write_vec.begin(), data_to_write_vec.end())); + } +} + +bool FastPairGattServiceClientImpl::SubscribeKeyBasedCharacteristic() { + NEARBY_LOGS(INFO) << __func__ + << " :Start to subscribe notification " + "once keybased characteristic changed."; + key_based_subscription_timer_.Start( + kGattOperationTimeout / absl::Milliseconds(1), 0, + absl::bind_front( + &FastPairGattServiceClientImpl::NotifyWriteRequestError, this, + PairFailure::kKeyBasedPairingCharacteristicSubscriptionTimeout)); + + if (gatt_client_->SetCharacteristicSubscription( + key_based_characteristic_.value(), true, + [this](absl::string_view value) { + FastPairGattServiceClientImpl::OnCharacteristicValueChanged( + key_based_characteristic_.value(), value); + })) { + key_based_subscription_timer_.Stop(); + NEARBY_LOGS(INFO) + << __func__ << ": Successfully subscribe the key based characteristic."; + return true; + } + NEARBY_LOGS(INFO) << __func__ + << ": Failed to subscribe the key based characteristic."; + NotifyWriteRequestError( + PairFailure::kKeyBasedPairingCharacteristicSubscription); + return false; +} + +void FastPairGattServiceClientImpl::WriteKeyBasedCharacteristic( + absl::string_view request) { + NEARBY_LOGS(INFO) << __func__ << " :Start to write keybased characteristic."; + key_based_write_request_timer_.Start( + kGattOperationTimeout / absl::Milliseconds(1), 0, + absl::bind_front(&FastPairGattServiceClientImpl::NotifyWriteRequestError, + this, PairFailure::kKeyBasedPairingResponseTimeout)); + + if (gatt_client_->WriteCharacteristic( + key_based_characteristic_.value(), request, + api::ble_v2::GattClient::WriteType::kWithResponse)) { + NEARBY_LOGS(INFO) << __func__ + << ": Successfully write the key basedcharacteristic."; + return; + } + NEARBY_LOGS(INFO) << __func__ + << ": Failed to write the key based characteristic "; + NotifyWriteRequestError(PairFailure::kKeyBasedPairingCharacteristicWrite); +} + +void FastPairGattServiceClientImpl::WritePasskeyAsync( + uint8_t message_type, uint32_t passkey, + const FastPairDataEncryptor& fast_pair_data_encryptor, + WriteResponseCallback callback) { + DCHECK(is_initialized_); + DCHECK(message_type == kSeekerPasskey); + + passkey_write_response_callback_ = std::move(callback); + const std::array data_to_write = + fast_pair_data_encryptor.EncryptBytes( + CreatePasskeyBlock(message_type, passkey)); + std::vector data_to_write_vec(data_to_write.begin(), + data_to_write.end()); + + // Subscribe the notification once the passkey characteristic's value changed + if (SubscribePasskeyCharacteristic()) { + is_passkey_notification_subscribed_ = true; + // Write passkey confonirmation request to the passkey characteristic + WritePasskeyCharacteristic( + std::string(data_to_write_vec.begin(), data_to_write_vec.end())); + } +} + +bool FastPairGattServiceClientImpl::SubscribePasskeyCharacteristic() { + NEARBY_LOGS(INFO) << __func__ + << " :Start to subscribe notification " + "once passkey characteristic changed."; + passkey_subscription_timer_.Start( + kGattOperationTimeout / absl::Milliseconds(1), 0, + absl::bind_front(&FastPairGattServiceClientImpl::NotifyWritePasskeyError, + this, + PairFailure::kPasskeyCharacteristicSubscriptionTimeout)); + + if (gatt_client_->SetCharacteristicSubscription( + passkey_characteristic_.value(), true, + [this](absl::string_view value) { + FastPairGattServiceClientImpl::OnCharacteristicValueChanged( + passkey_characteristic_.value(), value); + })) { + passkey_subscription_timer_.Stop(); + NEARBY_LOGS(INFO) << __func__ + << ": Successfully subscribe the passkey characteristic."; + return true; + } + NEARBY_LOGS(INFO) << __func__ + << ": Failed to subscribe the passkey characteristic."; + NotifyWritePasskeyError(PairFailure::kPasskeyCharacteristicSubscription); + return false; +} + +void FastPairGattServiceClientImpl::WritePasskeyCharacteristic( + absl::string_view request) { + passkey_write_request_timer_.Start( + kGattOperationTimeout / absl::Milliseconds(1), 0, + absl::bind_front(&FastPairGattServiceClientImpl::NotifyWritePasskeyError, + this, PairFailure::kPasskeyResponseTimeout)); + if (gatt_client_->WriteCharacteristic( + passkey_characteristic_.value(), request, + api::ble_v2::GattClient::WriteType::kWithResponse)) { + NEARBY_LOGS(INFO) << __func__ + << ": Successfully write the passkey characteristic."; + return; + } + NEARBY_LOGS(INFO) << __func__ + << ": Failed to write the passkey characteristic "; + NotifyWritePasskeyError(PairFailure::kPasskeyPairingCharacteristicWrite); +} + +void FastPairGattServiceClientImpl::OnCharacteristicValueChanged( + const GattCharacteristic& characteristic, absl::string_view value) { + // We check that the callbacks still exists still before we run the + // it with the response bytes to handle the case where the callback + // has already been used to notify error. This can happen if the timer for + // fires with an error, and then the write completes successfully after and + // we get response bytes here. + if (characteristic == key_based_characteristic_.value() && + key_based_write_response_callback_) { + key_based_write_request_timer_.Stop(); + NEARBY_LOGS(INFO) << __func__ + << ": key based characteristic value changed."; + std::move(key_based_write_response_callback_)(value, + /*failure=*/std::nullopt); + } else if (characteristic == passkey_characteristic_.value() && + passkey_write_response_callback_) { + passkey_write_request_timer_.Stop(); + NEARBY_LOGS(INFO) << __func__ << ": Passkey characteristic value changed."; + std::move(passkey_write_response_callback_)(value, + /*failure=*/std::nullopt); + } +} + +void FastPairGattServiceClientImpl::NotifyInitializedError( + PairFailure failure) { + NEARBY_LOGS(VERBOSE) << __func__ << failure; + ClearCurrentState(); + + if (on_gatt_initialized_callback_) { + NEARBY_LOGS(VERBOSE) << __func__ << "Executing initialized callback"; + std::move(on_gatt_initialized_callback_)(failure); + } +} + +void FastPairGattServiceClientImpl::NotifyWriteRequestError( + PairFailure failure) { + NEARBY_LOGS(VERBOSE) << __func__ << "NotifyWriteRequestError"; + key_based_write_request_timer_.Stop(); + DCHECK(key_based_write_response_callback_); + std::move(key_based_write_response_callback_)("", failure); +} + +void FastPairGattServiceClientImpl::NotifyWritePasskeyError( + PairFailure failure) { + NEARBY_LOGS(VERBOSE) << __func__ << "NotifyWritePasskeyError"; + passkey_write_request_timer_.Stop(); + DCHECK(passkey_write_response_callback_); + std::move(passkey_write_response_callback_)("", failure); +} + +void FastPairGattServiceClientImpl::ClearCurrentState() { + gatt_client_.reset(); + key_based_characteristic_ = std::nullopt; + passkey_characteristic_ = std::nullopt; + gatt_service_discovery_timer_.Stop(); + passkey_subscription_timer_.Stop(); + key_based_subscription_timer_.Stop(); + passkey_write_request_timer_.Stop(); + key_based_write_request_timer_.Stop(); +} + +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/handshake/fast_pair_gatt_service_client_impl.h b/fastpair/handshake/fast_pair_gatt_service_client_impl.h new file mode 100644 index 00000000..d904e14a --- /dev/null +++ b/fastpair/handshake/fast_pair_gatt_service_client_impl.h @@ -0,0 +1,152 @@ +// 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_HANDSHAKE_FAST_PAIR_GATT_SERVICE_CLIENT_IMPL_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_GATT_SERVICE_CLIENT_IMPL_H_ + +#include +#include +#include +#include + +#include "absl/functional/any_invocable.h" +#include "absl/strings/string_view.h" +#include "fastpair/common/fast_pair_device.h" +#include "fastpair/common/pair_failure.h" +#include "fastpair/handshake/fast_pair_gatt_service_client.h" +#include "fastpair/internal/ble/ble.h" +#include "internal/platform/ble_v2.h" +#include "internal/platform/timer_impl.h" + +namespace nearby { +namespace fastpair { +using GattCharacteristic = api::ble_v2::GattCharacteristic; + +// This class is responsible for connecting to the Fast Pair GATT service for a +// device and invoking a callback when ready, or when an error is discovered +// during initialization. +class FastPairGattServiceClientImpl : public FastPairGattServiceClient { + public: + class Factory { + public: + static std::unique_ptr Create( + const FastPairDevice& device); + static void SetFactoryForTesting(Factory* test_factory); + + protected: + virtual ~Factory(); + virtual std::unique_ptr CreateInstance() = 0; + + private: + static Factory* g_test_factory_; + }; + + explicit FastPairGattServiceClientImpl(const FastPairDevice& device); + FastPairGattServiceClientImpl(const FastPairGattServiceClientImpl&) = delete; + FastPairGattServiceClientImpl& operator=( + const FastPairGattServiceClientImpl&) = delete; + ~FastPairGattServiceClientImpl() override = default; + + void InitializeGattConnection( + absl::AnyInvocable)> + on_gatt_initialized_callback) override; + + void WriteRequestAsync( + uint8_t message_type, uint8_t flags, absl::string_view provider_address, + absl::string_view seekers_address, + const FastPairDataEncryptor& fast_pair_data_encryptor, + WriteResponseCallback write_response_callback) override; + + void WritePasskeyAsync( + uint8_t message_type, uint32_t passkey, + const FastPairDataEncryptor& fast_pair_data_encryptor, + WriteResponseCallback write_response_callback) override; + + private: + // Attempt to create a GATT connection with the device. This method may be + // called multiple times. + void AttemptGattConnection(); + void CreateGattConnection(); + void DiscoverServiceAndCharacteristics(); + void GetFastPairGattCharacteristics(); + std::optional GetCharacteristicsByUUIDs( + const Uuid& uuidV1, const Uuid& uuidV2); + + // Operations on KeyBased Characteristic + // Creates a data vector based on parameter information. + std::array CreateRequest( + uint8_t message_type, uint8_t flags, absl::string_view provider_address, + absl::string_view seekers_address); + // Subscribe notification when KeyBased Characteristic value changes + bool SubscribeKeyBasedCharacteristic(); + // Write request to KeyBased Characteristic + void WriteKeyBasedCharacteristic(absl::string_view request); + + // Operations on Passkey Characteristic + // Creates a data vector based on parameter information. + std::array CreatePasskeyBlock( + uint8_t message_type, uint32_t passkey); + // Subscribe notification when Passkey Characteristic value changes + bool SubscribePasskeyCharacteristic(); + // Write request to Passkey Characteristic + void WritePasskeyCharacteristic(absl::string_view request); + + // Callback is triggered when characteristic value changes + void OnCharacteristicValueChanged(const GattCharacteristic& characteristic, + absl::string_view value); + + // Invokes the initialized callback with the proper PairFailure and clears + // local state. + void NotifyInitializedError(PairFailure failure); + // Invokes the write response callback with the proper PairFailure on a + // write error. + void NotifyWriteRequestError(PairFailure failure); + void NotifyWritePasskeyError(PairFailure failure); + + void ClearCurrentState(); + + // Timers + TimerImpl gatt_service_discovery_timer_; + TimerImpl key_based_subscription_timer_; + TimerImpl passkey_subscription_timer_; + TimerImpl key_based_write_request_timer_; + TimerImpl passkey_write_request_timer_; + + void OnGattServiceDiscoveryTimeout(); + + // Callback + absl::AnyInvocable)> + on_gatt_initialized_callback_; + WriteResponseCallback key_based_write_response_callback_; + WriteResponseCallback passkey_write_response_callback_; + + // Fast Pair Characteristic + std::optional key_based_characteristic_; + std::optional passkey_characteristic_; + + bool is_key_based_notification_subscribed_ = false; + bool is_passkey_notification_subscribed_ = false; + + // Initialize with zero failures. + int num_gatt_connection_attempts_ = 0; + + bool is_initialized_ = false; + std::string device_address_; + std::unique_ptr gatt_client_; + Ble ble_; +}; +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_GATT_SERVICE_CLIENT_IMPL_H_ diff --git a/fastpair/handshake/fast_pair_gatt_service_client_impl_test.cc b/fastpair/handshake/fast_pair_gatt_service_client_impl_test.cc new file mode 100644 index 00000000..07f36962 --- /dev/null +++ b/fastpair/handshake/fast_pair_gatt_service_client_impl_test.cc @@ -0,0 +1,347 @@ +// 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/handshake/fast_pair_gatt_service_client_impl.h" + +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "fastpair/common/constant.h" +#include "fastpair/common/fast_pair_device.h" +#include "fastpair/common/pair_failure.h" +#include "fastpair/handshake/fake_fast_pair_data_encryptor.h" +#include "internal/platform/ble_v2.h" +#include "internal/platform/bluetooth_adapter.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/count_down_latch.h" +#include "internal/platform/implementation/system_clock.h" +#include "internal/platform/medium_environment.h" + +namespace nearby { +namespace fastpair { + +namespace { +using Property = nearby::api::ble_v2::GattCharacteristic::Property; +using Permission = nearby::api::ble_v2::GattCharacteristic::Permission; +using GattCharacteristic = nearby::api::ble_v2::GattCharacteristic; +using WriteType = nearby::api::ble_v2::GattClient::WriteType; + +constexpr absl::Duration kGattOperationTimeout = absl::Seconds(15); +constexpr absl::string_view kMetadataId("test_id"); +constexpr absl::string_view kProviderAddress("11:22:33:44:55:66"); +constexpr absl::string_view kSeekerAddress("AA:BB:CC:DD:EE:00"); +constexpr Uuid kFastPairServiceUuid(0x0000FE2C00001000, 0x800000805F9B34FB); +constexpr Uuid kKeyBasedCharacteristicUuidV2(0xFE2C123483664814, + 0x8EB001DE32100BEA); +constexpr Uuid kPasskeyCharacteristicUuidV2(0xFE2C123583664814, + 0x8EB001DE32100BEA); +// Length of advertisement byte should be 16 +constexpr absl::string_view kKeyBasedCharacteristicAdvertisementByte = + "keyBasedCharacte"; +constexpr absl::string_view kPasskeyharacteristicAdvertisementByte = + "passkeyCharacter"; +constexpr Uuid kWrongServiceId(0x0000FE2B00001000, 0x800000805F9B34FB); +constexpr uint8_t kMessageType = 0x00; +constexpr uint8_t kFlags = 0x00; + +constexpr uint32_t kPasskey = 123456; + +constexpr std::array kPublicKey = { + 0x01, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D, 0x01, 0x5E, 0x3F, + 0x45, 0x61, 0xC3, 0x32, 0x1D, 0x01, 0x5E, 0x3F, 0x45, 0x61, 0xC3, + 0x32, 0x1D, 0x01, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D, 0x01, + 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D, 0x01, 0x5E, 0x3F, 0x45, + 0x61, 0xC3, 0x32, 0x1D, 0x01, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, + 0x1D, 0x01, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D}; +} // namespace + +class FastPairGattServiceClientTest : public testing::Test { + public: + FastPairGattServiceClientTest() { + fast_pair_data_encryptor_ = std::make_unique(); + fast_pair_data_encryptor_->SetPublickey(kPublicKey); + } + + void SetUp() override { + env_.Start({.use_simulated_clock = true}); + BluetoothAdapter adapter; + BleV2Medium ble(adapter); + gatt_server_ = ble.StartGattServer(/*ServerGattConnectionCallback=*/{}); + } + + void TearDown() override { + key_based_characteristic_ = std::nullopt; + passkey_characteristic_ = std::nullopt; + initalized_failure_ = std::nullopt; + write_failure_ = std::nullopt; + gatt_client_.reset(); + gatt_server_->Stop(); + gatt_server_.reset(); + env_.Stop(); + } + + void InsertCorrectGattCharacteristics() { + key_based_characteristic_ = gatt_server_->CreateCharacteristic( + kFastPairServiceUuid, kKeyBasedCharacteristicUuidV2, permissions_, + properties_); + gatt_server_->UpdateCharacteristic( + key_based_characteristic_.value(), + ByteArray(std::string(kKeyBasedCharacteristicAdvertisementByte))); + + passkey_characteristic_ = gatt_server_->CreateCharacteristic( + kFastPairServiceUuid, kPasskeyCharacteristicUuidV2, permissions_, + properties_); + gatt_server_->UpdateCharacteristic( + passkey_characteristic_.value(), + ByteArray(std::string(kPasskeyharacteristicAdvertisementByte))); + } + + void InsertCharacteristicsWithWrongServiceId() { + key_based_characteristic_ = gatt_server_->CreateCharacteristic( + kWrongServiceId, kKeyBasedCharacteristicUuidV2, permissions_, + properties_); + gatt_server_->UpdateCharacteristic( + key_based_characteristic_.value(), + ByteArray(std::string(kKeyBasedCharacteristicAdvertisementByte))); + + passkey_characteristic_ = gatt_server_->CreateCharacteristic( + kWrongServiceId, kPasskeyCharacteristicUuidV2, permissions_, + properties_); + gatt_server_->UpdateCharacteristic( + passkey_characteristic_.value(), + ByteArray(std::string(kPasskeyharacteristicAdvertisementByte))); + } + + void InsertKeyBasedGattCharacteristicsWithEmptyValue() { + key_based_characteristic_ = gatt_server_->CreateCharacteristic( + kFastPairServiceUuid, kKeyBasedCharacteristicUuidV2, permissions_, + properties_); + gatt_server_->UpdateCharacteristic(key_based_characteristic_.value(), + ByteArray("")); + + passkey_characteristic_ = gatt_server_->CreateCharacteristic( + kFastPairServiceUuid, kPasskeyCharacteristicUuidV2, permissions_, + properties_); + gatt_server_->UpdateCharacteristic( + passkey_characteristic_.value(), + ByteArray(std::string(kPasskeyharacteristicAdvertisementByte))); + } + + void InsertPasskeyGattCharacteristicsWithEmptyValue() { + key_based_characteristic_ = gatt_server_->CreateCharacteristic( + kFastPairServiceUuid, kKeyBasedCharacteristicUuidV2, permissions_, + properties_); + gatt_server_->UpdateCharacteristic( + key_based_characteristic_.value(), + ByteArray(std::string(kKeyBasedCharacteristicAdvertisementByte))); + + passkey_characteristic_ = gatt_server_->CreateCharacteristic( + kFastPairServiceUuid, kPasskeyCharacteristicUuidV2, permissions_, + properties_); + gatt_server_->UpdateCharacteristic(passkey_characteristic_.value(), + ByteArray("")); + } + + void InitializeFastPairGattServiceClient() { + FastPairDevice device(kMetadataId, kProviderAddress, + Protocol::kFastPairInitialPairing); + gatt_client_ = FastPairGattServiceClientImpl::Factory::Create(device); + gatt_client_->InitializeGattConnection( + [this](std::optional failure) { + initalized_failure_ = failure; + }); + } + + void RemoveDiscoveredKeyBasedCharacteristic() { + env_.EraseBleV2MediumGattCharacteristicsForDiscovery( + key_based_characteristic_.value()); + } + + void RemoveDiscoveredPasskeyCharacteristic() { + env_.EraseBleV2MediumGattCharacteristicsForDiscovery( + passkey_characteristic_.value()); + } + + bool UnsubceibeKeyBasedCharacteristic() { + return env_.SetBleV2MediumGattCharacteristicSubscription( + key_based_characteristic_.value(), false, {}); + } + + bool UnsubceibePasskeyCharacteristic() { + return env_.SetBleV2MediumGattCharacteristicSubscription( + passkey_characteristic_.value(), false, {}); + } + + absl::optional GetInitializedCallbackResult() { + return initalized_failure_; + } + + void WriteTestCallback(absl::string_view response, + absl::optional failure) { + write_failure_ = failure; + } + + absl::optional GetWriteCallbackResult() { + return write_failure_; + } + + void WriteRequestToKeyBased() { + gatt_client_->WriteRequestAsync( + kMessageType, kFlags, kProviderAddress, /* Seeker Address*/ "", + *fast_pair_data_encryptor_, + [&](absl::string_view response, std::optional failure) { + WriteTestCallback(response, failure); + }); + } + + void WriteRequestToPasskey() { + gatt_client_->WritePasskeyAsync( + kSeekerPasskey, kPasskey, *fast_pair_data_encryptor_, + [this](absl::string_view response, std::optional failure) { + WriteTestCallback(response, failure); + }); + } + + absl::Status TriggerKeyBasedGattChanged() { + return gatt_server_->NotifyCharacteristicChanged( + key_based_characteristic_.value(), false, + ByteArray(std::string(kKeyBasedCharacteristicAdvertisementByte))); + } + + absl::Status TriggerPasskeyGattChanged() { + return gatt_server_->NotifyCharacteristicChanged( + passkey_characteristic_.value(), false, + ByteArray(std::string(kPasskeyharacteristicAdvertisementByte))); + } + + protected: + MediumEnvironment& env_{MediumEnvironment::Instance()}; + std::unique_ptr internal_gatt_client_; + std::unique_ptr gatt_client_; + std::unique_ptr gatt_server_; + std::unique_ptr fast_pair_data_encryptor_; + + private: + std::optional key_based_characteristic_; + std::optional passkey_characteristic_; + absl::optional initalized_failure_; + absl::optional write_failure_; + Property properties_ = Property::kWrite | Property::kNotify; + Permission permissions_ = Permission::kWrite; +}; + +TEST_F(FastPairGattServiceClientTest, SuccessInitializeGattConnection) { + InsertCorrectGattCharacteristics(); + InitializeFastPairGattServiceClient(); + EXPECT_EQ(GetInitializedCallbackResult(), std::nullopt); +} + +TEST_F(FastPairGattServiceClientTest, FailedDiscoverServiceAndCharacteristics) { + InsertCharacteristicsWithWrongServiceId(); + InitializeFastPairGattServiceClient(); + EXPECT_EQ(GetInitializedCallbackResult(), PairFailure::kCreateGattConnection); +} + +TEST_F(FastPairGattServiceClientTest, FailedGetKeyBasedCharacteristics) { + InsertKeyBasedGattCharacteristicsWithEmptyValue(); + InitializeFastPairGattServiceClient(); + EXPECT_EQ(GetInitializedCallbackResult(), + PairFailure::kKeyBasedPairingCharacteristicDiscovery); +} + +TEST_F(FastPairGattServiceClientTest, FailedToGetPasskeyCharacteristics) { + InsertPasskeyGattCharacteristicsWithEmptyValue(); + InitializeFastPairGattServiceClient(); + EXPECT_EQ(GetInitializedCallbackResult(), + PairFailure::kPasskeyCharacteristicDiscovery); +} + +TEST_F(FastPairGattServiceClientTest, SuccessfulWriteKeyBaseCharacteristics) { + InsertCorrectGattCharacteristics(); + InitializeFastPairGattServiceClient(); + WriteRequestToKeyBased(); + EXPECT_EQ(TriggerKeyBasedGattChanged(), absl::OkStatus()); + EXPECT_EQ(GetWriteCallbackResult(), absl::nullopt); +} + +TEST_F(FastPairGattServiceClientTest, SuccessfulWritePasskeyCharacteristics) { + InsertCorrectGattCharacteristics(); + InitializeFastPairGattServiceClient(); + WriteRequestToPasskey(); + EXPECT_EQ(TriggerPasskeyGattChanged(), absl::OkStatus()); + EXPECT_EQ(GetWriteCallbackResult(), absl::nullopt); +} + +TEST_F(FastPairGattServiceClientTest, FailedSubscribeKeybaseCharacteristic) { + InsertCorrectGattCharacteristics(); + InitializeFastPairGattServiceClient(); + RemoveDiscoveredKeyBasedCharacteristic(); + WriteRequestToKeyBased(); + EXPECT_EQ(GetWriteCallbackResult(), + PairFailure::kKeyBasedPairingCharacteristicSubscription); + WriteRequestToPasskey(); + EXPECT_EQ(TriggerPasskeyGattChanged(), absl::OkStatus()); + EXPECT_EQ(GetWriteCallbackResult(), absl::nullopt); +} + +TEST_F(FastPairGattServiceClientTest, FailedSubscribePasskeyCharacteristic) { + InsertCorrectGattCharacteristics(); + InitializeFastPairGattServiceClient(); + RemoveDiscoveredPasskeyCharacteristic(); + WriteRequestToKeyBased(); + EXPECT_EQ(TriggerKeyBasedGattChanged(), absl::OkStatus()); + EXPECT_EQ(GetWriteCallbackResult(), absl::nullopt); + WriteRequestToPasskey(); + EXPECT_EQ(GetWriteCallbackResult(), + PairFailure::kPasskeyCharacteristicSubscription); +} + +TEST_F(FastPairGattServiceClientTest, KeyBasedPairingResponseTimeout) { + InsertCorrectGattCharacteristics(); + InitializeFastPairGattServiceClient(); + CountDownLatch latch(1); + gatt_client_->WriteRequestAsync( + kMessageType, kFlags, kProviderAddress, kSeekerAddress, + *fast_pair_data_encryptor_, + [&](absl::string_view response, std::optional failure) { + WriteTestCallback(response, failure); + latch.CountDown(); + }); + SystemClock::Sleep(kGattOperationTimeout); + latch.Await(); + EXPECT_EQ(GetWriteCallbackResult(), + PairFailure::kKeyBasedPairingResponseTimeout); +} + +TEST_F(FastPairGattServiceClientTest, PasskeyResponseTimeout) { + InsertCorrectGattCharacteristics(); + InitializeFastPairGattServiceClient(); + CountDownLatch latch(1); + gatt_client_->WritePasskeyAsync( + kSeekerPasskey, kPasskey, *fast_pair_data_encryptor_, + [&](absl::string_view response, std::optional failure) { + WriteTestCallback(response, failure); + latch.CountDown(); + }); + SystemClock::Sleep(kGattOperationTimeout); + latch.Await(); + EXPECT_EQ(GetWriteCallbackResult(), PairFailure::kPasskeyResponseTimeout); +} +} // namespace fastpair +} // namespace nearby diff --git a/internal/platform/BUILD b/internal/platform/BUILD index 1128b4b7..af83cd26 100644 --- a/internal/platform/BUILD +++ b/internal/platform/BUILD @@ -188,6 +188,7 @@ cc_library( ], visibility = [ "//connections/implementation:__subpackages__", + "//fastpair:__subpackages__", "//internal/platform/implementation:__subpackages__", "//presence:__subpackages__", ],