blob: 0b5cf653c1f90ffb6b524dd69f18a00ffd55fa8e [file] [edit]
// 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