| // Copyright 2025 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/config.h" |
| |
| #if PW_BLUETOOTH_PROXY_ASYNC != 0 |
| |
| #include "pw_assert/check.h" |
| #include "pw_bluetooth_proxy/internal/proxy_host_async.h" |
| #include "pw_bluetooth_proxy/proxy_host.h" |
| #include "pw_log/log.h" |
| |
| namespace pw::bluetooth::proxy { |
| |
| using BasicRequest = internal::ProxyHostImpl::BasicRequest; |
| using ChannelRequest = internal::ProxyHostImpl::ChannelRequest; |
| |
| Status ProxyHost::SetDispatcher(async2::Dispatcher& dispatcher) { |
| PW_TRY(l2cap_channel_manager_.impl().Init(dispatcher)); |
| impl_.Start(); |
| return OkStatus(); |
| } |
| |
| void ProxyHost::HandleH4HciFromHost(H4PacketWithH4&& h4_packet) { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| DoHandleH4HciFromHost(std::move(h4_packet)); |
| return; |
| } |
| // TODO(b/411168474): The H4 packet must remain valid as it is asynchronously |
| // handled by the ProxyHost. Currently, the `H4PacketWithH4` wraps a span that |
| // is backed by some container memory. If the `release_fn_` field is set on |
| // the packet, it is assumed that the packet should clean itself up. If not, |
| // the container may drop or free this memory when this method returns. |
| // |
| // Ideally, `H4PacketWithH4` would wrap a multibuf that managed the lifetime |
| // of the memory backing the packet. Until then, we check whether a |
| // `ReleaseFn` is set, and if not, copy its data to an allocated packet with |
| // a `ReleaseFn`. |
| if (h4_packet.HasReleaseFn()) { |
| impl_.SendH4PacketFromHost(std::move(h4_packet)); |
| return; |
| } |
| Allocator& allocator = l2cap_channel_manager_.impl().allocator(); |
| Result<H4PacketWithH4> owned_h4_packet_result = |
| H4PacketWithH4::CopyFrom(allocator, h4_packet); |
| if (!owned_h4_packet_result.ok()) { |
| PW_LOG_ERROR( |
| "Dropping H4 packet from host: unable to allocate storage to handle " |
| "asynchronously!"); |
| return; |
| } |
| impl_.SendH4PacketFromHost(std::move(owned_h4_packet_result.value())); |
| } |
| |
| void ProxyHost::HandleH4HciFromController(H4PacketWithHci&& h4_packet) { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| DoHandleH4HciFromController(std::move(h4_packet)); |
| return; |
| } |
| // TODO(b/411168474): See the corresponding note in `HandleH4HciFromHost`. |
| // As with that method, if a `ReleaseFn` is not set, copy its data to an |
| // allocated packet with a `ReleaseFn`. |
| if (h4_packet.HasReleaseFn()) { |
| impl_.SendH4PacketFromController(std::move(h4_packet)); |
| return; |
| } |
| Allocator& allocator = l2cap_channel_manager_.impl().allocator(); |
| Result<H4PacketWithHci> owned_h4_packet_result = |
| H4PacketWithHci::CopyFrom(allocator, h4_packet); |
| if (!owned_h4_packet_result.ok()) { |
| PW_LOG_ERROR( |
| "Dropping H4 packet from controller: unable to allocate storage to " |
| "handle asynchronously!"); |
| return; |
| } |
| impl_.SendH4PacketFromController(std::move(owned_h4_packet_result.value())); |
| } |
| |
| void ProxyHost::Reset() { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| DoReset(); |
| } else { |
| BasicRequest request{ |
| .type = BasicRequest::Type::kReset, |
| }; |
| impl_.BasicSendAndReceive(std::move(request)); |
| } |
| } |
| |
| Result<L2capCoc> ProxyHost::AcquireL2capCoc( |
| 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) { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| return DoAcquireL2capCoc(rx_multibuf_allocator, |
| connection_handle, |
| rx_config, |
| tx_config, |
| std::move(receive_fn), |
| std::move(event_fn)); |
| } |
| ChannelRequest request{ |
| .params = |
| ChannelRequest::L2capCocParams{ |
| .rx_multibuf_allocator = &rx_multibuf_allocator, |
| .connection_handle = connection_handle, |
| .rx_config = rx_config, |
| .tx_config = tx_config, |
| .receive_fn = std::move(receive_fn), |
| .event_fn = std::move(event_fn), |
| }, |
| }; |
| PW_TRY_ASSIGN(auto channel, impl_.ChannelSendAndReceive(std::move(request))); |
| return Result<L2capCoc>(std::move(std::get<L2capCoc>(channel))); |
| } |
| |
| Result<BasicL2capChannel> 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) { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| return DoAcquireBasicL2capChannel(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)); |
| } |
| ChannelRequest request{ |
| .params = |
| ChannelRequest::BasicL2capParams{ |
| .rx_multibuf_allocator = &rx_multibuf_allocator, |
| .connection_handle = connection_handle, |
| .local_cid = local_cid, |
| .remote_cid = remote_cid, |
| .transport = transport, |
| .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_TRY_ASSIGN(auto channel, impl_.ChannelSendAndReceive(std::move(request))); |
| return Result<BasicL2capChannel>( |
| std::move(std::get<BasicL2capChannel>(channel))); |
| } |
| |
| Result<UniquePtr<ChannelProxy>> ProxyHost::DoInterceptBasicModeChannel( |
| 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) { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| return InternalDoInterceptBasicModeChannel( |
| 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)); |
| } |
| ChannelRequest request{ |
| .params = |
| ChannelRequest::BasicChannelProxyParams{ |
| .connection_handle = connection_handle, |
| .local_channel_id = local_channel_id, |
| .remote_channel_id = remote_channel_id, |
| .transport = transport, |
| .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_TRY_ASSIGN(auto channel, impl_.ChannelSendAndReceive(std::move(request))); |
| return Result<UniquePtr<ChannelProxy>>( |
| std::move(std::get<UniquePtr<ChannelProxy>>(channel))); |
| } |
| |
| Result<GattNotifyChannel> ProxyHost::AcquireGattNotifyChannel( |
| int16_t connection_handle, |
| uint16_t attribute_handle, |
| ChannelEventCallback&& event_fn) { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| return DoAcquireGattNotifyChannel( |
| connection_handle, attribute_handle, std::move(event_fn)); |
| } |
| ChannelRequest request{ |
| .params = |
| ChannelRequest::GattNotifyParams{ |
| .connection_handle = connection_handle, |
| .attribute_handle = attribute_handle, |
| .event_fn = std::move(event_fn), |
| }, |
| }; |
| PW_TRY_ASSIGN(auto channel, impl_.ChannelSendAndReceive(std::move(request))); |
| return Result<GattNotifyChannel>( |
| std::move(std::get<GattNotifyChannel>(channel))); |
| } |
| |
| bool ProxyHost::HasSendLeAclCapability() const { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| return DoHasSendLeAclCapability(); |
| } |
| BasicRequest request; |
| request.type = BasicRequest::Type::kHasSendLeAclCapability; |
| return impl_.BasicSendAndReceive(std::move(request)) != 0; |
| } |
| |
| bool ProxyHost::HasSendBrEdrAclCapability() const { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| return DoHasSendBrEdrAclCapability(); |
| } |
| BasicRequest request; |
| request.type = BasicRequest::Type::kHasSendBrEdrAclCapability; |
| return impl_.BasicSendAndReceive(std::move(request)) != 0; |
| } |
| |
| uint16_t ProxyHost::GetNumFreeLeAclPackets() const { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| return DoGetNumFreeLeAclPackets(); |
| } |
| BasicRequest request; |
| request.type = BasicRequest::Type::kGetNumFreeLeAclPackets; |
| return impl_.BasicSendAndReceive(std::move(request)); |
| } |
| |
| uint16_t ProxyHost::GetNumFreeBrEdrAclPackets() const { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| return DoGetNumFreeBrEdrAclPackets(); |
| } |
| BasicRequest request; |
| request.type = BasicRequest::Type::kGetNumFreeBrEdrAclPackets; |
| return impl_.BasicSendAndReceive(std::move(request)); |
| } |
| |
| void ProxyHost::RegisterL2capStatusDelegate(L2capStatusDelegate& delegate) { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| DoRegisterL2capStatusDelegate(delegate); |
| return; |
| } |
| BasicRequest request; |
| request.type = BasicRequest::Type::kRegisterL2capStatusDelegate; |
| request.delegate = &delegate; |
| impl_.BasicSendAndReceive(std::move(request)); |
| } |
| |
| void ProxyHost::UnregisterL2capStatusDelegate(L2capStatusDelegate& delegate) { |
| if (l2cap_channel_manager_.impl().IsRunningOnDispatcherThread()) { |
| DoUnregisterL2capStatusDelegate(delegate); |
| return; |
| } |
| BasicRequest request; |
| request.type = BasicRequest::Type::kUnregisterL2capStatusDelegate; |
| request.delegate = &delegate; |
| impl_.BasicSendAndReceive(std::move(request)); |
| } |
| |
| namespace internal { |
| |
| ProxyHostImpl::ProxyHostImpl(ProxyHost& proxy) |
| : proxy_(proxy), |
| host_packet_task_(PW_ASYNC_TASK_NAME("ProxyHost:FromHost"), *this), |
| controller_packet_task_(PW_ASYNC_TASK_NAME("ProxyHost:FromController"), |
| *this), |
| basic_task_(PW_ASYNC_TASK_NAME("ProxyHost:Basic"), *this), |
| channel_task_(PW_ASYNC_TASK_NAME("ProxyHost:Channel"), *this) { |
| [[maybe_unused]] auto [host_packet_handle, |
| host_packet_sender, |
| host_packet_receiver] = |
| async2::CreateSpscChannel(host_packet_storage_); |
| host_packet_sender_ = std::move(host_packet_sender); |
| host_packet_task_.set_receiver(std::move(host_packet_receiver)); |
| |
| [[maybe_unused]] auto [controller_packet_handle, |
| controller_packet_sender, |
| controller_packet_receiver] = |
| async2::CreateSpscChannel(controller_packet_storage_); |
| controller_packet_sender_ = std::move(controller_packet_sender); |
| controller_packet_task_.set_receiver(std::move(controller_packet_receiver)); |
| |
| [[maybe_unused]] auto [basic_request_handle, |
| basic_request_sender, |
| basic_request_receiver] = |
| async2::CreateSpscChannel(basic_request_storage_); |
| [[maybe_unused]] auto [basic_response_handle, |
| basic_response_sender, |
| basic_response_receiver] = |
| async2::CreateSpscChannel(basic_response_storage_); |
| basic_request_sender_ = std::move(basic_request_sender); |
| basic_task_.set_receiver(std::move(basic_request_receiver)); |
| basic_task_.set_sender(std::move(basic_response_sender)); |
| basic_response_receiver_ = std::move(basic_response_receiver); |
| |
| [[maybe_unused]] auto [channel_request_handle, |
| channel_request_sender, |
| channel_request_receiver] = |
| async2::CreateSpscChannel(channel_request_storage_); |
| [[maybe_unused]] auto [channel_response_handle, |
| channel_response_sender, |
| channel_response_receiver] = |
| async2::CreateSpscChannel(channel_response_storage_); |
| channel_request_sender_ = std::move(channel_request_sender); |
| channel_task_.set_receiver(std::move(channel_request_receiver)); |
| channel_task_.set_sender(std::move(channel_response_sender)); |
| channel_response_receiver_ = std::move(channel_response_receiver); |
| } |
| |
| async2::Dispatcher& ProxyHostImpl::dispatcher() const { |
| return proxy_.l2cap_channel_manager_.impl().dispatcher(); |
| } |
| |
| void ProxyHostImpl::Start() { |
| dispatcher().Post(host_packet_task_); |
| dispatcher().Post(controller_packet_task_); |
| dispatcher().Post(basic_task_); |
| dispatcher().Post(channel_task_); |
| } |
| |
| void ProxyHostImpl::SendH4PacketFromHost(H4PacketWithH4&& h4_packet) const { |
| auto reservation = host_packet_sender_.TryReserveSend(); |
| if (reservation.ok()) { |
| reservation->Commit(std::move(h4_packet)); |
| } else { |
| host_packet_sender_.BlockingSend(dispatcher(), std::move(h4_packet)) |
| .IgnoreError(); |
| } |
| } |
| |
| void ProxyHostImpl::SendH4PacketFromController( |
| H4PacketWithHci&& h4_packet) const { |
| auto reservation = controller_packet_sender_.TryReserveSend(); |
| if (reservation.ok()) { |
| reservation->Commit(std::move(h4_packet)); |
| } else { |
| controller_packet_sender_.BlockingSend(dispatcher(), std::move(h4_packet)) |
| .IgnoreError(); |
| } |
| } |
| |
| uint16_t ProxyHostImpl::BasicSendAndReceive(BasicRequest&& request) const { |
| if (!basic_request_sender_.BlockingSend(dispatcher(), std::move(request)) |
| .ok()) { |
| return 0; |
| } |
| |
| auto response = basic_response_receiver_.BlockingReceive(dispatcher()); |
| if (!response.ok()) { |
| return 0; |
| } |
| return *response; |
| } |
| |
| void ProxyHostImpl::DoHandleRequest(H4PacketWithH4&& h4_packet) { |
| proxy_.DoHandleH4HciFromHost(std::move(h4_packet)); |
| } |
| |
| void ProxyHostImpl::DoHandleRequest(H4PacketWithHci&& h4_packet) { |
| proxy_.DoHandleH4HciFromController(std::move(h4_packet)); |
| } |
| |
| uint16_t ProxyHostImpl::DoHandleRequest(BasicRequest&& request) { |
| switch (request.type) { |
| case BasicRequest::Type::kReset: |
| proxy_.DoReset(); |
| break; |
| |
| case BasicRequest::Type::kRegisterL2capStatusDelegate: |
| proxy_.DoRegisterL2capStatusDelegate(*request.delegate); |
| break; |
| |
| case BasicRequest::Type::kUnregisterL2capStatusDelegate: |
| proxy_.DoUnregisterL2capStatusDelegate(*request.delegate); |
| break; |
| |
| case BasicRequest::Type::kHasSendLeAclCapability: |
| return proxy_.DoHasSendLeAclCapability(); |
| |
| case BasicRequest::Type::kHasSendBrEdrAclCapability: |
| return proxy_.DoHasSendBrEdrAclCapability(); |
| |
| case BasicRequest::Type::kGetNumFreeLeAclPackets: |
| return proxy_.DoGetNumFreeLeAclPackets(); |
| |
| case BasicRequest::Type::kGetNumFreeBrEdrAclPackets: |
| return proxy_.DoGetNumFreeBrEdrAclPackets(); |
| } |
| return 0; |
| } |
| |
| Result<ProxyHostImpl::ClientChannel> ProxyHostImpl::ChannelSendAndReceive( |
| ChannelRequest&& request) const { |
| PW_TRY( |
| channel_request_sender_.BlockingSend(dispatcher(), std::move(request))); |
| auto response = channel_response_receiver_.BlockingReceive(dispatcher()); |
| if (!response.ok()) { |
| return response.status(); |
| } |
| return Result<ClientChannel>(std::move(*response)); |
| } |
| |
| namespace { |
| |
| template <typename... Request> |
| struct ChannelRequestVisitor : Request... { |
| using Request::operator()...; |
| }; |
| |
| template <typename... Request> |
| ChannelRequestVisitor(Request...) -> ChannelRequestVisitor<Request...>; |
| |
| } // namespace |
| |
| Result<ProxyHostImpl::ClientChannel> ProxyHostImpl::DoHandleRequest( |
| ChannelRequest&& request) { |
| return std::visit( |
| ChannelRequestVisitor{ |
| [this](ChannelRequest::BasicL2capParams&& params) |
| -> Result<ProxyHostImpl::ClientChannel> { |
| PW_TRY_ASSIGN(ClientChannel channel, |
| proxy_.DoAcquireBasicL2capChannel( |
| *params.rx_multibuf_allocator, |
| params.connection_handle, |
| params.local_cid, |
| params.remote_cid, |
| params.transport, |
| std::move(params.payload_from_controller_fn), |
| std::move(params.payload_from_host_fn), |
| std::move(params.event_fn))); |
| return Result<ClientChannel>(std::move(channel)); |
| }, |
| [this](ChannelRequest::GattNotifyParams&& params) |
| -> Result<ProxyHostImpl::ClientChannel> { |
| PW_TRY_ASSIGN( |
| ClientChannel channel, |
| proxy_.DoAcquireGattNotifyChannel(params.connection_handle, |
| params.attribute_handle, |
| std::move(params.event_fn))); |
| return Result<ClientChannel>(std::move(channel)); |
| }, |
| [this](ChannelRequest::L2capCocParams&& params) |
| -> Result<ProxyHostImpl::ClientChannel> { |
| PW_TRY_ASSIGN( |
| ClientChannel channel, |
| proxy_.DoAcquireL2capCoc(*params.rx_multibuf_allocator, |
| params.connection_handle, |
| params.rx_config, |
| params.tx_config, |
| std::move(params.receive_fn), |
| std::move(params.event_fn))); |
| return Result<ClientChannel>(std::move(channel)); |
| }, |
| [this](ChannelRequest::BasicChannelProxyParams&& params) |
| -> Result<ProxyHostImpl::ClientChannel> { |
| PW_TRY_ASSIGN(ClientChannel channel, |
| proxy_.InternalDoInterceptBasicModeChannel( |
| params.connection_handle, |
| params.local_channel_id, |
| params.remote_channel_id, |
| params.transport, |
| std::move(params.payload_from_controller_fn), |
| std::move(params.payload_from_host_fn), |
| std::move(params.event_fn))); |
| return Result<ClientChannel>(std::move(channel)); |
| }}, |
| std::move(request.params)); |
| } |
| |
| } // namespace internal |
| } // namespace pw::bluetooth::proxy |
| |
| #endif // PW_BLUETOOTH_PROXY_ASYNC != 0 |