From 91d980838eb0f8cf505acc1bc4205d7babe4ef12 Mon Sep 17 00:00:00 2001 From: Austin Foxley Date: Wed, 27 Nov 2024 07:43:00 +0000 Subject: [PATCH] pw_bluetooth_proxy: Add automatic LE connection tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I71de373c4acc59e60fbfeb04530800b3bbb54636 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/251053 Lint: Lint 🤖 Docs-Not-Needed: Ben Lawson Reviewed-by: Ben Lawson Pigweed-Auto-Submit: Austin Foxley Commit-Queue: Auto-Submit --- .../public/pw_bluetooth/hci_common.emb | 30 +++++++++ .../public/pw_bluetooth/hci_events.emb | 44 +++++++++++-- pw_bluetooth_proxy/acl_data_channel.cc | 65 ++++++++++++++++++- pw_bluetooth_proxy/proxy_host.cc | 39 +++++++++++ pw_bluetooth_proxy/proxy_host_test.cc | 41 ++++++++++++ .../internal/acl_data_channel.h | 12 ++++ .../public/pw_bluetooth_proxy/proxy_host.h | 3 + 7 files changed, 227 insertions(+), 7 deletions(-) diff --git a/pw_bluetooth/public/pw_bluetooth/hci_common.emb b/pw_bluetooth/public/pw_bluetooth/hci_common.emb index 4ed1d3193..46abf119a 100644 --- a/pw_bluetooth/public/pw_bluetooth/hci_common.emb +++ b/pw_bluetooth/public/pw_bluetooth/hci_common.emb @@ -600,6 +600,36 @@ enum EventCode: VENDOR_DEBUG = 0xFF +enum LeSubEventCode: + -- HCI LE Meta event SubEvent codes + -- Core Spec v6.0 Vol 4, Part E, Section 7.7.65 + [maximum_bits: 8] + CONNECTION_COMPLETE = 0x01 + ADVERTISING_REPORT = 0x02 + CONNECTION_UPDATE_COMPLETE = 0x03 + READ_REMOTE_FEATURES_COMPLETE = 0x04 + LONG_TERM_KEY_REQUEST = 0x05 + REMOTE_CONNECTION_PARAMETER_REQUEST = 0x06 + DATA_LENGTH_CHANGE = 0x07 + READ_LOCAL_P256_PUBLIC_KEY_COMPLETE = 0x08 + GENERATE_DH_KEY_COMPLETE = 0x09 + ENHANCED_CONNECTION_COMPLETE_V1 = 0x0A + DIRECTED_ADVERTISING_REPORT = 0x0B + PHY_UPDATE_COMPLETE = 0x0C + EXTENDED_ADVERTISING_REPORT = 0x0D + PERIODIC_ADVERTISING_SYNC_ESTABLISHED = 0x0E + PERIODIC_ADVERTISING_REPORT = 0x0F + PERIODIC_ADVERTISING_SYNC_LOST = 0x10 + SCAN_TIMEOUT = 0x11 + ADVERTISING_SET_TERMINATED = 0x12 + SCAN_REQUEST_RECEIVED = 0x13 + CHANNEL_SELECTION_ALGORITHM = 0x14 + CIS_ESTABLISHED = 0x19 + REQUEST_PEER_SCA_COMPLETE = 0x1F + CIS_REQUEST = 0x1A + ENHANCED_CONNECTION_COMPLETE_V2 = 0x29 + + enum MajorDeviceClass: [maximum_bits: 5] MISCELLANEOUS = 0x00 diff --git a/pw_bluetooth/public/pw_bluetooth/hci_events.emb b/pw_bluetooth/public/pw_bluetooth/hci_events.emb index a95c05e12..a5fe91403 100644 --- a/pw_bluetooth/public/pw_bluetooth/hci_events.emb +++ b/pw_bluetooth/public/pw_bluetooth/hci_events.emb @@ -1552,7 +1552,7 @@ struct ExtendedInquiryResultEvent: [requires: -127 <= this <= 20] $next [+240] UInt:8[240] extended_inquiry_response - -- Extended inquiey response data as defined in Vol 3, Part C, Sec 8 + -- Extended inquiry response data as defined in Vol 3, Part C, Sec 8 struct EncryptionKeyRefreshCompleteEvent: @@ -1669,8 +1669,9 @@ struct UserPasskeyNotificationEvent: struct LEMetaEvent: -- 7.7.65 LE Meta event let hdr_size = hci.EventHeader.$size_in_bytes - 0 [+hdr_size] hci.EventHeader header - $next [+1] UInt subevent_code + 0 [+hdr_size] hci.EventHeader header + hdr_size [+1] UInt subevent_code + hdr_size [+1] hci.LeSubEventCode subevent_code_enum -- The event code for the LE subevent. @@ -1840,10 +1841,41 @@ struct LEEnhancedConnectionCompleteSubeventV1: $next [+1] LEClockAccuracy central_clock_accuracy -- Only valid for a peripheral. On a central, this parameter shall be set to 0x00. -# 7.7.65.10 LE Enhanced Connection Complete event -# HCI_LE_Enhanced_Connection_Complete -# TODO: b/265052417 - Definition needs to be added +struct LEEnhancedConnectionCompleteSubeventV2: + -- 7.7.65.10 LE Enhanced Connection Complete event + -- HCI_LE_Enhanced_Connection_Complete + 0 [+LEMetaEvent.$size_in_bytes] LEMetaEvent le_meta_event + $next [+1] hci.StatusCode status + $next [+2] UInt connection_handle + [requires: 0x0000 <= this <= 0x0EFF] + + $next [+1] hci.ConnectionRole role + $next [+1] hci.LEAddressType peer_address_type + $next [+hci.BdAddr.$size_in_bytes] hci.BdAddr peer_address + $next [+hci.BdAddr.$size_in_bytes] hci.BdAddr local_resolvable_private_address + $next [+hci.BdAddr.$size_in_bytes] hci.BdAddr peer_resolvable_private_address + $next [+2] UInt connection_interval + -- Time: N * 1.25 ms + -- Range: 7.5 ms to 4 s + [requires: 0x0006 <= this <= 0x0C80] + + $next [+2] UInt peripheral_latency + [requires: 0x0000 <= this <= 0x01F3] + + $next [+2] UInt supervision_timeout + -- Time: N * 10 ms + -- Range: 100 ms to 32 s + [requires: 0x000A <= this <= 0x0C80] + + $next [+1] LEClockAccuracy central_clock_accuracy + -- Only valid for a peripheral. On a central, this parameter shall be set to 0x00. + + $next [+1] UInt advertising_handle + -- Used to identify an advertising set + + $next [+2] UInt sync_handle + -- Used to identify the periodic advertising train. # 7.7.65.11 LE Directed Advertising Report event # HCI_LE_Directed_Advertising_Report diff --git a/pw_bluetooth_proxy/acl_data_channel.cc b/pw_bluetooth_proxy/acl_data_channel.cc index b70cb41ba..d33aeed7c 100644 --- a/pw_bluetooth_proxy/acl_data_channel.cc +++ b/pw_bluetooth_proxy/acl_data_channel.cc @@ -211,7 +211,6 @@ void AclDataChannel::HandleNumberOfCompletedPacketsEvent( } } -// Create new tracked connection and pass on to host. void AclDataChannel::HandleConnectionCompleteEvent( H4PacketWithHci&& h4_packet) { pw::span hci_buffer = h4_packet.GetHciSpan(); @@ -241,6 +240,70 @@ void AclDataChannel::HandleConnectionCompleteEvent( hci_transport_.SendToHost(std::move(h4_packet)); } +void AclDataChannel::HandleLeConnectionCompleteEvent( + uint16_t connection_handle, emboss::StatusCode status) { + if (status != emboss::StatusCode::SUCCESS) { + return; + } + + if (CreateAclConnection(connection_handle, AclTransportType::kLe) == + Status::ResourceExhausted()) { + PW_LOG_ERROR( + "Could not track connection like requested. Max connections " + "reached."); + } +} + +void AclDataChannel::HandleLeConnectionCompleteEvent( + H4PacketWithHci&& h4_packet) { + pw::span hci_buffer = h4_packet.GetHciSpan(); + Result event = + MakeEmbossView(hci_buffer); + if (!event.ok()) { + hci_transport_.SendToHost(std::move(h4_packet)); + return; + } + + HandleLeConnectionCompleteEvent(event->connection_handle().Read(), + event->status().Read()); + + hci_transport_.SendToHost(std::move(h4_packet)); +} + +void AclDataChannel::HandleLeEnhancedConnectionCompleteV1Event( + H4PacketWithHci&& h4_packet) { + pw::span hci_buffer = h4_packet.GetHciSpan(); + Result event = + MakeEmbossView( + hci_buffer); + if (!event.ok()) { + hci_transport_.SendToHost(std::move(h4_packet)); + return; + } + + HandleLeConnectionCompleteEvent(event->connection_handle().Read(), + event->status().Read()); + + hci_transport_.SendToHost(std::move(h4_packet)); +} + +void AclDataChannel::HandleLeEnhancedConnectionCompleteV2Event( + H4PacketWithHci&& h4_packet) { + pw::span hci_buffer = h4_packet.GetHciSpan(); + Result event = + MakeEmbossView( + hci_buffer); + if (!event.ok()) { + hci_transport_.SendToHost(std::move(h4_packet)); + return; + } + + HandleLeConnectionCompleteEvent(event->connection_handle().Read(), + event->status().Read()); + + hci_transport_.SendToHost(std::move(h4_packet)); +} + void AclDataChannel::HandleDisconnectionCompleteEvent( H4PacketWithHci&& h4_packet) { Result dc_event = diff --git a/pw_bluetooth_proxy/proxy_host.cc b/pw_bluetooth_proxy/proxy_host.cc index 8c75b015e..7a7698bc8 100644 --- a/pw_bluetooth_proxy/proxy_host.cc +++ b/pw_bluetooth_proxy/proxy_host.cc @@ -149,6 +149,10 @@ void ProxyHost::HandleEventFromController(H4PacketWithHci&& h4_packet) { acl_data_channel_.HandleConnectionCompleteEvent(std::move(h4_packet)); break; } + case emboss::EventCode::LE_META_EVENT: { + HandleLeMetaEvent(std::move(h4_packet)); + break; + } default: { hci_transport_.SendToHost(std::move(h4_packet)); return; @@ -210,6 +214,41 @@ void ProxyHost::HandleAclFromController(H4PacketWithHci&& h4_packet) { } } +void ProxyHost::HandleLeMetaEvent(H4PacketWithHci&& h4_packet) { + pw::span hci_buffer = h4_packet.GetHciSpan(); + Result le_meta_event_view = + MakeEmbossView(hci_buffer); + if (!le_meta_event_view.ok()) { + PW_LOG_ERROR( + "Buffer is too small for LE_META_EVENT event. So will not process."); + hci_transport_.SendToHost(std::move(h4_packet)); + return; + } + + PW_MODIFY_DIAGNOSTICS_PUSH(); + PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum"); + switch (le_meta_event_view->subevent_code_enum().Read()) { + case emboss::LeSubEventCode::CONNECTION_COMPLETE: { + acl_data_channel_.HandleLeConnectionCompleteEvent(std::move(h4_packet)); + return; + } + case emboss::LeSubEventCode::ENHANCED_CONNECTION_COMPLETE_V1: { + acl_data_channel_.HandleLeEnhancedConnectionCompleteV1Event( + std::move(h4_packet)); + return; + } + case emboss::LeSubEventCode::ENHANCED_CONNECTION_COMPLETE_V2: { + acl_data_channel_.HandleLeEnhancedConnectionCompleteV2Event( + std::move(h4_packet)); + return; + } + default: + break; + } + PW_MODIFY_DIAGNOSTICS_POP(); + hci_transport_.SendToHost(std::move(h4_packet)); +} + void ProxyHost::HandleCommandCompleteEvent(H4PacketWithHci&& h4_packet) { pw::span hci_buffer = h4_packet.GetHciSpan(); Result command_complete_event = diff --git a/pw_bluetooth_proxy/proxy_host_test.cc b/pw_bluetooth_proxy/proxy_host_test.cc index b683e237c..109f1f3a9 100644 --- a/pw_bluetooth_proxy/proxy_host_test.cc +++ b/pw_bluetooth_proxy/proxy_host_test.cc @@ -171,6 +171,28 @@ Status SendConnectionCompleteEvent(ProxyHost& proxy, return OkStatus(); } +// Send a LE_Connection_Complete event to `proxy` indicating the provided +// `handle` has disconnected. +Status SendLeConnectionCompleteEvent(ProxyHost& proxy, + uint16_t handle, + emboss::StatusCode status) { + std::array + hci_arr_dc{}; + H4PacketWithHci dc_event{emboss::H4PacketType::EVENT, hci_arr_dc}; + PW_TRY_ASSIGN(auto view, + MakeEmbossWriter( + dc_event.GetHciSpan())); + view.le_meta_event().header().event_code_enum().Write( + emboss::EventCode::LE_META_EVENT); + view.le_meta_event().subevent_code_enum().Write( + emboss::LeSubEventCode::CONNECTION_COMPLETE); + view.status().Write(status); + view.connection_handle().Write(handle); + proxy.HandleH4HciFromController(std::move(dc_event)); + return OkStatus(); +} + // Send a Disconnection_Complete event to `proxy` indicating the provided // `handle` has disconnected. Status SendDisconnectionCompleteEvent(ProxyHost& proxy, @@ -4700,6 +4722,25 @@ TEST(ProxyHostConnectionEventTest, EXPECT_EQ(host_called, 1U); } +TEST(ProxyHostConnectionEventTest, LeConnectionCompletePassthroughOk) { + size_t host_called = 0; + pw::Function send_to_controller_fn( + []([[maybe_unused]] H4PacketWithH4&& packet) {}); + + pw::Function send_to_host_fn( + [&host_called]([[maybe_unused]] H4PacketWithHci&& packet) { + ++host_called; + }); + + ProxyHost proxy = ProxyHost(std::move(send_to_host_fn), + std::move(send_to_controller_fn), + /*le_acl_credits_to_reserve=*/0); + + PW_TEST_EXPECT_OK( + SendLeConnectionCompleteEvent(proxy, 1, emboss::StatusCode::SUCCESS)); + EXPECT_EQ(host_called, 1U); +} + // TODO: https://pwbug.dev/360929142 - Add many more tests exercising queueing // credit-based control flow. diff --git a/pw_bluetooth_proxy/public/pw_bluetooth_proxy/internal/acl_data_channel.h b/pw_bluetooth_proxy/public/pw_bluetooth_proxy/internal/acl_data_channel.h index ac097d663..b68ecc9e8 100644 --- a/pw_bluetooth_proxy/public/pw_bluetooth_proxy/internal/acl_data_channel.h +++ b/pw_bluetooth_proxy/public/pw_bluetooth_proxy/internal/acl_data_channel.h @@ -83,6 +83,15 @@ class AclDataChannel { // Create new tracked connection and pass on to host. void HandleConnectionCompleteEvent(H4PacketWithHci&& h4_packet); + // Create new tracked connection and pass on to host. + void HandleLeConnectionCompleteEvent(H4PacketWithHci&& h4_packet); + + // Create new tracked connection and pass on to host. + void HandleLeEnhancedConnectionCompleteV1Event(H4PacketWithHci&& h4_packet); + + // Create new tracked connection and pass on to host. + void HandleLeEnhancedConnectionCompleteV2Event(H4PacketWithHci&& h4_packet); + /// Indicates whether the proxy has the capability of sending ACL packets. /// Note that this indicates intention, so it can be true even if the proxy /// has not yet or has been unable to reserve credits from the host. @@ -258,6 +267,9 @@ class AclDataChannel { const Credits& LookupCredits(AclTransportType transport) const PW_EXCLUSIVE_LOCKS_REQUIRED(credit_allocation_mutex_); + void HandleLeConnectionCompleteEvent(uint16_t connection_handle, + emboss::StatusCode status); + // Maximum number of simultaneous credit-allocated LE connections supported. // TODO: https://pwbug.dev/349700888 - Make size configurable. static constexpr size_t kMaxConnections = 10; diff --git a/pw_bluetooth_proxy/public/pw_bluetooth_proxy/proxy_host.h b/pw_bluetooth_proxy/public/pw_bluetooth_proxy/proxy_host.h index 9ff32254e..474ad63ed 100644 --- a/pw_bluetooth_proxy/public/pw_bluetooth_proxy/proxy_host.h +++ b/pw_bluetooth_proxy/public/pw_bluetooth_proxy/proxy_host.h @@ -245,6 +245,9 @@ class ProxyHost { // Handle HCI ACL data packet from the controller. void HandleAclFromController(H4PacketWithHci&& h4_packet); + // Process an LE_META_EVENT + void HandleLeMetaEvent(H4PacketWithHci&& h4_packet); + // Process a Command_Complete event. void HandleCommandCompleteEvent(H4PacketWithHci&& h4_packet);