blob: 3a79fa986a97ee6ad55a28ebcb3d1b8af365b57e [file] [log] [blame]
// 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_assert/check.h" // IWYU pragma: keep
#include "pw_bluetooth/hci_common.emb.h"
#include "pw_bluetooth/hci_h4.emb.h"
#include "pw_bluetooth_proxy/emboss_util.h"
#include "pw_bluetooth_proxy/h4_packet.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)
: hci_transport_(std::move(send_to_host_fn),
std::move(send_to_controller_fn)),
acl_data_channel_(hci_transport_, le_acl_credits_to_reserve),
acl_send_pending_(false) {}
void ProxyHost::HandleH4HciFromHost(H4PacketWithH4&& h4_packet) {
hci_transport_.SendToController(std::move(h4_packet));
}
void ProxyHost::ProcessH4HciFromController(pw::span<uint8_t> hci_buffer) {
auto event = MakeEmboss<emboss::EventHeaderView>(hci_buffer);
if (!event.IsComplete()) {
PW_LOG_ERROR("Buffer is too small for EventHeader. So will not process.");
return;
}
PW_MODIFY_DIAGNOSTICS_PUSH();
PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum");
switch (event.event_code_enum().Read()) {
case emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS: {
auto nocp_event =
MakeEmboss<emboss::NumberOfCompletedPacketsEventWriter>(hci_buffer);
if (!nocp_event.IsComplete()) {
PW_LOG_ERROR(
"Buffer is too small for NUMBER_OF_COMPLETED_PACKETS event. So "
"will not process.");
break;
}
acl_data_channel_.ProcessNumberOfCompletedPacketsEvent(nocp_event);
break;
}
case emboss::EventCode::DISCONNECTION_COMPLETE: {
auto dc_event =
MakeEmboss<emboss::DisconnectionCompleteEventWriter>(hci_buffer);
if (!dc_event.IsComplete()) {
PW_LOG_ERROR(
"Buffer is too small for DISCONNECTION_COMPLETE event. So will not "
"process.");
break;
}
acl_data_channel_.ProcessDisconnectionCompleteEvent(dc_event);
break;
}
case emboss::EventCode::COMMAND_COMPLETE: {
ProcessCommandCompleteEvent(hci_buffer);
break;
}
default: {
PW_LOG_ERROR("Received unexpected HCI event. So will not process.");
return;
}
}
PW_MODIFY_DIAGNOSTICS_POP();
}
void ProxyHost::ProcessCommandCompleteEvent(pw::span<uint8_t> hci_buffer) {
auto command_complete_event =
MakeEmboss<emboss::CommandCompleteEventView>(hci_buffer);
if (!command_complete_event.IsComplete()) {
PW_LOG_ERROR(
"Buffer is too small for COMMAND_COMPLETE event. So will not process.");
return;
}
PW_MODIFY_DIAGNOSTICS_PUSH();
PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum");
switch (command_complete_event.command_opcode_enum().Read()) {
case emboss::OpCode::LE_READ_BUFFER_SIZE_V1: {
auto read_event =
MakeEmboss<emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(
hci_buffer);
if (!read_event.IsComplete()) {
PW_LOG_ERROR(
"Buffer is too small for LE_READ_BUFFER_SIZE_V1 command complete "
"event. So will not process.");
return;
}
acl_data_channel_.ProcessLEReadBufferSizeCommandCompleteEvent(read_event);
break;
}
case emboss::OpCode::LE_READ_BUFFER_SIZE_V2: {
auto read_event =
MakeEmboss<emboss::LEReadBufferSizeV2CommandCompleteEventWriter>(
hci_buffer);
if (!read_event.IsComplete()) {
PW_LOG_ERROR(
"Buffer is too small for LE_READ_BUFFER_SIZE_V2 command complete "
"event. So will not process.");
return;
}
acl_data_channel_.ProcessLEReadBufferSizeCommandCompleteEvent(read_event);
break;
}
default:
// Nothing to process
break;
}
PW_MODIFY_DIAGNOSTICS_POP();
}
void ProxyHost::HandleH4HciFromController(H4PacketWithHci&& h4_packet) {
if (h4_packet.GetHciSpan().empty()) {
PW_LOG_ERROR("Received empty H4 buffer. So will not process.");
} else if (h4_packet.GetH4Type() == emboss::H4PacketType::EVENT) {
ProcessH4HciFromController(h4_packet.GetHciSpan());
}
hci_transport_.SendToHost(std::move(h4_packet));
}
pw::Status ProxyHost::sendGattNotify(uint16_t connection_handle,
uint16_t attribute_handle,
const pw::span<uint8_t> attribute_value) {
if (connection_handle > 0x0EFF) {
PW_LOG_ERROR(
"Invalid connection handle: %d (max: 0x0EFF). So will not process.",
connection_handle);
return pw::Status::InvalidArgument();
}
if (attribute_handle == 0) {
PW_LOG_ERROR("Attribute handle cannot be 0. So will not process.");
return pw::Status::InvalidArgument();
}
if (constexpr uint16_t kMaxAttributeSize =
kH4BuffSize - 1 - emboss::AttNotifyOverAcl::MinSizeInBytes();
attribute_value.size() > kMaxAttributeSize) {
PW_LOG_ERROR("Attribute too large (%zu > %d). So will not process.",
attribute_value.size(),
kMaxAttributeSize);
return pw::Status::InvalidArgument();
}
H4PacketWithH4 h4_att_notify({});
{
std::lock_guard lock(acl_send_mutex_);
// TODO: https://pwbug.dev/348680331 - Currently ProxyHost only supports 1
// in-flight ACL send, increase this to support multiple.
if (acl_send_pending_) {
return pw::Status::Unavailable();
}
acl_send_pending_ = true;
size_t acl_packet_size =
emboss::AttNotifyOverAcl::MinSizeInBytes() + attribute_value.size();
emboss::AttNotifyOverAclWriter att_notify =
emboss::MakeAttNotifyOverAclView(attribute_value.size(),
H4HciSubspan(h4_buff_).data(),
acl_packet_size);
if (!att_notify.IsComplete()) {
PW_LOG_ERROR("Buffer is too small for ATT Notify. So will not send.");
return pw::Status::InvalidArgument();
}
BuildAttNotify(
att_notify, connection_handle, attribute_handle, attribute_value);
size_t h4_packet_size = 1 + acl_packet_size;
H4PacketWithH4 h4_temp(pw::span(h4_buff_.data(), h4_packet_size),
[this](const uint8_t* buffer) {
std::lock_guard inner_lock(acl_send_mutex_);
PW_CHECK_PTR_EQ(
buffer,
h4_buff_.data(),
"Received release callback for buffer that "
"doesn't match our buffer.");
PW_LOG_DEBUG("H4 packet release fn called.");
acl_send_pending_ = false;
});
h4_temp.SetH4Type(emboss::H4PacketType::ACL_DATA);
h4_att_notify = std::move(h4_temp);
}
// H4 packet is hereby moved. Either ACL data channel will move packet to
// controller or will be unable to send packet. In either case, packet will be
// destructed, so its release function will clear the `acl_send_pending` flag.
if (!acl_data_channel_.SendAcl(std::move(h4_att_notify))) {
return pw::Status::Unavailable();
}
return OkStatus();
}
void ProxyHost::BuildAttNotify(emboss::AttNotifyOverAclWriter att_notify,
uint16_t connection_handle,
uint16_t attribute_handle,
const pw::span<uint8_t> attribute_value) {
// ACL header
att_notify.acl_header().handle().Write(connection_handle);
att_notify.acl_header().packet_boundary_flag().Write(
emboss::AclDataPacketBoundaryFlag::FIRST_NON_FLUSHABLE);
att_notify.acl_header().broadcast_flag().Write(
emboss::AclDataPacketBroadcastFlag::POINT_TO_POINT);
size_t att_pdu_size =
emboss::AttHandleValueNtf::MinSizeInBytes() + attribute_value.size();
att_notify.acl_header().data_total_length().Write(
emboss::BFrameHeader::IntrinsicSizeInBytes() + att_pdu_size);
// L2CAP header
// TODO: https://pwbug.dev/349602172 - Define ATT CID in pw_bluetooth.
constexpr uint16_t kAttributeProtocolCID = 0x0004;
att_notify.l2cap_header().channel_id().Write(kAttributeProtocolCID);
att_notify.l2cap_header().pdu_length().Write(att_pdu_size);
// ATT PDU
att_notify.att_handle_value_ntf().attribute_opcode().Write(
emboss::AttOpcode::ATT_HANDLE_VALUE_NTF);
att_notify.att_handle_value_ntf().attribute_handle().Write(attribute_handle);
std::memcpy(att_notify.att_handle_value_ntf()
.attribute_value()
.BackingStorage()
.data(),
attribute_value.data(),
attribute_value.size());
}
bool ProxyHost::HasSendAclCapability() const {
return acl_data_channel_.GetLeAclCreditsToReserve() > 0;
}
uint16_t ProxyHost::GetNumFreeLeAclPackets() const {
return acl_data_channel_.GetNumFreeLeAclPackets();
}
} // namespace pw::bluetooth::proxy