blob: 58951128311106d8ef4867be5e8572c70fb6216d [file] [edit]
// Copyright 2026 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/rfcomm/rfcomm_manager.h"
#include "pw_allocator/allocator.h"
#include "pw_assert/check.h"
#include "pw_bluetooth/emboss_util.h"
#include "pw_bluetooth_proxy/l2cap_channel_common.h"
#include "pw_bluetooth_proxy/rfcomm/rfcomm_common.h"
#include "pw_containers/vector.h"
#include "pw_log/log.h"
#include "pw_multibuf/multibuf.h"
namespace pw::bluetooth::proxy::rfcomm {
RfcommManager::ConnectionState::ConnectionState(ConnectionHandle handle,
uint16_t local_cid_arg,
uint16_t remote_cid_arg,
Allocator& allocator)
: connection_handle(handle),
local_cid(local_cid_arg),
remote_cid(remote_cid_arg),
channels(allocator) {}
RfcommManager::RfcommManager(
L2capChannelManagerInterface& l2cap_channel_manager, Allocator& allocator)
: l2cap_channel_manager_(l2cap_channel_manager),
allocator_(allocator),
connections_(allocator) {
PW_LOG_INFO("RFCOMM manager created");
}
RfcommManager::~RfcommManager() {
PW_LOG_INFO("RFCOMM manager destroyed");
DeregisterAndCloseChannels(RfcommEvent::kChannelClosedByOther);
}
Result<RfcommChannel> RfcommManager::DoAcquireRfcommChannel(
multibuf::MultiBufAllocator& rx_multibuf_allocator,
ConnectionHandle connection_handle,
uint8_t channel_number,
RfcommDirection direction,
bool mux_initiator,
const RfcommChannelConfig& rx_config,
const RfcommChannelConfig& tx_config,
RfcommReceiveCallback&& receive_fn,
RfcommEventCallback&& event_fn) {
std::lock_guard lock(connections_mutex_);
auto it = connections_.find(connection_handle);
// If the connection is not found, create a new connection state and L2CAP
// channel proxy.
if (it == connections_.end()) {
OptionalBufferReceiveFunction from_controller_fn =
[this](multibuf::MultiBuf&& payload,
ConnectionHandle handle,
uint16_t local_cid,
uint16_t remote_cid) -> std::optional<multibuf::MultiBuf> {
return HandlePduFromController(
std::move(payload), handle, local_cid, remote_cid);
};
auto proxy_result = l2cap_channel_manager_.InterceptBasicModeChannel(
connection_handle,
rx_config.cid,
tx_config.cid,
AclTransportType::kBrEdr,
std::move(from_controller_fn),
{},
[this, connection_handle](L2capChannelEvent event) {
this->HandleL2capEvent(event, connection_handle);
});
if (!proxy_result.ok()) {
return proxy_result.status();
}
auto emplace_result = connections_.try_emplace(connection_handle,
connection_handle,
rx_config.cid,
tx_config.cid,
allocator_);
if (!emplace_result.has_value()) {
return Status::ResourceExhausted();
}
it = emplace_result.value().first;
it->second.l2cap_channel_proxy = std::move(proxy_result).value();
PW_LOG_INFO("New RFCOMM connection state created for connection handle %hu",
static_cast<uint16_t>(connection_handle));
} else {
// If the connection already exists, verify that the CIDs match.
if (it->second.local_cid != rx_config.cid ||
it->second.remote_cid != tx_config.cid) {
PW_LOG_ERROR(
"RFCOMM CIDs do not match existing connection for handle %hu",
static_cast<uint16_t>(connection_handle));
return Status::InvalidArgument();
}
}
auto& conn_state = it->second;
auto channel_it =
conn_state.channels.find(MakeDlci(channel_number, direction));
// If the channel already exists, return an error.
if (channel_it != conn_state.channels.end()) {
return Status::AlreadyExists();
}
// Insert the new channel into the connection state.
auto emplace_result =
conn_state.channels.try_emplace(MakeDlci(channel_number, direction),
rx_multibuf_allocator,
*conn_state.l2cap_channel_proxy,
connection_handle,
channel_number,
direction,
mux_initiator,
rx_config,
tx_config,
kRfcommCrc,
std::move(receive_fn),
std::move(event_fn));
if (!emplace_result.has_value()) {
PW_LOG_ERROR("Failed to insert RFCOMM channel");
return Status::ResourceExhausted();
}
return RfcommChannel(connection_handle, channel_number, direction, this);
}
StatusWithMultiBuf RfcommManager::DoWrite(ConnectionHandle connection_handle,
uint8_t channel_number,
RfcommDirection direction,
multibuf::MultiBuf&& payload) {
std::lock_guard lock(connections_mutex_);
auto it = connections_.find(connection_handle);
if (it == connections_.end()) {
return {Status::NotFound(), std::move(payload)};
}
auto& conn_state = it->second;
auto channel_it =
conn_state.channels.find(MakeDlci(channel_number, direction));
if (channel_it == conn_state.channels.end()) {
return {Status::NotFound(), std::move(payload)};
}
return channel_it->second.Write(std::move(payload));
}
void RfcommManager::DeregisterAndCloseChannels(RfcommEvent event) {
PW_LOG_INFO("Deregistering and closing RFCOMM channels with event: %d",
static_cast<int>(event));
ConnectionMap connections_to_close(allocator_);
{
std::lock_guard lock(connections_mutex_);
connections_to_close.swap(connections_);
}
while (!connections_to_close.empty()) {
CloseConnectionState(
connections_to_close.take(connections_to_close.begin()), event);
}
}
Status RfcommManager::DoReleaseRfcommChannel(ConnectionHandle connection_handle,
uint8_t channel_number,
RfcommDirection direction) {
UniquePtr<ConnectionMap::node_type> conn_state_to_delete;
UniquePtr<ChannelMap::node_type> channel_to_close;
{
std::lock_guard lock(connections_mutex_);
auto it = connections_.find(connection_handle);
if (it == connections_.end()) {
return Status::NotFound();
}
auto& conn_state = it->second;
auto channel_it =
conn_state.channels.find(MakeDlci(channel_number, direction));
// If the channel is found, remove it from the map.
if (channel_it != conn_state.channels.end()) {
channel_to_close = conn_state.channels.take(channel_it);
}
// If this is the last RFCOMM channel for the connection, close the
// connection.
if (conn_state.channels.empty()) {
PW_LOG_INFO(
"Last RFCOMM channel closed for connection handle %hu. "
"Closing connection.",
static_cast<uint16_t>(conn_state.connection_handle));
conn_state_to_delete = connections_.take(it);
}
}
// Close the channel outside the lock.
if (channel_to_close != nullptr) {
channel_to_close->mapped().Close(RfcommEvent::kChannelClosedByOther);
}
return OkStatus();
}
Status RfcommManager::DoSendAdditionalRxCredits(
ConnectionHandle connection_handle,
uint8_t channel_number,
RfcommDirection direction,
uint8_t credits) {
std::lock_guard lock(connections_mutex_);
auto it = connections_.find(connection_handle);
if (it == connections_.end()) {
PW_LOG_WARN("Connection handle %hu not found",
static_cast<uint16_t>(connection_handle));
return Status::NotFound();
}
auto& conn_state = it->second;
auto channel_it =
conn_state.channels.find(MakeDlci(channel_number, direction));
if (channel_it == conn_state.channels.end()) {
PW_LOG_WARN("Channel %d (direction: %hhu) not found",
channel_number,
static_cast<uint8_t>(direction));
return Status::NotFound();
}
return channel_it->second.SendAdditionalRxCredits(credits);
}
void RfcommManager::CloseAllChannelsForConnection(
ConnectionHandle connection_handle, RfcommEvent event) {
UniquePtr<ConnectionMap::node_type> conn_state_to_close;
{
std::lock_guard lock(connections_mutex_);
auto it = connections_.find(connection_handle);
if (it != connections_.end()) {
conn_state_to_close = connections_.take(it);
}
}
if (conn_state_to_close != nullptr) {
CloseConnectionState(std::move(conn_state_to_close), event);
}
}
void RfcommManager::CloseConnectionState(
UniquePtr<ConnectionMap::node_type>&& conn_state_node, RfcommEvent event) {
if (conn_state_node == nullptr) {
return;
}
ChannelMap channels_to_close(allocator_);
channels_to_close.swap(conn_state_node->mapped().channels);
for (auto& [_, channel] : channels_to_close) {
channel.Close(event);
}
}
Result<emboss::RfcommFrameView> RfcommManager::ParseRfcommFrame(
ConstByteSpan pdu) {
// Parse the RFCOMM frame.
auto frame_view = emboss::MakeRfcommFrameView(pdu.data(), pdu.size());
if (!frame_view.Ok()) {
PW_LOG_WARN("Failed to parse RFCOMM frame.");
return Status::InvalidArgument();
}
// The FCS must be verified before we can trust the contents of the frame.
const uint8_t received_fcs = frame_view.fcs().Read();
// For UIH frames, FCS is calculated over the address and control fields.
// Otherwise FCS is calculated over the address, control, and length fields.
emboss::RfcommHeaderLength header_size_for_fcs =
emboss::RfcommHeaderLength::WITHOUT_LENGTH;
if (!frame_view.uih().Read()) {
if (frame_view.length_extended_flag().Read() ==
emboss::RfcommLengthExtended::EXTENDED) {
header_size_for_fcs = emboss::RfcommHeaderLength::WITH_EXTENDED_LENGTH;
} else {
header_size_for_fcs = emboss::RfcommHeaderLength::WITH_LENGTH;
}
}
const uint8_t calculated_fcs =
kRfcommCrc.Calculate(pdu.first(static_cast<size_t>(header_size_for_fcs)));
if (calculated_fcs != received_fcs) {
PW_LOG_WARN("RFCOMM FCS mismatch (expected: %02x, got: %02x)",
calculated_fcs,
received_fcs);
return Status::DataLoss();
}
return frame_view;
}
std::optional<multibuf::MultiBuf> RfcommManager::HandlePduFromController(
multibuf::MultiBuf&& pdu,
ConnectionHandle connection_handle,
uint16_t local_cid,
uint16_t remote_cid) {
UniquePtr<ConnectionMap::node_type> conn_state_to_delete;
UniquePtr<ChannelMap::node_type> channel_to_close;
uint8_t uih_credits = 0;
span<const uint8_t> uih_info_bytes;
std::optional<internal::BorrowedRfcommChannel> borrowed_channel;
{
std::lock_guard lock(connections_mutex_);
auto it = connections_.find(connection_handle);
if (it == connections_.end()) {
return std::move(pdu);
}
if (it->second.local_cid != local_cid ||
it->second.remote_cid != remote_cid) {
PW_LOG_ERROR("Received L2CAP PDU with mismatched CIDs for handle %hu",
static_cast<uint16_t>(connection_handle));
return std::move(pdu);
}
auto& conn_state = it->second;
auto contiguous_span = pdu.ContiguousSpan();
PW_CHECK(contiguous_span.has_value(),
"Received fragmented L2CAP PDU for handle %hu, which is not yet "
"supported",
static_cast<uint16_t>(connection_handle));
ConstByteSpan pdu_bytes = as_bytes(*contiguous_span);
auto parsed_frame_result = ParseRfcommFrame(pdu_bytes);
if (!parsed_frame_result.ok()) {
PW_LOG_WARN("Failed to parse RFCOMM frame.");
return std::move(pdu);
}
auto parsed_frame = std::move(parsed_frame_result).value();
const uint8_t channel_number =
static_cast<uint8_t>(parsed_frame.channel().Read());
const RfcommDirection direction = parsed_frame.direction().Read()
? RfcommDirection::kInitiator
: RfcommDirection::kResponder;
auto channel_it =
conn_state.channels.find(MakeDlci(channel_number, direction));
if (channel_it == conn_state.channels.end()) {
PW_LOG_DEBUG(
"Received RFCOMM PDU for unknown DLCI: (channel %u, direction %u, "
"size %zu), "
"forwarding to host.",
channel_number,
static_cast<uint8_t>(direction),
pdu_bytes.size());
return std::move(pdu);
}
internal::RfcommChannelInternal* channel = &channel_it->second;
// Handle the RFCOMM frame type.
emboss::RfcommFrameType frame_type = parsed_frame.control().Read();
if (parsed_frame.uih().Read()) {
borrowed_channel.emplace(*channel);
uih_credits = parsed_frame.credits().IsComplete()
? parsed_frame.credits().Read()
: 0;
uih_info_bytes = span<const uint8_t>(
parsed_frame.information().BackingStorage().data(),
parsed_frame.information().SizeInBytes());
} else if (frame_type == emboss::RfcommFrameType::DISCONNECT_MODE ||
frame_type ==
emboss::RfcommFrameType::DISCONNECT_MODE_AND_POLL_FINAL ||
frame_type == emboss::RfcommFrameType::DISCONNECT ||
frame_type ==
emboss::RfcommFrameType::DISCONNECT_AND_POLL_FINAL) {
PW_LOG_INFO("Channel number %u closed by remote", channel_number);
channel_to_close = conn_state.channels.take(channel_it);
if (conn_state.channels.empty()) {
PW_LOG_INFO(
"Last RFCOMM channel closed for connection handle %hu. "
"Closing connection.",
static_cast<uint16_t>(conn_state.connection_handle));
conn_state_to_delete = connections_.take(it);
}
}
}
if (borrowed_channel.has_value()) {
if (!borrowed_channel.value()->HandlePduFromController(
uih_credits, as_bytes(uih_info_bytes))) {
return std::nullopt;
}
}
if (channel_to_close != nullptr) {
channel_to_close->mapped().Close(RfcommEvent::kChannelClosedByRemote);
}
return std::move(pdu);
}
void RfcommManager::HandleL2capEvent(L2capChannelEvent event,
ConnectionHandle connection_handle) {
UniquePtr<ConnectionMap::node_type> conn_state_to_close;
{
std::lock_guard lock(connections_mutex_);
auto it = connections_.find(connection_handle);
// If the connection is not found, do nothing.
if (it == connections_.end()) {
return;
}
PW_LOG_INFO(
"RFCOMM connection received L2CAP event: %d for connection handle: %hu",
static_cast<int>(event),
static_cast<uint16_t>(connection_handle));
// If the L2CAP channel is closed by the other side or the connection is
// reset, close all RFCOMM channels for the connection.
if (event == L2capChannelEvent::kChannelClosedByOther ||
event == L2capChannelEvent::kReset) {
conn_state_to_close = connections_.take(it);
}
}
if (conn_state_to_close != nullptr) {
RfcommEvent rfcomm_event = event == L2capChannelEvent::kReset
? RfcommEvent::kReset
: RfcommEvent::kChannelClosedByOther;
CloseConnectionState(std::move(conn_state_to_close), rfcomm_event);
}
}
} // namespace pw::bluetooth::proxy::rfcomm