diff --git a/fastpair/handshake/BUILD b/fastpair/handshake/BUILD index f099e941..526c8873 100644 --- a/fastpair/handshake/BUILD +++ b/fastpair/handshake/BUILD @@ -20,6 +20,7 @@ cc_library( "fast_pair_data_encryptor_impl.cc", "fast_pair_gatt_service_client_impl.cc", "fast_pair_handshake_impl.cc", + "fast_pair_handshake_lookup.cc", ], hdrs = [ "fast_pair_data_encryptor.h", @@ -28,6 +29,7 @@ cc_library( "fast_pair_gatt_service_client_impl.h", "fast_pair_handshake.h", "fast_pair_handshake_impl.h", + "fast_pair_handshake_lookup.h", ], visibility = [ "//:__subpackages__", @@ -47,9 +49,11 @@ cc_library( "//internal/platform:types", "//internal/platform:uuid", "@boringssl//:crypto", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/functional:bind_front", "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", ], ) @@ -142,3 +146,22 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "fast_pair_handshake_lookup_test", + size = "small", + srcs = [ + "fast_pair_handshake_lookup_test.cc", + ], + shard_count = 16, + deps = [ + ":handshake", + "//fastpair/common", + "//internal/platform:test_util", + "//internal/platform:types", + "//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/fast_pair_handshake_lookup.cc b/fastpair/handshake/fast_pair_handshake_lookup.cc new file mode 100644 index 00000000..cad7e193 --- /dev/null +++ b/fastpair/handshake/fast_pair_handshake_lookup.cc @@ -0,0 +1,89 @@ +// 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_handshake_lookup.h" + +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "fastpair/handshake/fast_pair_handshake_impl.h" + +namespace nearby { +namespace fastpair { + +FastPairHandshakeLookup* FastPairHandshakeLookup::instance_ = nullptr; +absl::Mutex FastPairHandshakeLookup::mutex_(absl::kConstInit); + +// static +FastPairHandshakeLookup* FastPairHandshakeLookup::GetInstance() { + absl::MutexLock lock(&mutex_); + if (!instance_) { + instance_ = new FastPairHandshakeLookup(); + } + return instance_; +} + +FastPairHandshake* FastPairHandshakeLookup::Get(FastPairDevice* device) { + absl::MutexLock lock(&mutex_); + auto it = fast_pair_handshakes_.find(device); + return it != fast_pair_handshakes_.end() ? it->second.get() : nullptr; +} + +FastPairHandshake* FastPairHandshakeLookup::Get(absl::string_view address) { + absl::MutexLock lock(&mutex_); + for (const auto& pair : fast_pair_handshakes_) { + if (pair.first->public_address() == address || + pair.first->GetBleAddress() == address) { + return pair.second.get(); + } + } + return nullptr; +} + +bool FastPairHandshakeLookup::Erase(FastPairDevice* device) { + absl::MutexLock lock(&mutex_); + return fast_pair_handshakes_.erase(device) == 1; +} + +bool FastPairHandshakeLookup::Erase(absl::string_view address) { + absl::MutexLock lock(&mutex_); + for (const auto& pair : fast_pair_handshakes_) { + if (pair.first->public_address() == address || + pair.first->GetBleAddress() == address) { + fast_pair_handshakes_.erase(pair.first); + return true; + } + } + return false; +} + +void FastPairHandshakeLookup::Clear() { + absl::MutexLock lock(&mutex_); + fast_pair_handshakes_.clear(); +} + +FastPairHandshake* FastPairHandshakeLookup::Create( + FastPairDevice& device, OnCompleteCallback on_complete) { + absl::MutexLock lock(&mutex_); + auto it = fast_pair_handshakes_.emplace( + &device, + std::make_unique(device, std::move(on_complete))); + DCHECK(it.second); + return it.first->second.get(); +} + +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/handshake/fast_pair_handshake_lookup.h b/fastpair/handshake/fast_pair_handshake_lookup.h new file mode 100644 index 00000000..4909fa17 --- /dev/null +++ b/fastpair/handshake/fast_pair_handshake_lookup.h @@ -0,0 +1,86 @@ +// 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_HANDSHAKE_LOOKUP_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_HANDSHAKE_LOOKUP_H_ + +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/functional/any_invocable.h" +#include "absl/synchronization/mutex.h" +#include "fastpair/common/fast_pair_device.h" +#include "fastpair/common/pair_failure.h" +#include "fastpair/handshake/fast_pair_handshake.h" + +namespace nearby { +namespace fastpair { + +// This Singletonclass creates, deletes and exposes FastPairHandshake instances. +class FastPairHandshakeLookup { + public: + using OnCompleteCallback = absl::AnyInvocable failure)>; + + // 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(); + + // Singletons should not be cloneable. + FastPairHandshakeLookup(const FastPairHandshakeLookup&) = delete; + // Singletons should not be assignable. + FastPairHandshakeLookup& operator=(const FastPairHandshakeLookup&) = delete; + + // Get an existing instance for |FastPairdevice|. + FastPairHandshake* Get(FastPairDevice* device); + + // Get an existing instance for |address|. + FastPairHandshake* Get(absl::string_view address); + + // Erases the FastPairHandshake instance for |FastPairdevice| if it exists. + bool Erase(FastPairDevice* device); + + // Erases the FastPairHandshake instance for |FastPairdevice| if it exists. + bool Erase(absl::string_view address); + + // Deletes all existing FastPairHandshake instances. + void Clear(); + + // Creates and returns a new instance for |FastPairdevice| if no instance + // already exists. + // Returns the existing instance if there is one. + FastPairHandshake* Create(FastPairDevice& device, + OnCompleteCallback on_complete); + + protected: + // Constructor/destructor of singleton object should not be public + // for which the destructor will never be called. + // and constructor will be invoked once from GetInstance() static method. + FastPairHandshakeLookup() = default; + ~FastPairHandshakeLookup() = default; + + private: + static absl::Mutex mutex_; + static FastPairHandshakeLookup* instance_ ABSL_GUARDED_BY(mutex_); + + absl::flat_hash_map> + fast_pair_handshakes_ ABSL_GUARDED_BY(mutex_); +}; +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_HANDSHAKE_LOOKUP_H_ diff --git a/fastpair/handshake/fast_pair_handshake_lookup_test.cc b/fastpair/handshake/fast_pair_handshake_lookup_test.cc new file mode 100644 index 00000000..d30e88ab --- /dev/null +++ b/fastpair/handshake/fast_pair_handshake_lookup_test.cc @@ -0,0 +1,128 @@ +// 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_handshake_lookup.h" + +#include +#include + +#include "gmock/gmock.h" +#include "protobuf-matchers/protocol-buffer-matchers.h" +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "fastpair/common/fast_pair_device.h" +#include "fastpair/common/pair_failure.h" +#include "fastpair/common/protocol.h" +#include "internal/platform/count_down_latch.h" +#include "internal/platform/medium_environment.h" + +namespace nearby { +namespace fastpair { +namespace { +constexpr absl::string_view kValidModelId("718c17"); +constexpr absl::string_view kBLEAddress("ble_address"); +constexpr absl::string_view kPubliceAddress("public_address"); +class FastPairHandshakeLookupTest : public ::testing::Test { + public: + FastPairHandshakeLookupTest() { + device_ = new FastPairDevice(kValidModelId, kBLEAddress, + Protocol::kFastPairInitialPairing); + device_->set_public_address(kPubliceAddress); + } + + void CreateFastPairHandshkeInstanceForDevice(FastPairDevice& device) { + CountDownLatch latch(1); + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Create( + device, + [&](FastPairDevice& cb_device, std::optional failure) { + EXPECT_EQ(&device, &cb_device); + EXPECT_EQ(failure, PairFailure::kCreateGattConnection); + latch.CountDown(); + })); + latch.Await(); + } + + FastPairDevice* device_ = nullptr; +}; + +TEST_F(FastPairHandshakeLookupTest, CreateFastPairHandshkeInstanceForDevice) { + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Get(device_)); + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Get(kBLEAddress)); + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Get(kPubliceAddress)); + + CreateFastPairHandshkeInstanceForDevice(*device_); + + // GetFastPairHandshakeWithDevicePtr + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Get(device_)); + // GetFastPairHandshakeWithBLEAddress + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Get(kBLEAddress)); + // GetFastPairHandshakeWithPublicAddress + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Get(kPubliceAddress)); + // GetFastPairHandshakeWithDevicePtr + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Get(device_)); + // GetFastPairHandshakeWithWrongAddress + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Get("")); +} + +TEST_F(FastPairHandshakeLookupTest, EraseFastPairHandshakeWithDevicePtr) { + CreateFastPairHandshkeInstanceForDevice(*device_); + + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Get(device_)); + + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Erase(device_)); + // Already Erased + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Get(device_)); + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Erase(kBLEAddress)); + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Erase(kPubliceAddress)); +} + +TEST_F(FastPairHandshakeLookupTest, EraseFastPairHandshakeWithBLEAddress) { + CreateFastPairHandshkeInstanceForDevice(*device_); + + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Get(device_)); + // Erase Wrong Address + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Erase("")); + + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Erase(kBLEAddress)); + // Already Erased + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Get(device_)); + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Erase(device_)); + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Erase(kPubliceAddress)); +} + +TEST_F(FastPairHandshakeLookupTest, EraseFastPairHandshakeWithPublicAddress) { + CreateFastPairHandshkeInstanceForDevice(*device_); + + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Get(device_)); + + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Erase(kPubliceAddress)); + // Already Erased + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Get(device_)); + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Erase(device_)); + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Erase(kBLEAddress)); +} + +TEST_F(FastPairHandshakeLookupTest, ClearAllFastPairHandshakeInstances) { + CreateFastPairHandshkeInstanceForDevice(*device_); + + EXPECT_TRUE(FastPairHandshakeLookup::GetInstance()->Get(device_)); + + FastPairHandshakeLookup::GetInstance()->Clear(); + + EXPECT_FALSE(FastPairHandshakeLookup::GetInstance()->Get(device_)); +} + +} // namespace +} // namespace fastpair +} // namespace nearby