blob: 53b9f80624731460fa08b9882e76a86bdaccdc67 [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/l2cap_frames.emb.h"
#include "pw_bluetooth_proxy/gatt/gatt.h"
#include "pw_bluetooth_proxy/proxy_host.h"
#include "pw_bluetooth_proxy_private/test_utils.h"
#include "pw_containers/vector.h"
#include "pw_multibuf/from_span.h"
#include "pw_multibuf/multibuf.h"
#include "pw_span/cast.h"
#include "pw_span/span.h"
#include "pw_unit_test/framework.h"
namespace pw::bluetooth::proxy::gatt {
namespace {
constexpr ConnectionHandle kConnectionHandle1{0x0001};
constexpr AttributeHandle kAttributeHandle1{0x0001};
constexpr uint16_t kAttFixedChannelId =
static_cast<uint16_t>(emboss::L2capFixedCid::LE_U_ATTRIBUTE_PROTOCOL);
struct Notification {
ConnectionHandle connection_handle;
AttributeHandle value_handle;
multibuf::MultiBuf value;
};
class FakeClientDelegate final : public Client::Delegate {
public:
const Vector<Notification>& notifications() const { return notifications_; }
const Vector<std::pair<Error, ConnectionHandle>>& errors() const {
return errors_;
}
private:
void DoHandleNotification(ConnectionHandle connection_handle,
AttributeHandle value_handle,
multibuf::MultiBuf&& value) override {
notifications_.push_back(
{connection_handle, value_handle, std::move(value)});
}
void DoHandleError(Error error, ConnectionHandle connection_handle) override {
errors_.emplace_back(error, connection_handle);
}
Vector<Notification, 15> notifications_;
Vector<std::pair<Error, ConnectionHandle>, 10> errors_;
};
class FakeServerDelegate final : public Server::Delegate {
public:
FakeServerDelegate() = default;
FakeServerDelegate(Function<void(ConnectionHandle)> write_available_fn)
: write_available_fn_(std::move(write_available_fn)) {}
private:
void DoHandleWriteWithoutResponse(ConnectionHandle,
AttributeHandle,
multibuf::MultiBuf&&) override {}
void DoHandleWriteAvailable(ConnectionHandle connection_handle) override {
if (write_available_fn_) {
write_available_fn_(connection_handle);
}
}
void DoHandleError(Error /*error*/,
ConnectionHandle /*connection_handle*/) override {}
Function<void(ConnectionHandle)> write_available_fn_;
};
class GattIntegrationTest : public ProxyHostTest {};
TEST_F(GattIntegrationTest, ReceiveNotification) {
std::array<std::byte, 1024> packet_buffer;
allocator::test::AllocatorForTest<4096> allocator;
multibuf::SimpleAllocator multibuf_allocator{packet_buffer, allocator};
Function<void(H4PacketWithHci&&)> send_to_host_fn([](H4PacketWithHci&&) {});
Function<void(H4PacketWithH4&&)> send_to_controller_fn(
[](H4PacketWithH4&&) {});
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,
&allocator);
StartDispatcherOnCurrentThread(proxy);
PW_TEST_EXPECT_OK(SendLeReadBufferResponseFromController(proxy, 0));
PW_TEST_ASSERT_OK(
SendLeConnectionCompleteEvent(proxy,
cpp23::to_underlying(kConnectionHandle1),
emboss::StatusCode::SUCCESS));
Gatt gatt(proxy, allocator, multibuf_allocator);
FakeClientDelegate delegate;
Result<Client> client = gatt.CreateClient(kConnectionHandle1, delegate);
RunDispatcher();
PW_TEST_ASSERT_OK(client);
PW_TEST_ASSERT_OK(client->InterceptNotification(kAttributeHandle1));
uint8_t expected_value = 0x08;
std::array<uint8_t, 4> att_packet = {
// ATT header
0x1b, // opcode (notification)
0x01,
0x00, // kAttributeHandle1
expected_value // value
};
SendL2capBFrame(proxy,
cpp23::to_underlying(kConnectionHandle1),
att_packet,
att_packet.size(),
kAttFixedChannelId);
ASSERT_EQ(delegate.notifications().size(), 1u);
EXPECT_EQ(delegate.notifications()[0].connection_handle, kConnectionHandle1);
EXPECT_EQ(delegate.notifications()[0].value_handle, kAttributeHandle1);
auto contiguous = delegate.notifications()[0].value.ContiguousSpan();
span<const uint8_t> received_value = span_cast<const uint8_t>(*contiguous);
EXPECT_EQ(received_value[0], expected_value);
}
TEST_F(GattIntegrationTest, SendNotifications) {
std::array<std::byte, 1024> packet_buffer;
allocator::test::AllocatorForTest<5120> allocator;
multibuf::SimpleAllocator multibuf_allocator{packet_buffer, allocator};
struct {
Vector<H4PacketWithH4, 20> packets_sent_to_controller;
} capture;
Function<void(H4PacketWithH4&&)> send_to_controller_fn(
[&capture](H4PacketWithH4&& h4) {
capture.packets_sent_to_controller.emplace_back(std::move(h4));
});
Function<void(H4PacketWithHci&&)> send_to_host_fn([](H4PacketWithHci&&) {});
ProxyHost proxy = ProxyHost(std::move(send_to_host_fn),
std::move(send_to_controller_fn),
/*le_acl_credits_to_reserve=*/1,
/*br_edr_acl_credits_to_reserve=*/0,
&allocator);
StartDispatcherOnCurrentThread(proxy);
PW_TEST_EXPECT_OK(SendLeReadBufferResponseFromController(proxy, 1));
PW_TEST_ASSERT_OK(
SendLeConnectionCompleteEvent(proxy,
cpp23::to_underlying(kConnectionHandle1),
emboss::StatusCode::SUCCESS));
Gatt gatt(proxy, allocator, multibuf_allocator);
std::array<CharacteristicInfo, 1> characteristic_info = {
CharacteristicInfo{kAttributeHandle1}};
int write_available_count = 0;
auto write_available_fn = [&write_available_count](ConnectionHandle) {
++write_available_count;
};
FakeServerDelegate delegate(std::move(write_available_fn));
Result<Server> server =
gatt.CreateServer(kConnectionHandle1, characteristic_info, delegate);
PW_TEST_ASSERT_OK(server);
std::array<std::byte, 2> data = {std::byte{0x09}, std::byte{0x0a}};
auto mbuf_result = multibuf::FromSpan(allocator, data, [](ByteSpan) {});
ASSERT_TRUE(mbuf_result.has_value());
multibuf::MultiBuf buffer = std::move(*mbuf_result);
StatusWithMultiBuf send_result =
server->SendNotification(kAttributeHandle1, std::move(buffer));
PW_TEST_EXPECT_OK(send_result.status);
RunDispatcher();
ASSERT_EQ(capture.packets_sent_to_controller.size(), 1u);
std::array<uint8_t, 14> kExpected = {
0x02, // H4 indicator (ACL)
// ACL header
0x01,
0x00, // connection handle
0x09,
0x00, // data total length
// L2CAP header
0x05,
0x00, // length
0x04,
0x00, // channel ID (ATT)
// ATT header
0x1b, // opcode (ATT_HANDLE_VALUE_NTF)
0x01,
0x00, // attribute handle
0x09,
0x0a, // payload
};
auto sent = capture.packets_sent_to_controller.front().GetH4Span();
EXPECT_TRUE(
std::equal(sent.begin(), sent.end(), kExpected.begin(), kExpected.end()));
do {
mbuf_result = multibuf::FromSpan(allocator, data, [](ByteSpan) {});
ASSERT_TRUE(mbuf_result.has_value());
multibuf::MultiBuf temp_buffer = std::move(*mbuf_result);
send_result =
server->SendNotification(kAttributeHandle1, std::move(temp_buffer));
} while (send_result.status.ok());
EXPECT_EQ(write_available_count, 0);
PW_TEST_EXPECT_OK(SendNumberOfCompletedPackets(
proxy, {{cpp23::to_underlying(kConnectionHandle1), 1}}));
EXPECT_EQ(write_available_count, 1);
capture.packets_sent_to_controller.clear();
}
} // namespace
} // namespace pw::bluetooth::proxy::gatt