From 6148e5f047c685d1c058620bbf125194b11e37dc Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 10 Jan 2025 12:17:19 -0800 Subject: [PATCH] pw_bluetooth_sapphire: Add PairingTokens to Peer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add PairingTokens to Peer to facilitate cross-transport pairing synchronization for CTKD. Bug: 388607971 Change-Id: I917b3ab35b86d78649342f4f0745865671bda3a8 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/259112 Lint: Lint 🤖 Reviewed-by: Jason Graffius Docs-Not-Needed: Ben Lawson Commit-Queue: Auto-Submit Pigweed-Auto-Submit: Ben Lawson --- pw_bluetooth_sapphire/host/gap/peer.cc | 65 +++++++++++++++++++ pw_bluetooth_sapphire/host/gap/peer_test.cc | 34 ++++++++++ .../internal/host/gap/peer.h | 48 ++++++++++++-- 3 files changed, 141 insertions(+), 6 deletions(-) diff --git a/pw_bluetooth_sapphire/host/gap/peer.cc b/pw_bluetooth_sapphire/host/gap/peer.cc index adaea0dd7..f08cf9f13 100644 --- a/pw_bluetooth_sapphire/host/gap/peer.cc +++ b/pw_bluetooth_sapphire/host/gap/peer.cc @@ -202,6 +202,39 @@ Peer::ConnectionToken Peer::LowEnergyData::RegisterConnection() { return ConnectionToken(std::move(unregister_cb)); } +Peer::PairingToken Peer::LowEnergyData::RegisterPairing() { + pairing_tokens_count_++; + auto unregister_cb = [self = peer_->GetWeakPtr(), this] { + if (!self.is_alive()) { + return; + } + pairing_tokens_count_--; + OnPairingMaybeComplete(); + }; + return PairingToken(std::move(unregister_cb)); +} + +bool Peer::LowEnergyData::is_pairing() const { + return pairing_tokens_count_ > 0; +} + +void Peer::LowEnergyData::add_pairing_completion_callback( + fit::callback&& callback) { + pairing_complete_callbacks_.emplace_back(std::move(callback)); + OnPairingMaybeComplete(); +} + +void Peer::LowEnergyData::OnPairingMaybeComplete() { + if (pairing_tokens_count_ > 0 || pairing_complete_callbacks_.empty()) { + return; + } + std::vector> callbacks; + std::swap(callbacks, pairing_complete_callbacks_); + for (auto& cb : callbacks) { + cb(); + } +} + void Peer::LowEnergyData::SetConnectionParameters( const hci_spec::LEConnectionParameters& params) { PW_DCHECK(peer_->connectable()); @@ -380,6 +413,38 @@ Peer::ConnectionToken Peer::BrEdrData::RegisterConnection() { }); } +Peer::PairingToken Peer::BrEdrData::RegisterPairing() { + PW_CHECK(!is_pairing()); + pairing_tokens_count_++; + auto unregister_cb = [self = peer_->GetWeakPtr(), this] { + if (!self.is_alive()) { + return; + } + pairing_tokens_count_--; + OnPairingMaybeComplete(); + }; + return PairingToken(std::move(unregister_cb)); +} + +bool Peer::BrEdrData::is_pairing() const { return pairing_tokens_count_ > 0; } + +void Peer::BrEdrData::add_pairing_completion_callback( + fit::callback&& callback) { + pairing_complete_callbacks_.emplace_back(std::move(callback)); + OnPairingMaybeComplete(); +} + +void Peer::BrEdrData::OnPairingMaybeComplete() { + if (pairing_tokens_count_ > 0 || pairing_complete_callbacks_.empty()) { + return; + } + std::vector> callbacks; + std::swap(callbacks, pairing_complete_callbacks_); + for (auto& cb : callbacks) { + cb(); + } +} + void Peer::BrEdrData::OnConnectionStateMaybeChanged(ConnectionState previous) { if (previous == connection_state()) { return; diff --git a/pw_bluetooth_sapphire/host/gap/peer_test.cc b/pw_bluetooth_sapphire/host/gap/peer_test.cc index 301c17508..bea10461e 100644 --- a/pw_bluetooth_sapphire/host/gap/peer_test.cc +++ b/pw_bluetooth_sapphire/host/gap/peer_test.cc @@ -1608,5 +1608,39 @@ TEST_F(PeerTest, OverwritingBrEdrBondWithSameSecuritySucceeds) { EXPECT_EQ(peer().MutBrEdr().link_key().value(), kSecureBrEdrKey2); } +TEST_F(PeerTest, LowEnergyPairingToken) { + EXPECT_FALSE(peer().MutLe().is_pairing()); + int count_0 = 0; + peer().MutLe().add_pairing_completion_callback([&count_0](){count_0++;}); + EXPECT_EQ(count_0, 1); + std::optional token = peer().MutLe().RegisterPairing(); + int count_1 = 0; + peer().MutLe().add_pairing_completion_callback([&count_1](){count_1++;}); + int count_2 = 0; + peer().MutLe().add_pairing_completion_callback([&count_2](){count_2++;}); + EXPECT_EQ(count_1, 0); + EXPECT_EQ(count_2, 0); + token.reset(); + EXPECT_EQ(count_1, 1); + EXPECT_EQ(count_2, 1); +} + +TEST_F(PeerTest, BrEdrPairingToken) { + EXPECT_FALSE(peer().MutBrEdr().is_pairing()); + int count_0 = 0; + peer().MutBrEdr().add_pairing_completion_callback([&count_0]{count_0++;}); + EXPECT_EQ(count_0, 1); + std::optional token = peer().MutBrEdr().RegisterPairing(); + int count_1 = 0; + peer().MutBrEdr().add_pairing_completion_callback([&count_1]{count_1++;}); + int count_2 = 0; + peer().MutBrEdr().add_pairing_completion_callback([&count_2]{count_2++;}); + EXPECT_EQ(count_1, 0); + EXPECT_EQ(count_2, 0); + token.reset(); + EXPECT_EQ(count_1, 1); + EXPECT_EQ(count_2, 1); +} + } // namespace } // namespace bt::gap diff --git a/pw_bluetooth_sapphire/host/gap/public/pw_bluetooth_sapphire/internal/host/gap/peer.h b/pw_bluetooth_sapphire/host/gap/public/pw_bluetooth_sapphire/internal/host/gap/peer.h index a4d0bc748..21c368266 100644 --- a/pw_bluetooth_sapphire/host/gap/public/pw_bluetooth_sapphire/internal/host/gap/peer.h +++ b/pw_bluetooth_sapphire/host/gap/public/pw_bluetooth_sapphire/internal/host/gap/peer.h @@ -144,9 +144,9 @@ class Peer final { // Attach peer as child node of |parent| with specified |name|. void AttachInspect(inspect::Node& parent, std::string name = "peer"); - enum class TokenType { kInitializing, kConnection }; + enum class TokenType { kInitializing, kConnection, kPairing }; template - class TokenWithCallback { + class [[nodiscard]] TokenWithCallback { public: explicit TokenWithCallback(fit::callback on_destruction) : on_destruction_(std::move(on_destruction)) {} @@ -170,6 +170,8 @@ class Peer final { // update the connection state. using ConnectionToken = TokenWithCallback; + using PairingToken = TokenWithCallback; + // Contains Peer data that apply only to the LE transport. class LowEnergyData final { public: @@ -275,13 +277,25 @@ class Peer final { // is returned that should be owned until the initialization is complete or // canceled. The connection state may be updated and listeners may be // notified. Multiple initializating connections may be registered. - [[nodiscard]] InitializingConnectionToken RegisterInitializingConnection(); + InitializingConnectionToken RegisterInitializingConnection(); // Register a connection that is in the connected state. A token is returned // that should be owned until the connection is disconnected. The connection // state may be updated and listeners may be notified. Multiple connections // may be registered. - [[nodiscard]] ConnectionToken RegisterConnection(); + ConnectionToken RegisterConnection(); + + // Register a pairing procedure. A token is returned that should be owned + // until the pairing procedure is completed. Only one pairing may be + // registered at a time. + PairingToken RegisterPairing(); + + // Returns true if there are outstanding PairingTokens. + bool is_pairing() const; + + // Add a callback that will be called when there are 0 outstanding + // PairingTokens (potentially immediately). + void add_pairing_completion_callback(fit::callback&& callback); // Modify the current or preferred connection parameters. // The device must be connectable. @@ -349,6 +363,8 @@ class Peer final { // Called when the connection state changes. void OnConnectionStateMaybeChanged(ConnectionState previous); + void OnPairingMaybeComplete(); + Peer* peer_; // weak inspect::Node node_; @@ -395,6 +411,9 @@ class Peer final { std::optional sleep_clock_accuracy_; + + uint8_t pairing_tokens_count_ = 0; + std::vector> pairing_complete_callbacks_; }; // Contains Peer data that apply only to the BR/EDR transport. @@ -475,13 +494,25 @@ class Peer final { // is returned that should be owned until the initialization is complete or // canceled. The connection state may be updated and listeners may be // notified. Multiple initializating connections may be registered. - [[nodiscard]] InitializingConnectionToken RegisterInitializingConnection(); + InitializingConnectionToken RegisterInitializingConnection(); // Register a connection that is in the connected state. A token is returned // that should be owned until the connection is disconnected. The connection // state may be updated and listeners may be notified. Only one connection // may be registered at a time (enforced by assertion). - [[nodiscard]] ConnectionToken RegisterConnection(); + ConnectionToken RegisterConnection(); + + // Register a pairing procedure. A token is returned that should be owned + // until the pairing procedure is completed. Only one pairing may be + // registered at a time. + PairingToken RegisterPairing(); + + // Returns true if there are outstanding PairingTokens. + bool is_pairing() const; + + // Add a callback that will be called when there are 0 outstanding + // PairingTokens (potentially immediately). + void add_pairing_completion_callback(fit::callback&& callback); // Stores a link key resulting from Secure Simple Pairing and makes this // peer "bonded." Marks the peer as non-temporary if necessary. All @@ -509,6 +540,8 @@ class Peer final { // Called when the connection state changes. void OnConnectionStateMaybeChanged(ConnectionState previous); + void OnPairingMaybeComplete(); + // All multi-byte fields must be in little-endian byte order as they were // received from the controller. void SetInquiryData( @@ -537,6 +570,9 @@ class Peer final { std::optional link_key_; StringInspectable> services_; + + uint8_t pairing_tokens_count_ = 0; + std::vector> pairing_complete_callbacks_; }; // Number that uniquely identifies this device with respect to the bt-host