| // Copyright 2024 The Pigweed Authors |
| // |
| // 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 "pw_bluetooth_proxy/proxy_host.h" |
| |
| #include <mutex> |
| |
| #include "pw_bluetooth/emboss_util.h" |
| #include "pw_bluetooth/hci_commands.emb.h" |
| #include "pw_bluetooth/hci_common.emb.h" |
| #include "pw_bluetooth/hci_h4.emb.h" |
| #include "pw_bluetooth_proxy/h4_packet.h" |
| #include "pw_bluetooth_proxy/internal/logical_transport.h" |
| #include "pw_bluetooth_proxy/l2cap_channel_common.h" |
| #include "pw_log/log.h" |
| |
| namespace pw::bluetooth::proxy { |
| |
| ProxyHost::ProxyHost( |
| pw::Function<void(H4PacketWithHci&& packet)>&& send_to_host_fn, |
| pw::Function<void(H4PacketWithH4&& packet)>&& send_to_controller_fn, |
| uint16_t le_acl_credits_to_reserve, |
| uint16_t br_edr_acl_credits_to_reserve, |
| pw::Allocator* allocator) |
| : impl_(*this), |
| hci_transport_(std::move(send_to_host_fn), |
| std::move(send_to_controller_fn)), |
| acl_data_channel_(hci_transport_, |
| le_acl_credits_to_reserve, |
| br_edr_acl_credits_to_reserve, |
| *allocator, |
| /*on_tx_credits_fn=*/[this]() { OnAclTxCredits(); }), |
| l2cap_channel_manager_(acl_data_channel_, *allocator) { |
| PW_LOG_INFO( |
| "btproxy: ProxyHost ctor - le_acl_credits_to_reserve: %u, " |
| "br_edr_acl_credits_to_reserve: %u", |
| le_acl_credits_to_reserve, |
| br_edr_acl_credits_to_reserve); |
| } |
| |
| ProxyHost::ProxyHost( |
| pw::Function<void(H4PacketWithHci&& packet)>&& send_to_host_fn, |
| pw::Function<void(H4PacketWithH4&& packet)>&& send_to_controller_fn, |
| pw::Allocator& allocator) |
| : impl_(*this), |
| hci_transport_(std::move(send_to_host_fn), |
| std::move(send_to_controller_fn)), |
| acl_data_channel_(hci_transport_, |
| allocator, |
| /*on_tx_credits_fn=*/[this]() { OnAclTxCredits(); }), |
| l2cap_channel_manager_(acl_data_channel_, allocator) { |
| PW_LOG_INFO("btproxy: ProxyHost ctor - Dynamic Credit Sharing"); |
| } |
| |
| ProxyHost::~ProxyHost() { |
| PW_LOG_INFO("btproxy: ProxyHost dtor"); |
| acl_data_channel_.Reset(); |
| l2cap_channel_manager_.DeregisterAndCloseChannels( |
| L2capChannelEvent::kChannelClosedByOther); |
| } |
| |
| void ProxyHost::SetEventBlocked(emboss::EventCode event_code, bool blocked) { |
| std::lock_guard lock(filter_mutex_); |
| blocked_events_.set(static_cast<uint8_t>(event_code), blocked); |
| } |
| |
| void ProxyHost::SetLeSubeventBlocked(emboss::LeSubEventCode subevent_code, |
| bool blocked) { |
| std::lock_guard lock(filter_mutex_); |
| blocked_le_subevents_.set(static_cast<uint8_t>(subevent_code), blocked); |
| } |
| |
| void ProxyHost::ResetFilters() { |
| std::lock_guard lock(filter_mutex_); |
| blocked_events_.reset(); |
| blocked_le_subevents_.reset(); |
| } |
| |
| bool ProxyHost::IsEventBlocked(emboss::EventCode event_code) const { |
| std::lock_guard lock(filter_mutex_); |
| return blocked_events_.test(static_cast<uint8_t>(event_code)); |
| } |
| |
| bool ProxyHost::IsLeSubeventBlocked( |
| emboss::LeSubEventCode subevent_code) const { |
| std::lock_guard lock(filter_mutex_); |
| return blocked_le_subevents_.test(static_cast<uint8_t>(subevent_code)); |
| } |
| |
| void ProxyHost::DoReset() { |
| // Reset AclDataChannel first, so that send credits are reset to 0 until |
| // reinitialized by controller event. This way, new channels can still be |
| // registered, but they cannot erroneously use invalidated send credits. |
| acl_data_channel_.Reset(); |
| l2cap_channel_manager_.DeregisterAndCloseChannels(L2capChannelEvent::kReset); |
| } |
| |
| void ProxyHost::DoHandleH4HciFromHost(H4PacketWithH4&& h4_packet) { |
| switch (h4_packet.GetH4Type()) { |
| case emboss::H4PacketType::COMMAND: |
| HandleCommandFromHost(std::move(h4_packet)); |
| return; |
| case emboss::H4PacketType::ACL_DATA: |
| HandleAclFromHost(std::move(h4_packet)); |
| return; |
| case emboss::H4PacketType::EVENT: |
| case emboss::H4PacketType::UNKNOWN: |
| case emboss::H4PacketType::SYNC_DATA: |
| case emboss::H4PacketType::ISO_DATA: |
| default: |
| hci_transport_.SendToController(std::move(h4_packet)); |
| return; |
| } |
| } |
| |
| void ProxyHost::DoHandleH4HciFromController(H4PacketWithHci&& h4_packet) { |
| switch (h4_packet.GetH4Type()) { |
| case emboss::H4PacketType::EVENT: |
| HandleEventFromController(std::move(h4_packet)); |
| return; |
| case emboss::H4PacketType::ACL_DATA: |
| HandleAclFromController(std::move(h4_packet)); |
| return; |
| case emboss::H4PacketType::UNKNOWN: |
| case emboss::H4PacketType::COMMAND: |
| case emboss::H4PacketType::SYNC_DATA: |
| case emboss::H4PacketType::ISO_DATA: |
| default: |
| hci_transport_.SendToHost(std::move(h4_packet)); |
| return; |
| } |
| } |
| |
| void ProxyHost::HandleEventFromController(H4PacketWithHci&& h4_packet) { |
| pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan(); |
| Result<emboss::EventHeaderView> event = |
| MakeEmbossView<emboss::EventHeaderView>(hci_buffer); |
| if (!event.ok()) { |
| PW_LOG_ERROR( |
| "Buffer is too small for EventHeader. So will pass on to host without " |
| "processing."); |
| hci_transport_.SendToHost(std::move(h4_packet)); |
| return; |
| } |
| |
| bool should_send_to_host = true; |
| bool should_deliver_pending_events = true; |
| emboss::EventCode event_code = event->event_code().Read(); |
| |
| PW_MODIFY_DIAGNOSTICS_PUSH(); |
| PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum"); |
| switch (event_code) { |
| case emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS: { |
| should_send_to_host = |
| acl_data_channel_.HandleNumberOfCompletedPacketsEvent(h4_packet); |
| break; |
| } |
| case emboss::EventCode::DISCONNECTION_COMPLETE: { |
| Result<emboss::DisconnectionCompleteEventView> dc_event = |
| MakeEmbossView<emboss::DisconnectionCompleteEventView>( |
| h4_packet.GetHciSpan()); |
| if (dc_event.ok() && |
| dc_event->status().Read() == emboss::StatusCode::SUCCESS) { |
| uint16_t conn_handle = dc_event->connection_handle().Read(); |
| l2cap_channel_manager_.HandleAclDisconnectionComplete(conn_handle); |
| acl_data_channel_.ProcessDisconnectionCompleteEvent( |
| conn_handle, dc_event->reason().Read()); |
| } |
| break; |
| } |
| case emboss::EventCode::COMMAND_COMPLETE: { |
| should_send_to_host = HandleCommandCompleteEvent(h4_packet); |
| break; |
| } |
| case emboss::EventCode::CONNECTION_COMPLETE: { |
| pw::span<uint8_t> hci_span = h4_packet.GetHciSpan(); |
| Result<emboss::ConnectionCompleteEventView> connection_complete_event = |
| MakeEmbossView<emboss::ConnectionCompleteEventView>(hci_span); |
| if (connection_complete_event.ok() && |
| connection_complete_event->status().Read() == |
| emboss::StatusCode::SUCCESS) { |
| OnConnectionCompleteSuccess( |
| connection_complete_event->connection_handle().Read(), |
| AclTransportType::kBrEdr); |
| } |
| break; |
| } |
| case emboss::EventCode::LE_META_EVENT: { |
| should_send_to_host = HandleLeMetaEvent(h4_packet); |
| break; |
| } |
| default: { |
| should_deliver_pending_events = false; |
| break; |
| } |
| } |
| PW_MODIFY_DIAGNOSTICS_POP(); |
| |
| if (should_send_to_host && !IsEventBlocked(event_code)) { |
| hci_transport_.SendToHost(std::move(h4_packet)); |
| } |
| |
| if (should_deliver_pending_events) { |
| l2cap_channel_manager_.DeliverPendingEvents(); |
| } |
| } |
| |
| void ProxyHost::HandleAclFromController(H4PacketWithHci&& h4_packet) { |
| acl_data_channel_.HandleAclFromController(std::move(h4_packet)); |
| // It's possible for a channel handling rx traffic to have queued tx traffic |
| // or events. |
| l2cap_channel_manager_.DrainChannelQueuesIfNewTx(); |
| l2cap_channel_manager_.DeliverPendingEvents(); |
| } |
| |
| bool ProxyHost::HandleLeMetaEvent(H4PacketWithHci& h4_packet) { |
| pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan(); |
| Result<emboss::LEMetaEventView> le_meta_event_view = |
| MakeEmbossView<emboss::LEMetaEventView>(hci_buffer); |
| if (!le_meta_event_view.ok()) { |
| PW_LOG_ERROR( |
| "Buffer is too small for LE_META_EVENT event. So will not process."); |
| return true; |
| } |
| |
| PW_MODIFY_DIAGNOSTICS_PUSH(); |
| PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum"); |
| switch (le_meta_event_view->subevent_code_enum().Read()) { |
| case emboss::LeSubEventCode::CONNECTION_COMPLETE: { |
| Result<emboss::LEConnectionCompleteSubeventView> event = |
| MakeEmbossView<emboss::LEConnectionCompleteSubeventView>(hci_buffer); |
| if (event.ok() && event->status().Read() == emboss::StatusCode::SUCCESS) { |
| OnConnectionCompleteSuccess(event->connection_handle().Read(), |
| AclTransportType::kLe); |
| } |
| break; |
| } |
| case emboss::LeSubEventCode::ENHANCED_CONNECTION_COMPLETE_V1: { |
| Result<emboss::LEEnhancedConnectionCompleteSubeventV1View> event = |
| MakeEmbossView<emboss::LEEnhancedConnectionCompleteSubeventV1View>( |
| hci_buffer); |
| if (event.ok() && event->status().Read() == emboss::StatusCode::SUCCESS) { |
| OnConnectionCompleteSuccess(event->connection_handle().Read(), |
| AclTransportType::kLe); |
| } |
| break; |
| } |
| case emboss::LeSubEventCode::ENHANCED_CONNECTION_COMPLETE_V2: { |
| Result<emboss::LEEnhancedConnectionCompleteSubeventV2View> event = |
| MakeEmbossView<emboss::LEEnhancedConnectionCompleteSubeventV2View>( |
| hci_buffer); |
| if (event.ok() && event->status().Read() == emboss::StatusCode::SUCCESS) { |
| OnConnectionCompleteSuccess(event->connection_handle().Read(), |
| AclTransportType::kLe); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| PW_MODIFY_DIAGNOSTICS_POP(); |
| |
| return !IsLeSubeventBlocked(le_meta_event_view->subevent_code_enum().Read()); |
| } |
| |
| bool ProxyHost::HandleCommandCompleteEvent(H4PacketWithHci& h4_packet) { |
| pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan(); |
| Result<emboss::CommandCompleteEventView> command_complete_event = |
| MakeEmbossView<emboss::CommandCompleteEventView>(hci_buffer); |
| if (!command_complete_event.ok()) { |
| PW_LOG_ERROR( |
| "Buffer is too small for COMMAND_COMPLETE event. So will not process."); |
| return true; |
| } |
| |
| PW_MODIFY_DIAGNOSTICS_PUSH(); |
| PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum"); |
| switch (command_complete_event->command_opcode().Read()) { |
| case emboss::OpCode::READ_BUFFER_SIZE: { |
| Result<emboss::ReadBufferSizeCommandCompleteEventWriter> read_event = |
| MakeEmbossWriter<emboss::ReadBufferSizeCommandCompleteEventWriter>( |
| hci_buffer); |
| if (!read_event.ok()) { |
| PW_LOG_ERROR( |
| "Buffer is too small for READ_BUFFER_SIZE command complete event. " |
| "Will not process."); |
| break; |
| } |
| acl_data_channel_.ProcessReadBufferSizeCommandCompleteEvent(*read_event); |
| break; |
| } |
| case emboss::OpCode::LE_READ_BUFFER_SIZE_V1: { |
| Result<emboss::LEReadBufferSizeV1CommandCompleteEventWriter> read_event = |
| MakeEmbossWriter< |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(hci_buffer); |
| if (!read_event.ok()) { |
| PW_LOG_ERROR( |
| "Buffer is too small for LE_READ_BUFFER_SIZE_V1 command complete " |
| "event. So will not process."); |
| break; |
| } |
| acl_data_channel_.ProcessLEReadBufferSizeCommandCompleteEvent( |
| *read_event); |
| break; |
| } |
| case emboss::OpCode::LE_READ_BUFFER_SIZE_V2: { |
| Result<emboss::LEReadBufferSizeV2CommandCompleteEventWriter> read_event = |
| MakeEmbossWriter< |
| emboss::LEReadBufferSizeV2CommandCompleteEventWriter>(hci_buffer); |
| if (!read_event.ok()) { |
| PW_LOG_ERROR( |
| "Buffer is too small for LE_READ_BUFFER_SIZE_V2 command complete " |
| "event. So will not process."); |
| break; |
| } |
| acl_data_channel_.ProcessLEReadBufferSizeCommandCompleteEvent( |
| *read_event); |
| break; |
| } |
| default: |
| break; |
| } |
| PW_MODIFY_DIAGNOSTICS_POP(); |
| return true; |
| } |
| |
| void ProxyHost::HandleCommandFromHost(H4PacketWithH4&& h4_packet) { |
| pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan(); |
| Result<emboss::GenericHciCommandView> command = |
| MakeEmbossView<emboss::GenericHciCommandView>(hci_buffer); |
| if (!command.ok()) { |
| hci_transport_.SendToController(std::move(h4_packet)); |
| return; |
| } |
| |
| if (command->header().opcode().Read() == emboss::OpCode::RESET) { |
| PW_LOG_INFO("Resetting proxy on HCI_Reset Command from host."); |
| Reset(); |
| } |
| |
| hci_transport_.SendToController(std::move(h4_packet)); |
| } |
| |
| void ProxyHost::HandleAclFromHost(H4PacketWithH4&& h4_packet) { |
| acl_data_channel_.HandleAclFromHost(std::move(h4_packet)); |
| // It's possible for a channel handling tx traffic to have queued tx traffic |
| // or events. |
| l2cap_channel_manager_.DrainChannelQueuesIfNewTx(); |
| l2cap_channel_manager_.DeliverPendingEvents(); |
| } |
| |
| pw::Result<L2capCoc> ProxyHost::DoAcquireL2capCoc( |
| multibuf::MultiBufAllocator& rx_multibuf_allocator, |
| uint16_t connection_handle, |
| L2capCoc::CocConfig rx_config, |
| L2capCoc::CocConfig tx_config, |
| Function<void(multibuf::MultiBuf&& payload)>&& receive_fn, |
| ChannelEventCallback&& event_fn) { |
| return l2cap_channel_manager_.AcquireL2capCoc(rx_multibuf_allocator, |
| connection_handle, |
| rx_config, |
| tx_config, |
| std::move(receive_fn), |
| std::move(event_fn)); |
| } |
| |
| pw::Result<BasicL2capChannel> ProxyHost::DoAcquireBasicL2capChannel( |
| multibuf::MultiBufAllocator& rx_multibuf_allocator, |
| uint16_t connection_handle, |
| uint16_t local_cid, |
| uint16_t remote_cid, |
| AclTransportType transport, |
| OptionalPayloadReceiveCallback&& payload_from_controller_fn, |
| OptionalPayloadReceiveCallback&& payload_from_host_fn, |
| ChannelEventCallback&& event_fn) { |
| return l2cap_channel_manager_.AcquireBasicL2capChannel( |
| rx_multibuf_allocator, |
| connection_handle, |
| local_cid, |
| remote_cid, |
| transport, |
| std::move(payload_from_controller_fn), |
| std::move(payload_from_host_fn), |
| std::move(event_fn)); |
| } |
| |
| pw::Result<GattNotifyChannel> ProxyHost::DoAcquireGattNotifyChannel( |
| int16_t connection_handle, |
| uint16_t attribute_handle, |
| ChannelEventCallback&& event_fn) { |
| return l2cap_channel_manager_.AcquireGattNotifyChannel( |
| static_cast<uint16_t>(connection_handle), |
| attribute_handle, |
| std::move(event_fn)); |
| } |
| |
| bool ProxyHost::DoHasSendLeAclCapability() const { |
| return acl_data_channel_.HasSendAclCapability(AclTransportType::kLe); |
| } |
| |
| bool ProxyHost::DoHasSendBrEdrAclCapability() const { |
| return acl_data_channel_.HasSendAclCapability(AclTransportType::kBrEdr); |
| } |
| |
| uint16_t ProxyHost::DoGetNumFreeLeAclPackets() const { |
| return acl_data_channel_.GetNumFreeAclPackets(AclTransportType::kLe); |
| } |
| |
| uint16_t ProxyHost::DoGetNumFreeBrEdrAclPackets() const { |
| return acl_data_channel_.GetNumFreeAclPackets(AclTransportType::kBrEdr); |
| } |
| |
| void ProxyHost::DoRegisterL2capStatusDelegate(L2capStatusDelegate& delegate) { |
| l2cap_channel_manager_.RegisterStatusDelegate(delegate); |
| } |
| |
| void ProxyHost::DoUnregisterL2capStatusDelegate(L2capStatusDelegate& delegate) { |
| l2cap_channel_manager_.UnregisterStatusDelegate(delegate); |
| } |
| |
| void ProxyHost::OnConnectionCompleteSuccess(uint16_t connection_handle, |
| AclTransportType transport) { |
| acl_data_channel_.HandleConnectionCompleteEvent(connection_handle, transport); |
| Status l2cap_status = |
| l2cap_channel_manager_.AddConnection(connection_handle, transport); |
| if (!l2cap_status.ok()) { |
| PW_LOG_WARN("Could not add L2CAP connection for %#x: %s", |
| connection_handle, |
| l2cap_status.str()); |
| } |
| } |
| |
| void ProxyHost::OnAclTxCredits() { |
| l2cap_channel_manager_.ForceDrainChannelQueues(); |
| } |
| |
| Result<UniquePtr<ChannelProxy>> ProxyHost::InternalDoInterceptBasicModeChannel( |
| ConnectionHandle connection_handle, |
| uint16_t local_channel_id, |
| uint16_t remote_channel_id, |
| AclTransportType transport, |
| BufferReceiveFunction&& payload_from_controller_fn, |
| BufferReceiveFunction&& payload_from_host_fn, |
| ChannelEventCallback&& event_fn) { |
| return l2cap_channel_manager_.InterceptBasicModeChannel( |
| connection_handle, |
| local_channel_id, |
| remote_channel_id, |
| transport, |
| std::move(payload_from_controller_fn), |
| std::move(payload_from_host_fn), |
| std::move(event_fn)); |
| } |
| |
| Result<UniquePtr<ChannelProxy>> |
| ProxyHost::DoInterceptCreditBasedFlowControlChannel( |
| ConnectionHandle connection_handle, |
| ConnectionOrientedChannelConfig rx_config, |
| ConnectionOrientedChannelConfig tx_config, |
| MultiBufReceiveFunction&& receive_fn, |
| ChannelEventCallback&& event_fn) { |
| return l2cap_channel_manager_.InterceptCreditBasedFlowControlChannel( |
| connection_handle, |
| rx_config, |
| tx_config, |
| std::move(receive_fn), |
| std::move(event_fn)); |
| } |
| |
| } // namespace pw::bluetooth::proxy |