blob: dd5f5995deb52ce3ca19a46358ffa5d3398d9347 [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 "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 {
std::array<containers::Pair<uint8_t*, bool>, ProxyHost::kNumH4Buffs>
ProxyHost::InitOccupiedMap() {
std::array<containers::Pair<uint8_t*, bool>, kNumH4Buffs> arr;
acl_send_mutex_.lock();
for (size_t i = 0; i < kNumH4Buffs; ++i) {
arr[i] = {h4_buffs_[i].data(), false};
}
acl_send_mutex_.unlock();
return arr;
}
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),
h4_buff_occupied_(InitOccupiedMap()) {}
void ProxyHost::HandleH4HciFromHost(H4PacketWithH4&& h4_packet) {
hci_transport_.SendToController(std::move(h4_packet));
}
void ProxyHost::HandleH4HciFromController(H4PacketWithHci&& h4_packet) {
pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
auto event = MakeEmboss<emboss::EventHeaderView>(hci_buffer);
if (!event.IsComplete()) {
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_enum().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_.HandleDisconnectionCompleteEvent(std::move(h4_packet));
break;
}
case emboss::EventCode::COMMAND_COMPLETE: {
HandleCommandCompleteEvent(std::move(h4_packet));
break;
}
default: {
hci_transport_.SendToHost(std::move(h4_packet));
return;
}
}
PW_MODIFY_DIAGNOSTICS_POP();
}
void ProxyHost::HandleCommandCompleteEvent(H4PacketWithHci&& h4_packet) {
pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
emboss::CommandCompleteEventView 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.");
hci_transport_.SendToHost(std::move(h4_packet));
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:
break;
}
PW_MODIFY_DIAGNOSTICS_POP();
hci_transport_.SendToHost(std::move(h4_packet));
}
void ProxyHost::Reset() {
acl_send_mutex_.lock();
acl_data_channel_.Reset();
for (const auto& [buff, _] : h4_buff_occupied_) {
h4_buff_occupied_.at(buff) = false;
}
acl_send_mutex_.unlock();
}
std::optional<pw::span<uint8_t>> ProxyHost::ReserveH4Buff() {
for (const auto& [buff, occupied] : h4_buff_occupied_) {
if (!occupied) {
h4_buff_occupied_.at(buff) = true;
return {{buff, kH4BuffSize}};
}
}
return std::nullopt;
}
pw::Status ProxyHost::SendGattNotify(uint16_t connection_handle,
uint16_t attribute_handle,
pw::span<const 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;
{
acl_send_mutex_.lock();
std::optional<span<uint8_t>> h4_buff = ReserveH4Buff();
if (!h4_buff) {
PW_LOG_WARN("No H4 buffers available. So will not send.");
acl_send_mutex_.unlock();
return pw::Status::Unavailable();
}
size_t acl_packet_size =
emboss::AttNotifyOverAcl::MinSizeInBytes() + attribute_value.size();
size_t h4_packet_size = sizeof(emboss::H4PacketType) + acl_packet_size;
H4PacketWithH4 h4_temp(
span(h4_buff->data(), h4_packet_size),
/*release_fn=*/[this](const uint8_t* buffer) {
acl_send_mutex_.lock();
PW_CHECK(h4_buff_occupied_.contains(const_cast<uint8_t*>(buffer)),
"Received release callback for invalid buffer address.");
h4_buff_occupied_.at(const_cast<uint8_t*>(buffer)) = false;
acl_send_mutex_.unlock();
});
h4_temp.SetH4Type(emboss::H4PacketType::ACL_DATA);
if (h4_packet_size > h4_buff->size()) {
PW_LOG_ERROR("Buffer is too small for H4 packet. So will not send.");
acl_send_mutex_.unlock();
return pw::Status::InvalidArgument();
}
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.");
acl_send_mutex_.unlock();
return pw::Status::InvalidArgument();
}
BuildAttNotify(
att_notify, connection_handle, attribute_handle, attribute_value);
h4_att_notify = std::move(h4_temp);
acl_send_mutex_.unlock();
}
// 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 corresponding flag in
// `h4_buff_occupied_`.
if (!acl_data_channel_.SendAcl(std::move(h4_att_notify))) {
// SendAcl function will have logged reason for failure to send..
return pw::Status::Unavailable();
}
return OkStatus();
}
void ProxyHost::BuildAttNotify(emboss::AttNotifyOverAclWriter att_notify,
uint16_t connection_handle,
uint16_t attribute_handle,
pw::span<const 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