blob: 5e089576856edd92f52eb19424c00d5ac7be94bd [file]
// 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/internal/l2cap_channel.h"
#include <mutex>
#include <optional>
#include "lib/stdcompat/utility.h"
#include "pw_assert/check.h"
#include "pw_bluetooth/emboss_util.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/internal/generic_l2cap_channel.h"
#include "pw_bluetooth_proxy/internal/l2cap_channel_manager.h"
#include "pw_bluetooth_proxy/l2cap_channel_common.h"
#include "pw_log/log.h"
#include "pw_span/span.h"
#include "pw_status/status.h"
#include "pw_status/try.h"
namespace pw::bluetooth::proxy {
namespace {
template <class... Ts>
struct Visitors : Ts... {
using Ts::operator()...;
};
template <class... Ts>
Visitors(Ts...) -> Visitors<Ts...>;
} // namespace
L2capChannel::~L2capChannel() {
// Block until there are no outstanding borrows. Callers (namely
// L2capChannelManager) MUST NOT be holding the `static_mutex_` when this
// destructor is called.
{
std::unique_lock lock(impl_.mutex_);
impl_.BlockWhileBorrowed(lock);
PW_LOG_INFO(
"btproxy: L2capChannel dtor - transport_: %u, connection_handle_ : "
"%#x, local_cid_: %#x, remote_cid_: %#x, state_: %u",
cpp23::to_underlying(transport_),
connection_handle(),
local_cid(),
remote_cid(),
cpp23::to_underlying(state_));
impl_.ClearQueue();
}
impl_.Close();
}
void L2capChannel::Stop() {
std::lock_guard rx_lock(rx_mutex_);
std::lock_guard lock(impl_.mutex_);
PW_LOG_INFO(
"btproxy: L2capChannel::Stop - transport_: %u, connection_handle_: %#x, "
"local_cid_: %#x, remote_cid_: %#x, previous state_: %u",
cpp23::to_underlying(transport_),
connection_handle(),
local_cid(),
remote_cid(),
cpp23::to_underlying(state_));
PW_CHECK(state_ != State::kNew && state_ != State::kClosed);
state_ = State::kStopped;
impl_.ClearQueue();
std::visit(
[](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (!std::is_same_v<T, std::monostate>) {
arg.Reset();
}
},
rx_engine_);
}
void L2capChannel::Close(L2capChannelEvent event) {
{
std::lock_guard rx_lock(rx_mutex_);
std::lock_guard lock(impl_.mutex_);
PW_LOG_INFO(
"btproxy: L2capChannel::Close - transport_: %u, "
"connection_handle_: %#x, local_cid_: %#x, remote_cid_: %#x, previous "
"state_: %u",
cpp23::to_underlying(transport_),
connection_handle(),
local_cid(),
remote_cid(),
cpp23::to_underlying(state_));
PW_CHECK(state_ != State::kNew);
if (state_ == State::kClosed) {
return;
}
state_ = State::kClosed;
impl_.ClearQueue();
std::visit(
[](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (!std::is_same_v<T, std::monostate>) {
arg.Reset();
}
},
rx_engine_);
}
impl_.SendEvent(event);
impl_.Close();
}
StatusWithMultiBuf L2capChannel::Write(multibuf::MultiBuf&& payload) {
StatusWithMultiBuf result = WriteDuringRx(std::move(payload));
DrainChannelQueuesIfNewTx();
return result;
}
bool L2capChannel::HandlePduFromController(pw::span<uint8_t> l2cap_pdu) {
if (state() != State::kRunning) {
PW_LOG_ERROR(
"btproxy: L2capChannel::OnPduReceivedFromController on non-running "
"channel. local_cid: %#x, remote_cid: %#x, state: %u",
local_cid(),
remote_cid(),
cpp23::to_underlying(state()));
impl_.SendEvent(L2capChannelEvent::kRxWhileStopped);
return true;
}
internal::RxEngine::HandlePduFromControllerReturnValue result;
{
std::lock_guard rx_lock(rx_mutex_);
result = rx_engine().HandlePduFromController(l2cap_pdu);
}
return std::visit(
Visitors{
[](std::monostate) {
// Do nothing and consume the packet.
return true;
},
[this](L2capChannelEvent event) {
StopAndSendEvent(event);
// Consume the packet that caused the event.
return true;
},
[this](multibuf::MultiBuf&& buffer) {
// MultiBufs are only returned by CreditBasedFlowControlRxEngine,
// which is used with PayloadReceiveCallback.
if (auto* receive_fn =
std::get_if<MultiBufReceiveFunction>(&from_controller_fn_);
*receive_fn != nullptr) {
(*receive_fn)(std::move(buffer));
}
return true;
},
[this](span<uint8_t> buffer) {
return std::visit(
Visitors{[](std::monostate) { return false; },
[this, buffer](OptionalPayloadReceiveCallback& cb) {
return SendPayloadToClient(buffer, &cb);
},
[this, buffer](OptionalBufferReceiveFunction& fn) {
return SendPayloadToClient(buffer, &fn);
},
[buffer](PayloadSpanReceiveCallback& cb) {
return cb(buffer);
},
[this, buffer](SpanReceiveFunction& fn) {
return fn(as_bytes(buffer),
ConnectionHandle{connection_handle()},
local_cid(),
remote_cid());
},
[](MultiBufReceiveFunction&) {
// CreditBasedFlowControlRxEngine only uses MultiBufs
PW_CRASH("Invalid from controller callback");
return false;
}
},
from_controller_fn_);
},
},
std::move(result));
}
L2capChannel::State L2capChannel::state() const {
std::lock_guard lock(impl_.mutex_);
return state_;
}
L2capChannel::L2capChannel(L2capChannelManager& l2cap_channel_manager,
multibuf::MultiBufAllocator* rx_multibuf_allocator,
uint16_t connection_handle,
AclTransportType transport,
uint16_t local_cid,
uint16_t remote_cid,
ChannelEventCallback&& event_fn)
: l2cap_channel_manager_(l2cap_channel_manager),
transport_(transport),
connection_handle_(connection_handle),
local_cid_(local_cid),
remote_cid_(remote_cid),
event_fn_(std::move(event_fn)),
rx_multibuf_allocator_(rx_multibuf_allocator),
impl_(*this) {
PW_LOG_INFO(
"btproxy: L2capChannel ctor - transport_: %u, connection_handle_ : %u, "
"local_cid_ : %#x, remote_cid_: %#x",
cpp23::to_underlying(transport_),
connection_handle,
local_cid,
remote_cid);
PW_CHECK(AreValidParameters(connection_handle, local_cid, remote_cid));
}
void L2capChannel::Start() {
PW_LOG_INFO(
"btproxy: L2capChannel initialized: "
"transport_: %u, connection_handle_ : %u, "
"local_cid_ : %#x, remote_cid_: %#x",
cpp23::to_underlying(transport_),
connection_handle(),
local_cid(),
remote_cid());
std::lock_guard lock(impl_.mutex_);
state_ = State::kRunning;
}
bool L2capChannel::AreValidParameters(uint16_t connection_handle,
uint16_t local_cid,
uint16_t remote_cid) {
if (connection_handle > kMaxValidConnectionHandle) {
PW_LOG_ERROR(
"Invalid connection handle %#x. Maximum connection handle is 0x0EFF.",
connection_handle);
return false;
}
if (local_cid == 0 || remote_cid == 0) {
PW_LOG_ERROR("L2CAP channel identifier 0 is not valid.");
return false;
}
return true;
}
pw::Result<H4PacketWithH4> L2capChannel::PopulateTxL2capPacket(
uint16_t data_length) {
return PopulateL2capPacket(data_length);
}
namespace {
constexpr size_t H4SizeForL2capData(uint16_t data_length) {
const size_t l2cap_packet_size =
emboss::BasicL2capHeader::IntrinsicSizeInBytes() + data_length;
const size_t acl_packet_size =
emboss::AclDataFrameHeader::IntrinsicSizeInBytes() + l2cap_packet_size;
return sizeof(emboss::H4PacketType) + acl_packet_size;
}
} // namespace
pw::Result<H4PacketWithH4> L2capChannel::PopulateL2capPacket(
uint16_t data_length) {
const size_t l2cap_packet_size =
emboss::BasicL2capHeader::IntrinsicSizeInBytes() + data_length;
const size_t h4_packet_size = H4SizeForL2capData(data_length);
pw::Result<H4PacketWithH4> h4_packet_res =
l2cap_channel_manager_.GetAclH4Packet(
static_cast<uint16_t>(h4_packet_size));
if (!h4_packet_res.ok()) {
return h4_packet_res.status();
}
H4PacketWithH4 h4_packet = std::move(h4_packet_res.value());
h4_packet.SetH4Type(emboss::H4PacketType::ACL_DATA);
PW_TRY_ASSIGN(
auto acl,
MakeEmbossWriter<emboss::AclDataFrameWriter>(h4_packet.GetHciSpan()));
acl.header().handle().Write(connection_handle());
// TODO: https://pwbug.dev/360932103 - Support packet segmentation, so this
// value will not always be FIRST_NON_FLUSHABLE.
acl.header().packet_boundary_flag().Write(
emboss::AclDataPacketBoundaryFlag::FIRST_NON_FLUSHABLE);
acl.header().broadcast_flag().Write(
emboss::AclDataPacketBroadcastFlag::POINT_TO_POINT);
acl.data_total_length().Write(l2cap_packet_size);
PW_TRY_ASSIGN(auto l2cap_header,
MakeEmbossWriter<emboss::BasicL2capHeaderWriter>(
acl.payload().BackingStorage().data(),
emboss::BasicL2capHeader::IntrinsicSizeInBytes()));
l2cap_header.pdu_length().Write(data_length);
l2cap_header.channel_id().Write(remote_cid());
return h4_packet;
}
std::optional<uint16_t> L2capChannel::MaxL2capPayloadSize() {
std::optional<uint16_t> max_acl_length =
channel_manager().MaxDataPacketLengthForTransport(transport());
if (!max_acl_length.has_value()) {
return std::nullopt;
}
if (*max_acl_length <= emboss::BasicL2capHeader::IntrinsicSizeInBytes()) {
return std::nullopt;
}
return *max_acl_length - emboss::BasicL2capHeader::IntrinsicSizeInBytes();
}
void L2capChannel::ReportNewTxPacketsOrCredits() {
impl_.ReportNewTxPacketsOrCredits();
}
void L2capChannel::DrainChannelQueuesIfNewTx() {
l2cap_channel_manager_.DrainChannelQueuesIfNewTx();
}
//-------
// Rx (protected)
//-------
bool L2capChannel::SendPayloadToClient(pw::span<uint8_t> payload,
SendPayloadToClientCallback callback) {
if (std::visit([](auto&& cb) { return *cb == nullptr; }, callback)) {
return false;
}
if (!rx_multibuf_allocator_) {
PW_LOG_ERROR(
"btproxy: rx_multibuf_allocator_ is null so unable to create multibuf "
"to pass to client. Will passthrough instead. "
"connection: %#x, local_cid: %#x ",
connection_handle(),
local_cid());
return false;
}
auto result = rx_multibuf_allocator_->AllocateContiguous(payload.size());
if (!result.has_value()) {
PW_LOG_ERROR(
"btproxy: rx_multibuf_allocator_ is out of memory. So stopping "
"channel and reporting it needs to be closed."
"connection: %#x, local_cid: %#x ",
connection_handle(),
local_cid());
StopAndSendEvent(L2capChannelEvent::kRxOutOfMemory);
return true;
}
multibuf::MultiBuf buffer = std::move(result.value());
auto bytes_copied = buffer.CopyFrom(as_bytes(payload));
PW_CHECK(bytes_copied.ok());
// If client returned multibuf to us, we copy it to the payload and indicate
// to the caller that packet should be forwarded.
// In the future when whole path is operating with multibuf's, we could pass
// it back up to container to be forwarded and avoid the two copies of
// payload.
auto client_multibuf =
std::visit(Visitors{[&buffer](OptionalPayloadReceiveCallback* cb) {
return (*cb)(std::move(buffer));
},
[this, &buffer](OptionalBufferReceiveFunction* fn) {
return (*fn)(std::move(buffer),
ConnectionHandle{connection_handle()},
local_cid(),
remote_cid());
}},
callback);
if (client_multibuf.has_value()) {
bytes_copied = client_multibuf->CopyTo(as_writable_bytes(payload));
PW_CHECK_UINT_EQ(bytes_copied.size(), payload.size());
return false;
}
return true;
}
pw::Status L2capChannel::StartRecombinationBuf(Direction direction,
size_t payload_size,
size_t extra_header_size) {
std::optional<multibuf::MultiBuf>& buf_optref =
GetRecombinationBufOptRef(direction);
PW_CHECK(!buf_optref.has_value());
if (rx_multibuf_allocator_ == nullptr) {
// TODO: https://pwbug.dev/423695410 - Should eventually recombine for these
// cases to allow channel to make handle/unhandle decision.
PW_LOG_WARN(
"Cannot start recombination without an allocator."
"connection: %#x, local_cid: %#x ",
connection_handle(),
local_cid());
return Status::FailedPrecondition();
}
buf_optref = rx_multibuf_allocator_->AllocateContiguous(extra_header_size +
payload_size);
if (!buf_optref.has_value()) {
return Status::ResourceExhausted();
}
buf_optref->DiscardPrefix(extra_header_size);
return pw::OkStatus();
}
void L2capChannel::EndRecombinationBuf(Direction direction) {
GetRecombinationBufOptRef(direction) = std::nullopt;
}
Status L2capChannel::InitBasic(FromControllerFn&& from_controller_fn,
FromHostFn&& from_host_fn) {
from_controller_fn_ = std::move(from_controller_fn);
from_host_fn_ = std::move(from_host_fn);
{
std::lock_guard rx_lock(rx_mutex_);
std::lock_guard lock(impl_.mutex_);
tx_engine_.emplace<internal::BasicModeTxEngine>(
connection_handle(), remote_cid(), *this);
rx_engine_.emplace<internal::BasicModeRxEngine>(local_cid());
}
return impl_.Init();
}
Status L2capChannel::InitCreditBasedFlowControl(
ConnectionOrientedChannelConfig rx_config,
ConnectionOrientedChannelConfig tx_config,
MultiBufReceiveFunction&& receive_fn) {
if (tx_config.mps < emboss::L2capLeCreditBasedConnectionReq::min_mps() ||
tx_config.mps > emboss::L2capLeCreditBasedConnectionReq::max_mps()) {
PW_LOG_ERROR("Tx MPS (%" PRIu16
" octets) invalid. L2CAP implementations shall support a "
"minimum MPS of %" PRIi32
" octets and may support an MPS up to %" PRIi32 " octets.",
tx_config.mps,
emboss::L2capLeCreditBasedConnectionReq::min_mps(),
emboss::L2capLeCreditBasedConnectionReq::max_mps());
return Status::InvalidArgument();
}
if (!rx_multibuf_allocator_) {
return Status::FailedPrecondition();
}
from_controller_fn_.emplace<MultiBufReceiveFunction>(std::move(receive_fn));
{
std::lock_guard rx_lock(rx_mutex_);
std::lock_guard lock(impl_.mutex_);
tx_engine_.emplace<internal::CreditBasedFlowControlTxEngine>(
tx_config, connection_handle(), local_cid(), *this);
rx_engine_.emplace<internal::CreditBasedFlowControlRxEngine>(
rx_config,
*rx_multibuf_allocator_,
pw::bind_member<&L2capChannel::ReplenishRxCredits>(this));
}
return impl_.Init();
}
Status L2capChannel::InitGattNotify(uint16_t attribute_handle) {
if (attribute_handle == 0) {
PW_LOG_ERROR("Attribute handle cannot be 0.");
return pw::Status::InvalidArgument();
}
{
std::lock_guard rx_lock(rx_mutex_);
std::lock_guard lock(impl_.mutex_);
rx_engine_.emplace<internal::GattNotifyRxEngine>();
tx_engine_.emplace<internal::GattNotifyTxEngine>(
connection_handle(), remote_cid(), attribute_handle, *this);
}
return impl_.Init();
}
Status L2capChannel::ReplenishRxCredits(uint16_t credits) {
PW_CHECK(rx_multibuf_allocator());
// SendFlowControlCreditInd logs if status is not ok, so no need to log here.
return channel_manager().SendFlowControlCreditInd(
connection_handle(), local_cid(), credits, *rx_multibuf_allocator());
}
Result<H4PacketWithH4> L2capChannel::AllocateH4(uint16_t length) {
return l2cap_channel_manager_.GetAclH4Packet(length);
}
internal::RxEngine& L2capChannel::rx_engine() {
return std::visit(
[](auto&& arg) -> internal::RxEngine& {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>)
PW_CRASH("RxEngine is monostate");
else
return arg;
},
rx_engine_);
}
internal::TxEngine& L2capChannel::tx_engine() {
return std::visit(
[](auto&& arg) -> internal::TxEngine& {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>)
PW_CRASH("TxEngine is monostate");
else
return arg;
},
tx_engine_);
}
bool L2capChannel::HandlePduFromHost(pw::span<uint8_t> l2cap_pdu) {
internal::TxEngine::HandlePduFromHostReturnValue result;
{
std::lock_guard lock(impl_.mutex_);
result = tx_engine().HandlePduFromHost(l2cap_pdu);
}
if (!result.send_to_client.has_value()) {
return !result.forward_to_controller;
}
span<uint8_t> buffer = result.send_to_client.value();
return std::visit(
Visitors{[](std::monostate) { return false; },
[this, &buffer](OptionalPayloadReceiveCallback& cb) {
return SendPayloadToClient(buffer, &cb);
},
[this, &buffer](OptionalBufferReceiveFunction& fn) {
return SendPayloadToClient(buffer, &fn);
},
[&buffer](PayloadSpanReceiveCallback& cb) { return cb(buffer); },
[this, &buffer](SpanReceiveFunction& fn) {
return fn(as_bytes(buffer),
ConnectionHandle{connection_handle()},
local_cid(),
remote_cid());
}},
from_host_fn_);
}
Status L2capChannel::AddTxCredits(uint16_t credits) {
Result<bool> result;
{
std::lock_guard lock(impl_.mutex_);
result = tx_engine().AddCredits(credits);
}
if (!result.ok()) {
StopAndSendEvent(L2capChannelEvent::kRxInvalid);
return result.status();
}
if (result.value()) {
ReportNewTxPacketsOrCredits();
}
return OkStatus();
}
Status L2capChannel::SendAdditionalRxCredits(uint16_t additional_rx_credits) {
if (state() != State::kRunning) {
return Status::FailedPrecondition();
}
std::lock_guard lock(rx_mutex_);
Status status = ReplenishRxCredits(additional_rx_credits);
if (status.ok()) {
status = rx_engine().AddRxCredits(additional_rx_credits);
}
DrainChannelQueuesIfNewTx();
return status;
}
std::optional<H4PacketWithH4> L2capChannel::GenerateNextTxPacket(
const multibuf::MultiBuf& payload, bool& keep_payload) {
Result<H4PacketWithH4> result =
tx_engine().GenerateNextPacket(payload, keep_payload);
if (!result.ok()) {
// TODO: https://pwbug.dev/450060983 - Return the result
return std::nullopt;
}
return std::move(result.value());
}
} // namespace pw::bluetooth::proxy