Skip to content

Commit

Permalink
pw_bluetooth_proxy: Add host to controller callback packet sniffing
Browse files Browse the repository at this point in the history
This change allows clients of the pw_bluetooth_proxy to register
a callback function for inspecting host-to-controller L2CAP
basic channel packets.

Bug: 390191420
Change-Id: Ic6dd8d196205d8751d097731ba46a12b869d5ca2
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/260553
Commit-Queue: Charlie Boutier <[email protected]>
Reviewed-by: Ali Saeed <[email protected]>
Lint: Lint 🤖 <[email protected]>
Reviewed-by: Ben Lawson <[email protected]>
Reviewed-by: Austin Foxley <[email protected]>
Docs-Not-Needed: Charlie Boutier <[email protected]>
  • Loading branch information
SilverBzH authored and CQ Bot Account committed Jan 22, 2025
1 parent 74fb2fc commit c20f1e9
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 5 deletions.
21 changes: 18 additions & 3 deletions pw_bluetooth_proxy/basic_l2cap_channel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pw::Result<BasicL2capChannel> BasicL2capChannel::Create(
uint16_t local_cid,
uint16_t remote_cid,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_controller_fn,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_host_fn,
Function<void(L2capChannelEvent event)>&& event_fn) {
if (!AreValidParameters(/*connection_handle=*/connection_handle,
/*local_cid=*/local_cid,
Expand All @@ -44,6 +45,7 @@ pw::Result<BasicL2capChannel> BasicL2capChannel::Create(
/*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));
}

Expand Down Expand Up @@ -98,6 +100,7 @@ BasicL2capChannel::BasicL2capChannel(
uint16_t local_cid,
uint16_t remote_cid,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_controller_fn,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_host_fn,
Function<void(L2capChannelEvent event)>&& event_fn)
: L2capChannel(
/*l2cap_channel_manager=*/l2cap_channel_manager,
Expand All @@ -106,6 +109,7 @@ BasicL2capChannel::BasicL2capChannel(
/*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_LOG_INFO("btproxy: BasicL2capChannel ctor");
}
Expand Down Expand Up @@ -133,9 +137,20 @@ bool BasicL2capChannel::DoHandlePduFromController(pw::span<uint8_t> bframe) {
bframe_view->payload().SizeInBytes()));
}

bool BasicL2capChannel::HandlePduFromHost(pw::span<uint8_t>) {
// Always forward to controller.
return false;
bool BasicL2capChannel::HandlePduFromHost(pw::span<uint8_t> bframe) {
Result<emboss::BFrameWriter> bframe_view =
MakeEmbossWriter<emboss::BFrameWriter>(bframe);

if (!bframe_view.ok()) {
// TODO: https://pwbug.dev/360929142 - Stop channel on error.
PW_LOG_ERROR("(CID: 0x%X) Host transmitted invalid B-frame. So will drop.",
local_cid());
return true;
}

return SendPayloadFromHostToClient(
span(bframe_view->payload().BackingStorage().data(),
bframe_view->payload().SizeInBytes()));
}

} // namespace pw::bluetooth::proxy
1 change: 1 addition & 0 deletions pw_bluetooth_proxy/gatt_notify_channel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ GattNotifyChannel::GattNotifyChannel(L2capChannelManager& l2cap_channel_manager,
/*local_cid=*/kAttributeProtocolCID,
/*remote_cid=*/kAttributeProtocolCID,
/*payload_from_controller_fn=*/nullptr,
/*payload_from_host_fn=*/nullptr,
/*event_fn=*/nullptr),
attribute_handle_(attribute_handle) {}

Expand Down
5 changes: 4 additions & 1 deletion pw_bluetooth_proxy/l2cap_channel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void L2capChannel::MoveFields(L2capChannel& other) {
remote_cid_ = other.remote_cid();
event_fn_ = std::move(other.event_fn_);
payload_from_controller_fn_ = std::move(other.payload_from_controller_fn_);
payload_from_host_fn_ = std::move(other.payload_from_host_fn_);
{
std::lock_guard lock(send_queue_mutex_);
std::lock_guard other_lock(other.send_queue_mutex_);
Expand Down Expand Up @@ -315,6 +316,7 @@ L2capChannel::L2capChannel(
uint16_t local_cid,
uint16_t remote_cid,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_controller_fn,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_host_fn,
Function<void(L2capChannelEvent event)>&& event_fn)
: l2cap_channel_manager_(l2cap_channel_manager),
state_(State::kRunning),
Expand All @@ -323,7 +325,8 @@ L2capChannel::L2capChannel(
local_cid_(local_cid),
remote_cid_(remote_cid),
event_fn_(std::move(event_fn)),
payload_from_controller_fn_(std::move(payload_from_controller_fn)) {
payload_from_controller_fn_(std::move(payload_from_controller_fn)),
payload_from_host_fn_(std::move(payload_from_host_fn)) {
PW_LOG_INFO(
"btproxy: L2capChannel ctor - transport_: %u, connection_handle_ : %u, "
"local_cid_ : %#x, remote_cid_: %#x",
Expand Down
1 change: 1 addition & 0 deletions pw_bluetooth_proxy/l2cap_coc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ L2capCoc::L2capCoc(
/*local_cid=*/rx_config.cid,
/*remote_cid=*/tx_config.cid,
/*payload_from_controller_fn=*/nullptr,
/*payload_from_host_fn=*/nullptr,
/*event_fn=*/std::move(event_fn)),
rx_multibuf_allocator_(rx_multibuf_allocator),
signaling_channel_(signaling_channel),
Expand Down
1 change: 1 addition & 0 deletions pw_bluetooth_proxy/l2cap_signaling_channel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ L2capSignalingChannel::L2capSignalingChannel(
/*local_cid=*/fixed_cid,
/*remote_cid=*/fixed_cid,
/*payload_from_controller_fn=*/nullptr,
/*payload_from_host_fn=*/nullptr,
/*event_fn=*/nullptr),
l2cap_channel_manager_(l2cap_channel_manager) {}

Expand Down
18 changes: 18 additions & 0 deletions pw_bluetooth_proxy/proxy_host.cc
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ pw::Result<BasicL2capChannel> ProxyHost::AcquireBasicL2capChannel(
uint16_t remote_cid,
AclTransportType transport,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_controller_fn,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_host_fn,
Function<void(L2capChannelEvent event)>&& event_fn) {
Status status =
acl_data_channel_.CreateAclConnection(connection_handle, transport);
Expand All @@ -540,9 +541,26 @@ pw::Result<BasicL2capChannel> ProxyHost::AcquireBasicL2capChannel(
/*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<BasicL2capChannel> ProxyHost::AcquireBasicL2capChannel(
uint16_t connection_handle,
uint16_t local_cid,
uint16_t remote_cid,
AclTransportType transport,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_controller_fn,
Function<void(L2capChannelEvent event)>&& event_fn) {
return AcquireBasicL2capChannel(connection_handle,
local_cid,
remote_cid,
transport,
std::move(payload_from_controller_fn),
nullptr,
std::move(event_fn));
}

namespace {

pw::Result<GattNotifyChannel> CreateGattNotifyChannel(
Expand Down
75 changes: 75 additions & 0 deletions pw_bluetooth_proxy/proxy_host_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2446,6 +2446,81 @@ TEST_F(BasicL2capChannelTest, BasicForward) {
EXPECT_EQ(capture.to_host_called, 1);
}

TEST_F(BasicL2capChannelTest, ReadPacketToController) {
struct {
int sends_called = 0;
int from_host_called = 0;
std::array<uint8_t, 3> expected_payload = {0xAB, 0xCD, 0xEF};
std::array<uint8_t,
emboss::AclDataFrameHeader::IntrinsicSizeInBytes() +
emboss::BasicL2capHeader::IntrinsicSizeInBytes() + 3>
hci_arr{};
} capture;

std::array<uint8_t, sizeof(emboss::H4PacketType) + capture.hci_arr.size()>
h4_arr;
h4_arr[0] = cpp23::to_underlying(emboss::H4PacketType::ACL_DATA);
H4PacketWithH4 h4_packet{h4_arr};

pw::Function<void(H4PacketWithHci && packet)>&& send_to_host_fn(
[](H4PacketWithHci&&) {});
pw::Function<void(H4PacketWithH4 && packet)>&& send_to_controller_fn(
[&capture](H4PacketWithH4&& packet) {
++capture.from_host_called;
EXPECT_TRUE(std::equal(packet.GetHciSpan().begin(),
packet.GetHciSpan().end(),
capture.hci_arr.begin(),
capture.hci_arr.end()));
});
ProxyHost proxy = ProxyHost(std::move(send_to_host_fn),
std::move(send_to_controller_fn),
/*le_acl_credits_to_reserve=*/0,
/*br_edr_acl_credits_to_reserve=*/0);
uint16_t handle = 0x334;
uint16_t local_cid = 0x443;
uint16_t remote_cid = 0x123;
PW_TEST_ASSERT_OK_AND_ASSIGN(BasicL2capChannel channel,
proxy.AcquireBasicL2capChannel(
/*connection_handle=*/handle,
/*local_cid=*/local_cid,
/*remote_cid=*/remote_cid,
/*transport=*/AclTransportType::kBrEdr,
/*payload_from_controller_fn=*/nullptr,
/*payload_from_host_fn=*/
[&capture](pw::span<uint8_t>) {
++capture.sends_called;
return false;
},
/*event_fn=*/nullptr));

Result<emboss::AclDataFrameWriter> acl =
MakeEmbossWriter<emboss::AclDataFrameWriter>(capture.hci_arr);
acl->header().handle().Write(handle);
acl->data_total_length().Write(
emboss::BasicL2capHeader::IntrinsicSizeInBytes() +
capture.expected_payload.size());

emboss::BasicL2capHeaderWriter l2cap_header =
emboss::MakeBasicL2capHeaderView(
acl->payload().BackingStorage().data(),
acl->payload().BackingStorage().SizeInBytes());
l2cap_header.pdu_length().Write(capture.expected_payload.size());
l2cap_header.channel_id().Write(remote_cid);

std::copy(capture.expected_payload.begin(),
capture.expected_payload.end(),
capture.hci_arr.begin() +
emboss::AclDataFrameHeader::IntrinsicSizeInBytes() +
emboss::BasicL2capHeader::IntrinsicSizeInBytes());

std::copy(capture.hci_arr.begin(), capture.hci_arr.end(), h4_arr.begin() + 1);

proxy.HandleH4HciFromHost(std::move(h4_packet));

EXPECT_EQ(capture.from_host_called, 1);
EXPECT_EQ(capture.sends_called, 1);
}

// ########## L2capSignalingTest

class L2capSignalingTest : public ProxyHostTest {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class BasicL2capChannel : public L2capChannel {
uint16_t local_cid,
uint16_t remote_cid,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_controller_fn,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_host_fn,
Function<void(L2capChannelEvent event)>&& event_fn);

BasicL2capChannel(const BasicL2capChannel& other) = delete;
Expand All @@ -50,6 +51,7 @@ class BasicL2capChannel : public L2capChannel {
uint16_t local_cid,
uint16_t remote_cid,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_controller_fn,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_host_fn,
Function<void(L2capChannelEvent event)>&& event_fn);

bool HandlePduFromHost(pw::span<uint8_t> bframe) override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ class L2capChannel : public IntrusiveForwardList<L2capChannel>::Item {
uint16_t local_cid,
uint16_t remote_cid,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_controller_fn,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_host_fn,
Function<void(L2capChannelEvent event)>&& event_fn);

// Returns whether or not ACL connection handle & L2CAP channel identifiers
Expand Down Expand Up @@ -298,6 +299,14 @@ class L2capChannel : public IntrusiveForwardList<L2capChannel>::Item {
// Remove all packets from queue.
void ClearQueue();

// Returns false if payload should be forwarded to controller instead.
virtual bool SendPayloadFromHostToClient(pw::span<uint8_t> payload) {
if (payload_from_host_fn_) {
return payload_from_host_fn_(payload);
}
return false;
}

//-------
// Rx (protected)
//-------
Expand Down Expand Up @@ -430,6 +439,8 @@ class L2capChannel : public IntrusiveForwardList<L2capChannel>::Item {

// Client-provided controller read callback.
pw::Function<bool(pw::span<uint8_t> payload)> payload_from_controller_fn_;
// Client-provided host read callback.
pw::Function<bool(pw::span<uint8_t> payload)> payload_from_host_fn_;
};

} // namespace pw::bluetooth::proxy
45 changes: 45 additions & 0 deletions pw_bluetooth_proxy/public/pw_bluetooth_proxy/proxy_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,50 @@ class ProxyHost {
uint16_t local_cid,
uint16_t additional_rx_credits);

/// Returns an L2CAP channel operating in basic mode that supports writing to
/// and reading from a remote peer.
///
/// @param[in] connection_handle The connection handle of the remote
/// peer.
///
/// @param[in] local_cid L2CAP channel ID of the local
/// endpoint.
///
/// @param[in] remote_cid L2CAP channel ID of the remote
/// endpoint.
///
/// @param[in] transport Logical link transport type.
///
/// @param[in] payload_from_controller_fn Read callback to be invoked on Rx
/// SDUs. Return value of false
/// indicates the packet should be
/// forwarded on to host.
///
/// @param[in] payload_from_host_fn Read callback to be invoked on Tx
/// SDUs. Return value of false
/// indicates the packet should be
/// forwarded on to the controller.
///
/// @param[in] event_fn Handle asynchronous events such as
/// errors encountered by the channel.
/// See `l2cap_channel_common.h`.
///
/// @returns @rst
///
/// .. pw-status-codes::
/// INVALID_ARGUMENT: If arguments are invalid (check logs).
/// UNAVAILABLE: If channel could not be created because no memory was
/// available to accommodate an additional ACL connection.
/// @endrst
pw::Result<BasicL2capChannel> AcquireBasicL2capChannel(
uint16_t connection_handle,
uint16_t local_cid,
uint16_t remote_cid,
AclTransportType transport,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_controller_fn,
Function<bool(pw::span<uint8_t> payload)>&& payload_from_host_fn,
Function<void(L2capChannelEvent event)>&& event_fn);

/// Returns an L2CAP channel operating in basic mode that supports writing to
/// and reading from a remote peer.
///
Expand Down Expand Up @@ -204,6 +248,7 @@ class ProxyHost {
/// UNAVAILABLE: If channel could not be created because no memory was
/// available to accommodate an additional ACL connection.
/// @endrst
/// @deprecated
pw::Result<BasicL2capChannel> AcquireBasicL2capChannel(
uint16_t connection_handle,
uint16_t local_cid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ class RfcommChannel final : public L2capChannel {
///
/// @param[in] channel_number RFCOMM channel number to use.
///
/// @param[in] receive_fn Read callback to be invoked on Rx frames.
/// @param[in] payload_from_controller_fn Read callback to be invoked
/// on Rx frames.
///
/// @returns @rst
///
Expand Down
1 change: 1 addition & 0 deletions pw_bluetooth_proxy/rfcomm_channel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ RfcommChannel::RfcommChannel(
/*local_cid=*/rx_config.cid,
/*remote_cid=*/tx_config.cid,
/*payload_from_controller_fn=*/nullptr,
/*payload_from_host_fn=*/nullptr,
/*event_fn=*/std::move(event_fn)),
rx_config_(rx_config),
tx_config_(tx_config),
Expand Down

0 comments on commit c20f1e9

Please sign in to comment.