// 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 "pw_assert/check.h" #include "pw_bluetooth/emboss_util.h" #include "pw_bluetooth/hci_commands.emb.h" #include "pw_bluetooth/hci_common.emb.h" #include "pw_bluetooth/hci_data.emb.h" #include "pw_bluetooth/hci_h4.emb.h" #include "pw_bluetooth/l2cap_frames.emb.h" #include "pw_bluetooth_proxy/h4_packet.h" #include "pw_bluetooth_proxy/internal/gatt_notify_channel_internal.h" #include "pw_bluetooth_proxy/internal/l2cap_coc_internal.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&& send_to_host_fn, pw::Function&& send_to_controller_fn, uint16_t le_acl_credits_to_reserve, uint16_t br_edr_acl_credits_to_reserve) : hci_transport_(std::move(send_to_host_fn), std::move(send_to_controller_fn)), acl_data_channel_(hci_transport_, l2cap_channel_manager_, le_acl_credits_to_reserve, br_edr_acl_credits_to_reserve), l2cap_channel_manager_(acl_data_channel_) { 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_LOG_INFO("btproxy: ProxyHost dtor"); acl_data_channel_.Reset(); l2cap_channel_manager_.DeregisterAndCloseChannels( L2capChannelEvent::kChannelClosedByOther); } void ProxyHost::Reset() { // 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::HandleH4HciFromHost(H4PacketWithH4&& h4_packet) { switch (h4_packet.GetH4Type()) { case emboss::H4PacketType::COMMAND: HandleCommandFromHost(std::move(h4_packet)); return; case emboss::H4PacketType::EVENT: HandleEventFromHost(std::move(h4_packet)); return; case emboss::H4PacketType::ACL_DATA: HandleAclFromHost(std::move(h4_packet)); return; 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::HandleH4HciFromController(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 hci_buffer = h4_packet.GetHciSpan(); Result event = MakeEmbossView(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; } PW_MODIFY_DIAGNOSTICS_PUSH(); PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum"); switch (event->event_code().Read()) { case emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS: { acl_data_channel_.HandleNumberOfCompletedPacketsEvent( std::move(h4_packet)); break; } case emboss::EventCode::DISCONNECTION_COMPLETE: { acl_data_channel_.ProcessDisconnectionCompleteEvent( h4_packet.GetHciSpan()); hci_transport_.SendToHost(std::move(h4_packet)); break; } case emboss::EventCode::COMMAND_COMPLETE: { HandleCommandCompleteEvent(std::move(h4_packet)); break; } case emboss::EventCode::CONNECTION_COMPLETE: { 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; } } PW_MODIFY_DIAGNOSTICS_POP(); l2cap_channel_manager_.DeliverPendingEvents(); } void ProxyHost::HandleEventFromHost(H4PacketWithH4&& h4_packet) { pw::span hci_buffer = h4_packet.GetHciSpan(); Result event = MakeEmbossView(hci_buffer); if (!event.ok()) { PW_LOG_ERROR( "Buffer is too small for EventHeader. So will pass on to controller " "without processing."); hci_transport_.SendToController(std::move(h4_packet)); return; } PW_MODIFY_DIAGNOSTICS_PUSH(); PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum"); switch (event->event_code().Read()) { case emboss::EventCode::DISCONNECTION_COMPLETE: { acl_data_channel_.ProcessDisconnectionCompleteEvent( h4_packet.GetHciSpan()); hci_transport_.SendToController(std::move(h4_packet)); break; } default: { hci_transport_.SendToController(std::move(h4_packet)); return; } } PW_MODIFY_DIAGNOSTICS_POP(); } void ProxyHost::HandleAclFromController(H4PacketWithHci&& h4_packet) { pw::span hci_buffer = h4_packet.GetHciSpan(); Result acl = MakeEmbossWriter(hci_buffer); if (!acl.ok()) { PW_LOG_ERROR( "Buffer is too small for ACL header. So will pass on to host."); hci_transport_.SendToHost(std::move(h4_packet)); return; } if (!acl_data_channel_.HandleAclData( AclDataChannel::Direction::kFromController, *acl)) { hci_transport_.SendToHost(std::move(h4_packet)); return; } } 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 = MakeEmbossView(hci_buffer); if (!command_complete_event.ok()) { PW_LOG_ERROR( "Buffer is too small for COMMAND_COMPLETE event. So will not process."); hci_transport_.SendToHost(std::move(h4_packet)); return; } PW_MODIFY_DIAGNOSTICS_PUSH(); PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum"); switch (command_complete_event->command_opcode().Read()) { case emboss::OpCode::READ_BUFFER_SIZE: { Result read_event = MakeEmbossWriter( 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 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 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(); hci_transport_.SendToHost(std::move(h4_packet)); } void ProxyHost::HandleCommandFromHost(H4PacketWithH4&& h4_packet) { pw::span hci_buffer = h4_packet.GetHciSpan(); Result command = MakeEmbossView(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) { pw::span hci_buffer = h4_packet.GetHciSpan(); Result acl = MakeEmbossWriter(hci_buffer); if (!acl.ok()) { PW_LOG_ERROR( "Buffer is too small for ACL header. So will pass on to controller."); hci_transport_.SendToController(std::move(h4_packet)); return; } if (!acl_data_channel_.HandleAclData(AclDataChannel::Direction::kFromHost, *acl)) { hci_transport_.SendToController(std::move(h4_packet)); return; } } pw::Result ProxyHost::AcquireL2capCoc( pw::multibuf::MultiBufAllocator& rx_multibuf_allocator, uint16_t connection_handle, L2capCoc::CocConfig rx_config, L2capCoc::CocConfig tx_config, Function&& receive_fn, ChannelEventCallback&& event_fn) { Status status = acl_data_channel_.CreateAclConnection(connection_handle, AclTransportType::kLe); if (status.IsResourceExhausted()) { return pw::Status::Unavailable(); } PW_CHECK(status.ok() || status.IsAlreadyExists()); L2capSignalingChannel* signaling_channel = acl_data_channel_.FindSignalingChannel( connection_handle, static_cast(emboss::L2capFixedCid::LE_U_SIGNALING)); PW_CHECK(signaling_channel); return L2capCocInternal::Create(rx_multibuf_allocator, l2cap_channel_manager_, signaling_channel, connection_handle, rx_config, tx_config, std::move(event_fn), /*receive_fn=*/std::move(receive_fn)); } pw::Status ProxyHost::SendAdditionalRxCredits(uint16_t connection_handle, uint16_t local_cid, uint16_t additional_rx_credits) { std::optional channel = l2cap_channel_manager_.FindChannelByLocalCid(connection_handle, local_cid); PW_CHECK(channel.has_value()); return static_cast(channel->channel()) .SendAdditionalRxCredits(additional_rx_credits); } pw::Result ProxyHost::AcquireBasicL2capChannel( 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) { Status status = acl_data_channel_.CreateAclConnection(connection_handle, transport); if (status.IsResourceExhausted()) { return pw::Status::Unavailable(); } PW_CHECK(status.ok() || status.IsAlreadyExists()); return BasicL2capChannel::Create(l2cap_channel_manager_, &rx_multibuf_allocator, /*connection_handle=*/connection_handle, /*transport=*/transport, /*local_cid=*/local_cid, /*remote_cid=*/remote_cid, /*payload_from_controller_fn=*/ std::move(payload_from_controller_fn), /*payload_from_host_fn=*/ std::move(payload_from_host_fn), /*event_fn=*/std::move(event_fn)); } pw::Result ProxyHost::AcquireGattNotifyChannel( int16_t connection_handle, uint16_t attribute_handle, ChannelEventCallback&& event_fn) { Status status = acl_data_channel_.CreateAclConnection(connection_handle, AclTransportType::kLe); if (status != OkStatus() && status != Status::AlreadyExists()) { return pw::Status::Unavailable(); } return GattNotifyChannelInternal::Create(l2cap_channel_manager_, connection_handle, attribute_handle, std::move(event_fn)); } StatusWithMultiBuf ProxyHost::SendGattNotify(uint16_t connection_handle, uint16_t attribute_handle, pw::multibuf::MultiBuf&& payload) { // TODO: https://pwbug.dev/369709521 - Migrate clients to channel API. pw::Result channel_result = AcquireGattNotifyChannel(connection_handle, attribute_handle, nullptr); if (!channel_result.ok()) { return {channel_result.status(), std::move(payload)}; } return channel_result->Write(std::move(payload)); } pw::Status ProxyHost::SendGattNotify(uint16_t connection_handle, uint16_t attribute_handle, pw::span attribute_value) { // TODO: https://pwbug.dev/369709521 - Migrate clients to channel API. pw::Result channel_result = AcquireGattNotifyChannel(connection_handle, attribute_handle, nullptr); if (!channel_result.ok()) { return channel_result.status(); } return channel_result->Write(attribute_value); } pw::Result ProxyHost::AcquireRfcommChannel( multibuf::MultiBufAllocator& rx_multibuf_allocator, uint16_t connection_handle, RfcommChannel::Config rx_config, RfcommChannel::Config tx_config, uint8_t channel_number, Function&& payload_from_controller_fn, ChannelEventCallback&& event_fn) { Status status = acl_data_channel_.CreateAclConnection( connection_handle, AclTransportType::kBrEdr); if (status != OkStatus() && status != Status::AlreadyExists()) { return pw::Status::Unavailable(); } return RfcommChannel::Create(l2cap_channel_manager_, rx_multibuf_allocator, connection_handle, rx_config, tx_config, channel_number, std::move(payload_from_controller_fn), std::move(event_fn)); } bool ProxyHost::HasSendLeAclCapability() const { return acl_data_channel_.HasSendAclCapability(AclTransportType::kLe); } bool ProxyHost::HasSendBrEdrAclCapability() const { return acl_data_channel_.HasSendAclCapability(AclTransportType::kBrEdr); } uint16_t ProxyHost::GetNumFreeLeAclPackets() const { return acl_data_channel_.GetNumFreeAclPackets(AclTransportType::kLe); } uint16_t ProxyHost::GetNumFreeBrEdrAclPackets() const { return acl_data_channel_.GetNumFreeAclPackets(AclTransportType::kBrEdr); } void ProxyHost::RegisterL2capStatusDelegate(L2capStatusDelegate& delegate) { l2cap_channel_manager_.RegisterStatusDelegate(delegate); } void ProxyHost::UnregisterL2capStatusDelegate(L2capStatusDelegate& delegate) { l2cap_channel_manager_.UnregisterStatusDelegate(delegate); } } // namespace pw::bluetooth::proxy