blob: 0ba8ad9233da605a1c941427f970088fd2c1d6ee [file]
// Copyright 2025 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_allocator/testing.h"
#include "pw_bluetooth_proxy/proxy_host.h"
#include "pw_bluetooth_proxy_private/test_utils.h"
namespace pw::bluetooth::proxy {
namespace {
constexpr uint16_t kConnectionHandle = 0x123;
constexpr uint16_t kRemoteChannelId = 0x1234;
constexpr uint16_t kLocalChannelId = 0x4321;
constexpr uint8_t kNumEventsSentToHost = 2u;
using SpanReceiveFn = L2capChannelManagerInterface::SpanReceiveFunction;
using MultiBufReceiveFn =
L2capChannelManagerInterface::OptionalBufferReceiveFunction;
constexpr std::array<uint8_t, 12> kTxH4Packet = {0x02, // H4 type (ACL)
// ACL header
0x23,
0x1, // connection handle
0x07,
0x00, // ACL length
// l2cap header
0x3,
00, // payload length
0x34,
0x12, // remote channel id
// payload
0x1,
0x2,
0x3};
constexpr std::array<uint8_t, 12> kRxAclPacket = {
// ACL header
0x23,
0x1, // connection handle
0x07,
0x00, // ACL length
// l2cap header
0x3,
00, // payload length
0x21,
0x43, // local channel id
// payload
0x1,
0x2,
0x3};
constexpr uint16_t kMaxAclPacketLength = 27;
constexpr std::array<uint8_t, 3> kExpectedPayload = {0x01, 0x02, 0x03};
constexpr std::array<std::byte, 3> kExpectedPayloadBytes = {
std::byte{0x01}, std::byte{0x02}, std::byte{0x03}};
class BasicModeChannelProxyTest : public ProxyHostTest {
public:
// ProxyHost is too large for the test fixture with the "light" test backend,
// so it has to be constructed on the test stack.
ProxyHost CreateProxy() {
Function<void(H4PacketWithHci&&)>&& send_to_host_fn(
[this](H4PacketWithHci&&) { ++sent_to_host_count_; });
Function<void(H4PacketWithH4&&)>&& send_to_controller_fn(
[this](H4PacketWithH4&& packet) {
++sent_to_controller_count_;
sent_to_controller_packets_.emplace_back(std::move(packet));
});
return ProxyHost(std::move(send_to_host_fn),
std::move(send_to_controller_fn),
/*le_acl_credits_to_reserve=*/10,
/*br_edr_acl_credits_to_reserve=*/0,
&allocator_);
}
void SendEvents(ProxyHost& proxy_host,
bool receive_read_buffer_response = true) {
StartDispatcherOnCurrentThread(proxy_host);
if (receive_read_buffer_response) {
PW_TEST_EXPECT_OK(SendLeReadBufferResponseFromController(
proxy_host,
/*num_credits_to_reserve=*/10,
/*le_acl_data_packet_length=*/kMaxAclPacketLength));
}
PW_TEST_EXPECT_OK(SendLeConnectionCompleteEvent(
proxy_host, kConnectionHandle, emboss::StatusCode::SUCCESS));
}
void TearDown() override {
sent_to_controller_packets_.clear();
ProxyHostTest::TearDown();
}
template <bool kConsumePayloads = false>
Result<UniquePtr<ChannelProxy>> CreateChannelWithSpanCallbacks(
ProxyHost& proxy_host) {
SpanReceiveFn from_controller_fn = [this](
ConstByteSpan payload,
ConnectionHandle connection_handle,
uint16_t local_channel_id,
uint16_t remote_channel_id) -> bool {
++payloads_from_controller_count_;
EXPECT_EQ(connection_handle, ConnectionHandle{kConnectionHandle});
EXPECT_EQ(local_channel_id, kLocalChannelId);
EXPECT_EQ(remote_channel_id, kRemoteChannelId);
EXPECT_TRUE(std::equal(payload.begin(),
payload.end(),
kExpectedPayloadBytes.begin(),
kExpectedPayloadBytes.end()));
return kConsumePayloads;
};
SpanReceiveFn from_host_fn = [this](ConstByteSpan payload,
ConnectionHandle connection_handle,
uint16_t local_channel_id,
uint16_t remote_channel_id) -> bool {
++payloads_from_host_count_;
EXPECT_EQ(connection_handle, ConnectionHandle{kConnectionHandle});
EXPECT_EQ(local_channel_id, kLocalChannelId);
EXPECT_EQ(remote_channel_id, kRemoteChannelId);
EXPECT_TRUE(std::equal(payload.begin(),
payload.end(),
kExpectedPayloadBytes.begin(),
kExpectedPayloadBytes.end()));
return kConsumePayloads;
};
return proxy_host.InterceptBasicModeChannel(
ConnectionHandle{kConnectionHandle},
kLocalChannelId,
kRemoteChannelId,
AclTransportType::kLe,
std::move(from_controller_fn),
std::move(from_host_fn),
[this](L2capChannelEvent event) { events_.push_back(event); });
}
template <bool kConsumePayloads = false>
Result<UniquePtr<ChannelProxy>> CreateChannelWithMultiBufCallbacks(
ProxyHost& proxy_host) {
MultiBufReceiveFn from_controllerr_fn =
[this](
multibuf::MultiBuf&& payload,
ConnectionHandle connection_handle,
uint16_t local_channel_id,
uint16_t remote_channel_id) -> std::optional<multibuf::MultiBuf> {
++payloads_from_controller_count_;
EXPECT_EQ(connection_handle, ConnectionHandle{kConnectionHandle});
EXPECT_EQ(local_channel_id, kLocalChannelId);
EXPECT_EQ(remote_channel_id, kRemoteChannelId);
EXPECT_TRUE(std::equal(payload.begin(),
payload.end(),
kExpectedPayloadBytes.begin(),
kExpectedPayloadBytes.end()));
if (kConsumePayloads) {
return std::nullopt;
}
return std::move(payload);
};
MultiBufReceiveFn from_host_fn =
[this](
multibuf::MultiBuf&& payload,
ConnectionHandle connection_handle,
uint16_t local_channel_id,
uint16_t remote_channel_id) -> std::optional<multibuf::MultiBuf> {
++payloads_from_host_count_;
EXPECT_EQ(connection_handle, ConnectionHandle{kConnectionHandle});
EXPECT_EQ(local_channel_id, kLocalChannelId);
EXPECT_EQ(remote_channel_id, kRemoteChannelId);
EXPECT_TRUE(std::equal(payload.begin(),
payload.end(),
kExpectedPayloadBytes.begin(),
kExpectedPayloadBytes.end()));
if (kConsumePayloads) {
return std::nullopt;
}
return std::move(payload);
};
return proxy_host.InterceptBasicModeChannel(
ConnectionHandle{kConnectionHandle},
kLocalChannelId,
kRemoteChannelId,
AclTransportType::kLe,
std::move(from_controllerr_fn),
std::move(from_host_fn),
[this](L2capChannelEvent event) { events_.push_back(event); });
}
void ExhaustAllocator() { allocator_.Exhaust(); }
int sent_to_controller_count() { return sent_to_controller_count_; }
int sent_to_host_count() { return sent_to_host_count_; }
const Vector<H4PacketWithH4>& sent_to_controller_packets() const {
return sent_to_controller_packets_;
}
int payloads_from_controller_count() const {
return payloads_from_controller_count_;
}
int payloads_from_host_count() const { return payloads_from_host_count_; }
Vector<L2capChannelEvent>& events() { return events_; }
private:
allocator::test::AllocatorForTest<4000> allocator_;
int sent_to_controller_count_ = 0;
Vector<H4PacketWithH4, 5> sent_to_controller_packets_;
int sent_to_host_count_ = 0;
int payloads_from_controller_count_ = 0;
int payloads_from_host_count_ = 0;
Vector<L2capChannelEvent, 5> events_;
};
TEST_F(BasicModeChannelProxyTest, WriteSuccess) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithSpanCallbacks(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
std::array<uint8_t, 3> payload = kExpectedPayload;
multibuf::MultiBuf mbuf = MultiBufFromSpan(span(payload));
PW_TEST_EXPECT_OK(channel->IsWriteAvailable());
PW_TEST_EXPECT_OK(channel->Write(std::move(mbuf)).status);
RunDispatcher();
EXPECT_EQ(sent_to_controller_count(), 1);
span<const uint8_t> sent_packet = sent_to_controller_packets()[0].GetH4Span();
EXPECT_TRUE(std::equal(sent_packet.begin(),
sent_packet.end(),
kTxH4Packet.begin(),
kTxH4Packet.end()));
TearDown();
}
TEST_F(BasicModeChannelProxyTest,
WriteUntilQueueFullThenGetWriteAvailableEvent) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithSpanCallbacks(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
Status write_status = OkStatus();
while (write_status.ok()) {
std::array<uint8_t, 3> payload = kExpectedPayload;
multibuf::MultiBuf mbuf = MultiBufFromSpan(span(payload));
write_status = channel->Write(std::move(mbuf)).status;
RunDispatcher();
}
EXPECT_EQ(channel->IsWriteAvailable(), Status::Unavailable());
PW_TEST_EXPECT_OK(
SendNumberOfCompletedPackets(proxy, {{kConnectionHandle, 1}}));
ASSERT_EQ(events().size(), 1u);
EXPECT_EQ(events().back(), L2capChannelEvent::kWriteAvailable);
PW_TEST_EXPECT_OK(channel->IsWriteAvailable());
TearDown();
}
TEST_F(BasicModeChannelProxyTest, ReceiveSpanFromControllerAndConsume) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithSpanCallbacks</*kConsumePayloads=*/true>(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
std::array<uint8_t, 12> rx_acl = kRxAclPacket;
H4PacketWithHci h4_packet(emboss::H4PacketType::ACL_DATA, rx_acl);
proxy.HandleH4HciFromController(std::move(h4_packet));
EXPECT_EQ(payloads_from_controller_count(), 1);
EXPECT_EQ(sent_to_host_count(), kNumEventsSentToHost);
TearDown();
}
TEST_F(BasicModeChannelProxyTest, ReceiveSpanFromControllerAndDoNotConsume) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithSpanCallbacks(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
auto acl_packet = kRxAclPacket;
H4PacketWithHci h4_packet(emboss::H4PacketType::ACL_DATA, acl_packet);
proxy.HandleH4HciFromController(std::move(h4_packet));
EXPECT_EQ(payloads_from_controller_count(), 1);
EXPECT_EQ(sent_to_host_count(), 1 + kNumEventsSentToHost);
TearDown();
}
TEST_F(BasicModeChannelProxyTest, ReceiveMultiBufFromControllerAndConsume) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithMultiBufCallbacks</*kConsumePayloads=*/true>(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
std::array<uint8_t, 12> rx_acl = kRxAclPacket;
H4PacketWithHci h4_packet(emboss::H4PacketType::ACL_DATA, rx_acl);
proxy.HandleH4HciFromController(std::move(h4_packet));
EXPECT_EQ(payloads_from_controller_count(), 1);
EXPECT_EQ(sent_to_host_count(), kNumEventsSentToHost);
TearDown();
}
TEST_F(BasicModeChannelProxyTest,
ReceiveMultiBufFromControllerAndDoNotConsume) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithMultiBufCallbacks(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
auto acl_packet = kRxAclPacket;
H4PacketWithHci h4_packet(emboss::H4PacketType::ACL_DATA, acl_packet);
proxy.HandleH4HciFromController(std::move(h4_packet));
EXPECT_EQ(payloads_from_controller_count(), 1);
EXPECT_EQ(sent_to_host_count(), 1 + kNumEventsSentToHost);
TearDown();
}
TEST_F(BasicModeChannelProxyTest, ReceiveSpanFromHostAndConsume) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithSpanCallbacks</*kConsumePayloads=*/true>(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
std::array<uint8_t, 12> h4_data = kTxH4Packet;
H4PacketWithH4 h4_packet(h4_data);
proxy.HandleH4HciFromHost(std::move(h4_packet));
EXPECT_EQ(payloads_from_host_count(), 1);
EXPECT_EQ(sent_to_controller_count(), 0);
TearDown();
}
TEST_F(BasicModeChannelProxyTest, ReceiveSpanFromHostAndDoNotConsume) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithSpanCallbacks(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
std::array<uint8_t, 12> h4_data = kTxH4Packet;
H4PacketWithH4 h4_packet(h4_data);
proxy.HandleH4HciFromHost(std::move(h4_packet));
EXPECT_EQ(payloads_from_host_count(), 1);
EXPECT_EQ(sent_to_controller_count(), 1);
TearDown();
}
TEST_F(BasicModeChannelProxyTest, ReceiveMultiBufFromHostAndConsume) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithMultiBufCallbacks</*kConsumePayloads=*/true>(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
std::array<uint8_t, 12> h4_data = kTxH4Packet;
H4PacketWithH4 h4_packet(h4_data);
proxy.HandleH4HciFromHost(std::move(h4_packet));
EXPECT_EQ(payloads_from_host_count(), 1);
EXPECT_EQ(sent_to_controller_count(), 0);
TearDown();
}
TEST_F(BasicModeChannelProxyTest, ReceiveMultiBufFromHostAndDoNotConsume) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithMultiBufCallbacks(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
std::array<uint8_t, 12> h4_data = kTxH4Packet;
H4PacketWithH4 h4_packet(h4_data);
proxy.HandleH4HciFromHost(std::move(h4_packet));
EXPECT_EQ(payloads_from_host_count(), 1);
EXPECT_EQ(sent_to_controller_count(), 1);
TearDown();
}
TEST_F(BasicModeChannelProxyTest, ChannelAllocationFails) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
ExhaustAllocator();
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithMultiBufCallbacks(proxy);
EXPECT_EQ(channel_result.status(), Status::ResourceExhausted());
TearDown();
}
TEST_F(BasicModeChannelProxyTest, InvalidConnectionHandle) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
proxy.InterceptBasicModeChannel(
ConnectionHandle{1337},
kLocalChannelId,
kRemoteChannelId,
AclTransportType::kLe,
[](auto, auto, auto, auto) { return false; },
[](auto, auto, auto, auto) { return false; },
[](L2capChannelEvent) {});
EXPECT_EQ(channel_result.status(), Status::InvalidArgument());
TearDown();
}
TEST_F(BasicModeChannelProxyTest, SendRxCreditFails) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithMultiBufCallbacks(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
Status status = channel->SendAdditionalRxCredits(1);
EXPECT_EQ(status, Status::Unimplemented());
TearDown();
}
TEST_F(BasicModeChannelProxyTest, ChannelClosedByOther) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithMultiBufCallbacks(proxy);
PW_TEST_ASSERT_OK(channel_result);
PW_TEST_EXPECT_OK(SendL2capDisconnectRsp(proxy,
Direction::kFromController,
AclTransportType::kLe,
kConnectionHandle,
kLocalChannelId,
kRemoteChannelId));
ASSERT_EQ(events().size(), 1u);
EXPECT_EQ(events().back(), L2capChannelEvent::kChannelClosedByOther);
TearDown();
}
TEST_F(BasicModeChannelProxyTest, CheckWriteParameterFailsPayloadTooLarge) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithSpanCallbacks(proxy);
PW_TEST_ASSERT_OK(channel_result);
UniquePtr<ChannelProxy> channel = std::move(channel_result.value());
std::array<uint8_t, kMaxAclPacketLength> payload = {};
payload.fill(0xFF);
multibuf::MultiBuf mbuf = MultiBufFromSpan(span(payload));
PW_TEST_EXPECT_OK(channel->IsWriteAvailable());
EXPECT_EQ(channel->Write(std::move(mbuf)).status, Status::InvalidArgument());
RunDispatcher();
EXPECT_EQ(sent_to_controller_count(), 0);
TearDown();
}
TEST_F(BasicModeChannelProxyTest, CheckWriteParameterFailsBufferSizeUnknown) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy, /*receive_read_buffer_response=*/false);
Result<UniquePtr<ChannelProxy>> channel_result =
CreateChannelWithSpanCallbacks(proxy);
EXPECT_EQ(channel_result.status(), Status::FailedPrecondition());
TearDown();
}
TEST_F(BasicModeChannelProxyTest, RegisterExistingChannelFailsAlreadyExists) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
Result<UniquePtr<ChannelProxy>> channel1_result =
CreateChannelWithSpanCallbacks(proxy);
PW_TEST_ASSERT_OK(channel1_result);
// Attempting to intercept again with the same parameters while channel1 is
// active should fail with AlreadyExists.
Result<UniquePtr<ChannelProxy>> channel2_result =
CreateChannelWithSpanCallbacks(proxy);
EXPECT_EQ(channel2_result.status(), Status::AlreadyExists());
TearDown();
}
TEST_F(BasicModeChannelProxyTest, ReplaceStaleChannelSuccess) {
ProxyHost proxy = CreateProxy();
SendEvents(proxy);
// Create first channel.
Result<UniquePtr<ChannelProxy>> channel1_result =
CreateChannelWithSpanCallbacks(proxy);
PW_TEST_ASSERT_OK(channel1_result);
// Destroy the client channel so that the underlying internal channel becomes
// stale.
channel1_result.value().Reset();
// Re-intercepting with the same parameters should replace the stale channel.
Result<UniquePtr<ChannelProxy>> channel2_result =
CreateChannelWithSpanCallbacks(proxy);
PW_TEST_ASSERT_OK(channel2_result);
TearDown();
}
} // namespace
} // namespace pw::bluetooth::proxy