blob: 5aabf1c70758d43b7435d0e275bc4a0d55df8a72 [file] [log] [blame] [edit]
// 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<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)
: 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<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;
}
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<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 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<uint8_t> hci_buffer = h4_packet.GetHciSpan();
Result<emboss::AclDataFrameWriter> acl =
MakeEmbossWriter<emboss::AclDataFrameWriter>(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<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.");
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<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.");
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<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();
hci_transport_.SendToHost(std::move(h4_packet));
}
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) {
pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
Result<emboss::AclDataFrameWriter> acl =
MakeEmbossWriter<emboss::AclDataFrameWriter>(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<L2capCoc> ProxyHost::AcquireL2capCoc(
pw::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) {
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<uint16_t>(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<L2capChannelManager::LockedL2capChannel> channel =
l2cap_channel_manager_.FindChannelByLocalCid(connection_handle,
local_cid);
PW_CHECK(channel.has_value());
return static_cast<L2capCoc&>(channel->channel())
.SendAdditionalRxCredits(additional_rx_credits);
}
pw::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) {
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<GattNotifyChannel> 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<GattNotifyChannel> 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<const uint8_t> attribute_value) {
// TODO: https://pwbug.dev/369709521 - Migrate clients to channel API.
pw::Result<GattNotifyChannel> channel_result =
AcquireGattNotifyChannel(connection_handle, attribute_handle, nullptr);
if (!channel_result.ok()) {
return channel_result.status();
}
return channel_result->Write(attribute_value);
}
pw::Result<RfcommChannel> ProxyHost::AcquireRfcommChannel(
multibuf::MultiBufAllocator& rx_multibuf_allocator,
uint16_t connection_handle,
RfcommChannel::Config rx_config,
RfcommChannel::Config tx_config,
uint8_t channel_number,
Function<void(multibuf::MultiBuf&& payload)>&& 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