pw_bluetooth_sapphire: Acquire wake leases in SignalingChannel
Acquire wake leases while commands are pending in SignalingChannel.
Bug: 408059126
Change-Id: Ie4ffce7bae78a95ec0edc67c3a20b9536f463d7c
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/280974
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jason Graffius <jgraff@google.com>
Docs-Not-Needed: Ben Lawson <benlawson@google.com>
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Commit-Queue: Ben Lawson <benlawson@google.com>
diff --git a/pw_bluetooth_sapphire/host/l2cap/BUILD.bazel b/pw_bluetooth_sapphire/host/l2cap/BUILD.bazel
index 2cd2acd..31284b5 100644
--- a/pw_bluetooth_sapphire/host/l2cap/BUILD.bazel
+++ b/pw_bluetooth_sapphire/host/l2cap/BUILD.bazel
@@ -257,6 +257,7 @@
deps = [
":l2cap",
":testing",
+ "//pw_bluetooth_sapphire:null_lease_provider",
"//pw_random:fuzzer_generator",
],
)
diff --git a/pw_bluetooth_sapphire/host/l2cap/BUILD.gn b/pw_bluetooth_sapphire/host/l2cap/BUILD.gn
index 6ae18aa..9a7dfc1 100644
--- a/pw_bluetooth_sapphire/host/l2cap/BUILD.gn
+++ b/pw_bluetooth_sapphire/host/l2cap/BUILD.gn
@@ -232,6 +232,7 @@
deps = [
":l2cap",
":testing",
+ "$dir_pw_bluetooth_sapphire:null_lease_provider",
"$dir_pw_random:fuzzer_generator",
]
}
diff --git a/pw_bluetooth_sapphire/host/l2cap/bredr_dynamic_channel_registry_fuzztest.cc b/pw_bluetooth_sapphire/host/l2cap/bredr_dynamic_channel_registry_fuzztest.cc
index f710733..5579ec6 100644
--- a/pw_bluetooth_sapphire/host/l2cap/bredr_dynamic_channel_registry_fuzztest.cc
+++ b/pw_bluetooth_sapphire/host/l2cap/bredr_dynamic_channel_registry_fuzztest.cc
@@ -21,6 +21,7 @@
#include "pw_bluetooth_sapphire/internal/host/l2cap/bredr_dynamic_channel.h"
#include "pw_bluetooth_sapphire/internal/host/l2cap/bredr_signaling_channel.h"
#include "pw_bluetooth_sapphire/internal/host/l2cap/fake_channel.h"
+#include "pw_bluetooth_sapphire/null_lease_provider.h"
constexpr static bt::hci_spec::ConnectionHandle kTestHandle = 0x0001;
@@ -49,6 +50,8 @@
// Dispatcher needed for signaling channel response timeout.
pw::async::test::FakeDispatcher dispatcher;
+ pw::bluetooth_sapphire::NullLeaseProvider lease_provider;
+
auto fake_chan = std::make_unique<bt::l2cap::testing::FakeChannel>(
bt::l2cap::kSignalingChannelId,
bt::l2cap::kSignalingChannelId,
@@ -61,7 +64,8 @@
bt::l2cap::internal::BrEdrSignalingChannel sig_chan(
fake_chan->GetWeakPtr(),
pw::bluetooth::emboss::ConnectionRole::CENTRAL,
- dispatcher);
+ dispatcher,
+ lease_provider);
auto open_cb = []([[maybe_unused]] auto chan) {};
auto close_cb = []([[maybe_unused]] auto chan) {};
diff --git a/pw_bluetooth_sapphire/host/l2cap/bredr_signaling_channel.cc b/pw_bluetooth_sapphire/host/l2cap/bredr_signaling_channel.cc
index d836fff..84a37ad 100644
--- a/pw_bluetooth_sapphire/host/l2cap/bredr_signaling_channel.cc
+++ b/pw_bluetooth_sapphire/host/l2cap/bredr_signaling_channel.cc
@@ -24,8 +24,9 @@
BrEdrSignalingChannel::BrEdrSignalingChannel(
Channel::WeakPtr chan,
pw::bluetooth::emboss::ConnectionRole role,
- pw::async::Dispatcher& dispatcher)
- : SignalingChannel(std::move(chan), role, dispatcher) {
+ pw::async::Dispatcher& dispatcher,
+ pw::bluetooth_sapphire::LeaseProvider& wake_lease_provider)
+ : SignalingChannel(std::move(chan), role, dispatcher, wake_lease_provider) {
set_mtu(kDefaultMTU);
// Add default handler for incoming Echo Request commands.
diff --git a/pw_bluetooth_sapphire/host/l2cap/bredr_signaling_channel_test.cc b/pw_bluetooth_sapphire/host/l2cap/bredr_signaling_channel_test.cc
index 3273432..dca64b2 100644
--- a/pw_bluetooth_sapphire/host/l2cap/bredr_signaling_channel_test.cc
+++ b/pw_bluetooth_sapphire/host/l2cap/bredr_signaling_channel_test.cc
@@ -14,6 +14,7 @@
#include "pw_bluetooth_sapphire/internal/host/l2cap/bredr_signaling_channel.h"
+#include "pw_bluetooth_sapphire/fake_lease_provider.h"
#include "pw_bluetooth_sapphire/internal/host/l2cap/mock_channel_test.h"
#include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
@@ -41,7 +42,7 @@
fake_chan_ = CreateFakeChannel(options);
sig_ = std::make_unique<BrEdrSignalingChannel>(
- fake_chan_->GetWeakPtr(), kDeviceRole, dispatcher());
+ fake_chan_->GetWeakPtr(), kDeviceRole, dispatcher(), lease_provider_);
}
void TearDown() override {
@@ -52,6 +53,7 @@
BrEdrSignalingChannel* sig() const { return sig_.get(); }
private:
+ pw::bluetooth_sapphire::testing::FakeLeaseProvider lease_provider_;
testing::FakeChannel::WeakPtr fake_chan_;
std::unique_ptr<BrEdrSignalingChannel> sig_;
};
diff --git a/pw_bluetooth_sapphire/host/l2cap/channel_manager_test.cc b/pw_bluetooth_sapphire/host/l2cap/channel_manager_test.cc
index 2335a07..6188b28 100644
--- a/pw_bluetooth_sapphire/host/l2cap/channel_manager_test.cc
+++ b/pw_bluetooth_sapphire/host/l2cap/channel_manager_test.cc
@@ -759,6 +759,10 @@
cmd_ids.extended_features_id,
kTestHandle1,
kExtendedFeaturesBitEnhancedRetransmission));
+ ReceiveAclDataPacket(testing::AclFixedChannelsSupportedInfoRsp(
+ cmd_ids.fixed_channels_supported_id,
+ kTestHandle1,
+ kFixedChannelsSupportedBitSignaling));
EXPECT_TRUE(chanmgr()->RegisterService(
kTestPsm, chan_params, std::move(channel_cb)));
diff --git a/pw_bluetooth_sapphire/host/l2cap/le_signaling_channel.cc b/pw_bluetooth_sapphire/host/l2cap/le_signaling_channel.cc
index 89db978..c6a48bd 100644
--- a/pw_bluetooth_sapphire/host/l2cap/le_signaling_channel.cc
+++ b/pw_bluetooth_sapphire/host/l2cap/le_signaling_channel.cc
@@ -25,8 +25,9 @@
LESignalingChannel::LESignalingChannel(
Channel::WeakPtr chan,
pw::bluetooth::emboss::ConnectionRole role,
- pw::async::Dispatcher& dispatcher)
- : SignalingChannel(std::move(chan), role, dispatcher) {
+ pw::async::Dispatcher& dispatcher,
+ pw::bluetooth_sapphire::LeaseProvider& wake_lease_provider)
+ : SignalingChannel(std::move(chan), role, dispatcher, wake_lease_provider) {
set_mtu(kMinLEMTU);
}
diff --git a/pw_bluetooth_sapphire/host/l2cap/le_signaling_channel_test.cc b/pw_bluetooth_sapphire/host/l2cap/le_signaling_channel_test.cc
index a36360d..520b02c 100644
--- a/pw_bluetooth_sapphire/host/l2cap/le_signaling_channel_test.cc
+++ b/pw_bluetooth_sapphire/host/l2cap/le_signaling_channel_test.cc
@@ -16,6 +16,7 @@
#include <pw_bytes/endian.h>
+#include "pw_bluetooth_sapphire/fake_lease_provider.h"
#include "pw_bluetooth_sapphire/internal/host/l2cap/fake_channel_test.h"
#include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
@@ -39,7 +40,7 @@
fake_sig_chan_ = CreateFakeChannel(options);
sig_ = std::make_unique<LESignalingChannel>(
- fake_sig_chan_->GetWeakPtr(), Role, dispatcher());
+ fake_sig_chan_->GetWeakPtr(), Role, dispatcher(), lease_provider_);
}
void TearDown() override { sig_ = nullptr; }
@@ -47,6 +48,7 @@
LESignalingChannel* sig() const { return sig_.get(); }
private:
+ pw::bluetooth_sapphire::testing::FakeLeaseProvider lease_provider_;
std::unique_ptr<testing::FakeChannel> fake_sig_chan_;
std::unique_ptr<LESignalingChannel> sig_;
diff --git a/pw_bluetooth_sapphire/host/l2cap/logical_link.cc b/pw_bluetooth_sapphire/host/l2cap/logical_link.cc
index 9c89b25..e230022 100644
--- a/pw_bluetooth_sapphire/host/l2cap/logical_link.cc
+++ b/pw_bluetooth_sapphire/host/l2cap/logical_link.cc
@@ -127,7 +127,10 @@
// Set up the signaling channel and dynamic channels.
if (type_ == bt::LinkType::kLE) {
signaling_channel_ = std::make_unique<LESignalingChannel>(
- OpenFixedChannel(kLESignalingChannelId), role_, pw_dispatcher_);
+ OpenFixedChannel(kLESignalingChannelId),
+ role_,
+ pw_dispatcher_,
+ wake_lease_provider_);
dynamic_registry_ = std::make_unique<LeDynamicChannelRegistry>(
signaling_channel_.get(),
fit::bind_member<&LogicalLink::OnChannelDisconnectRequest>(this),
@@ -138,7 +141,10 @@
ServeFlowControlCreditInd();
} else {
signaling_channel_ = std::make_unique<BrEdrSignalingChannel>(
- OpenFixedChannel(kSignalingChannelId), role_, pw_dispatcher_);
+ OpenFixedChannel(kSignalingChannelId),
+ role_,
+ pw_dispatcher_,
+ wake_lease_provider_);
dynamic_registry_ = std::make_unique<BrEdrDynamicChannelRegistry>(
signaling_channel_.get(),
fit::bind_member<&LogicalLink::OnChannelDisconnectRequest>(this),
diff --git a/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/bredr_signaling_channel.h b/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/bredr_signaling_channel.h
index 9130999..03a977e 100644
--- a/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/bredr_signaling_channel.h
+++ b/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/bredr_signaling_channel.h
@@ -24,9 +24,11 @@
// the L2CAP thread in production.
class BrEdrSignalingChannel final : public SignalingChannel {
public:
- BrEdrSignalingChannel(Channel::WeakPtr chan,
- pw::bluetooth::emboss::ConnectionRole role,
- pw::async::Dispatcher& dispatcher);
+ BrEdrSignalingChannel(
+ Channel::WeakPtr chan,
+ pw::bluetooth::emboss::ConnectionRole role,
+ pw::async::Dispatcher& dispatcher,
+ pw::bluetooth_sapphire::LeaseProvider& wake_lease_provider);
~BrEdrSignalingChannel() override = default;
// Test the link using an Echo Request command that can have an arbitrary
diff --git a/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/le_signaling_channel.h b/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/le_signaling_channel.h
index 1f3dbec..a63e78b 100644
--- a/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/le_signaling_channel.h
+++ b/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/le_signaling_channel.h
@@ -22,9 +22,11 @@
// Implements the L2CAP LE signaling fixed channel.
class LESignalingChannel final : public SignalingChannel {
public:
- LESignalingChannel(Channel::WeakPtr chan,
- pw::bluetooth::emboss::ConnectionRole role,
- pw::async::Dispatcher& dispatcher);
+ LESignalingChannel(
+ Channel::WeakPtr chan,
+ pw::bluetooth::emboss::ConnectionRole role,
+ pw::async::Dispatcher& dispatcher,
+ pw::bluetooth_sapphire::LeaseProvider& wake_lease_provider);
~LESignalingChannel() override = default;
private:
diff --git a/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/signaling_channel.h b/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/signaling_channel.h
index 8b40a50..0f9da56 100644
--- a/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/signaling_channel.h
+++ b/pw_bluetooth_sapphire/host/l2cap/public/pw_bluetooth_sapphire/internal/host/l2cap/signaling_channel.h
@@ -22,6 +22,7 @@
#include "pw_bluetooth_sapphire/internal/host/hci/connection.h"
#include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
#include "pw_bluetooth_sapphire/internal/host/l2cap/scoped_channel.h"
+#include "pw_bluetooth_sapphire/lease.h"
namespace bt::l2cap {
@@ -119,7 +120,8 @@
public:
SignalingChannel(Channel::WeakPtr chan,
pw::bluetooth::emboss::ConnectionRole role,
- pw::async::Dispatcher& dispatcher);
+ pw::async::Dispatcher& dispatcher,
+ pw::bluetooth_sapphire::LeaseProvider& wake_lease_provider);
~SignalingChannel() override = default;
// SignalingChannelInterface overrides
@@ -259,13 +261,15 @@
PendingCommand(const ByteBuffer& request_packet,
CommandCode response_command_code,
ResponseHandler response_handler_cb,
- pw::async::Dispatcher& dispatcher)
+ pw::async::Dispatcher& dispatcher,
+ pw::bluetooth_sapphire::Lease wake_lease_in)
: response_code(response_command_code),
response_handler(std::move(response_handler_cb)),
command_packet(std::make_unique<DynamicByteBuffer>(request_packet)),
transmit_count(1u),
timer_duration(0u),
- response_timeout_task(dispatcher) {}
+ response_timeout_task(dispatcher),
+ wake_lease(std::move(wake_lease_in)) {}
CommandCode response_code;
ResponseHandler response_handler;
@@ -281,6 +285,9 @@
// Automatically canceled by destruction if the response is received.
SmartTask response_timeout_task;
+
+ // Keep the system awake while this command is pending.
+ pw::bluetooth_sapphire::Lease wake_lease;
};
// Retransmit the request corresponding to |pending_command| and reset the RTX
@@ -289,6 +296,8 @@
pw::async::Dispatcher& pw_dispatcher_;
+ pw::bluetooth_sapphire::LeaseProvider& wake_lease_provider_;
+
bool is_open_;
l2cap::ScopedChannel chan_;
pw::bluetooth::emboss::ConnectionRole role_;
diff --git a/pw_bluetooth_sapphire/host/l2cap/signaling_channel.cc b/pw_bluetooth_sapphire/host/l2cap/signaling_channel.cc
index 89bee74..854c69f 100644
--- a/pw_bluetooth_sapphire/host/l2cap/signaling_channel.cc
+++ b/pw_bluetooth_sapphire/host/l2cap/signaling_channel.cc
@@ -24,10 +24,13 @@
namespace bt::l2cap::internal {
-SignalingChannel::SignalingChannel(Channel::WeakPtr chan,
- pw::bluetooth::emboss::ConnectionRole role,
- pw::async::Dispatcher& dispatcher)
+SignalingChannel::SignalingChannel(
+ Channel::WeakPtr chan,
+ pw::bluetooth::emboss::ConnectionRole role,
+ pw::async::Dispatcher& dispatcher,
+ pw::bluetooth_sapphire::LeaseProvider& wake_lease_provider)
: pw_dispatcher_(dispatcher),
+ wake_lease_provider_(wake_lease_provider),
is_open_(true),
chan_(std::move(chan)),
role_(role),
@@ -100,8 +103,17 @@
ResponseHandler cb) {
PW_CHECK(IsSupportedResponse(response_command_code));
- const auto [iter, inserted] = pending_commands_.try_emplace(
- id, request_packet, response_command_code, std::move(cb), pw_dispatcher_);
+ pw::bluetooth_sapphire::Lease wake_lease =
+ PW_SAPPHIRE_ACQUIRE_LEASE(wake_lease_provider_, "SignalingChannel")
+ .value_or(pw::bluetooth_sapphire::Lease());
+
+ const auto [iter, inserted] =
+ pending_commands_.try_emplace(id,
+ request_packet,
+ response_command_code,
+ std::move(cb),
+ pw_dispatcher_,
+ std::move(wake_lease));
PW_CHECK(inserted);
// Start the RTX timer per Core Spec v5.0, Volume 3, Part A, Sec 6.2.1 which
diff --git a/pw_bluetooth_sapphire/host/l2cap/signaling_channel_test.cc b/pw_bluetooth_sapphire/host/l2cap/signaling_channel_test.cc
index fbe5587..7219267 100644
--- a/pw_bluetooth_sapphire/host/l2cap/signaling_channel_test.cc
+++ b/pw_bluetooth_sapphire/host/l2cap/signaling_channel_test.cc
@@ -19,6 +19,7 @@
#include <chrono>
+#include "pw_bluetooth_sapphire/fake_lease_provider.h"
#include "pw_bluetooth_sapphire/internal/host/l2cap/fake_channel_test.h"
#include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
@@ -39,11 +40,14 @@
class TestSignalingChannel : public SignalingChannel {
public:
- explicit TestSignalingChannel(Channel::WeakPtr chan,
- pw::async::Dispatcher& dispatcher)
+ explicit TestSignalingChannel(
+ Channel::WeakPtr chan,
+ pw::async::Dispatcher& dispatcher,
+ pw::bluetooth_sapphire::LeaseProvider& lease_provider)
: SignalingChannel(std::move(chan),
pw::bluetooth::emboss::ConnectionRole::CENTRAL,
- dispatcher) {
+ dispatcher,
+ lease_provider) {
set_mtu(kTestMTU);
}
~TestSignalingChannel() override = default;
@@ -105,7 +109,7 @@
fake_channel_inst_ = CreateFakeChannel(options);
sig_ = std::make_unique<TestSignalingChannel>(
- fake_channel_inst_->GetWeakPtr(), dispatcher());
+ fake_channel_inst_->GetWeakPtr(), dispatcher(), lease_provider_);
}
void TearDown() override {
@@ -119,7 +123,12 @@
void DestroySig() { sig_ = nullptr; }
+ pw::bluetooth_sapphire::testing::FakeLeaseProvider& lease_provider() {
+ return lease_provider_;
+ }
+
private:
+ pw::bluetooth_sapphire::testing::FakeLeaseProvider lease_provider_;
std::unique_ptr<TestSignalingChannel> sig_;
// Own the fake channel so that its lifetime can span beyond that of |sig_|.
@@ -685,6 +694,7 @@
// for the following response.
TEST_F(SignalingChannelTest,
ExpectAdditionalResponseExtendsRtxTimeoutToErtxTimeout) {
+ EXPECT_EQ(lease_provider().lease_count(), 0u);
bool tx_success = false;
fake_chan()->SetSendCallback([&tx_success](auto) { tx_success = true; },
dispatcher());
@@ -692,8 +702,11 @@
const StaticByteBuffer req_data{'h', 'e', 'l', 'l', 'o'};
int rx_cb_calls = 0;
EXPECT_TRUE(sig()->SendRequest(
- kEchoRequest, req_data, [&rx_cb_calls](Status status, const ByteBuffer&) {
+ kEchoRequest,
+ req_data,
+ [this, &rx_cb_calls](Status status, const ByteBuffer&) {
rx_cb_calls++;
+ EXPECT_GT(lease_provider().lease_count(), 0u);
if (rx_cb_calls <= 2) {
EXPECT_EQ(Status::kSuccess, status);
} else {
@@ -703,9 +716,11 @@
kExpectAdditionalResponse;
}));
+ EXPECT_GT(lease_provider().lease_count(), 0u);
RunUntilIdle();
EXPECT_TRUE(tx_success);
EXPECT_EQ(0, rx_cb_calls);
+ EXPECT_GT(lease_provider().lease_count(), 0u);
const StaticByteBuffer echo_rsp(
// Echo response with no payload.
@@ -715,12 +730,14 @@
0x00);
fake_chan()->Receive(echo_rsp);
EXPECT_EQ(1, rx_cb_calls);
+ EXPECT_GT(lease_provider().lease_count(), 0u);
// The handler expects more responses so the RTX timer shouldn't have expired.
RunFor(kSignalingChannelResponseTimeout);
fake_chan()->Receive(echo_rsp);
EXPECT_EQ(2, rx_cb_calls);
+ EXPECT_GT(lease_provider().lease_count(), 0u);
// The second response should have reset the ERTX timer, so it shouldn't fire
// yet.
@@ -731,6 +748,7 @@
// kTimeOut "response."
RunFor(std::chrono::seconds(1));
EXPECT_EQ(3, rx_cb_calls);
+ EXPECT_EQ(lease_provider().lease_count(), 0u);
}
TEST_F(SignalingChannelTest, RegisterRequestResponder) {
@@ -870,10 +888,71 @@
fake_chan()->SetSendCallback(std::move(send_cb), dispatcher());
sig()->SendCommandWithoutResponse(kLEFlowControlCredit, payload);
+ EXPECT_EQ(lease_provider().lease_count(), 0u);
RunUntilIdle();
EXPECT_TRUE(cb_called);
}
+TEST_F(SignalingChannelTest, SendMultipleCommandsSimultaneously) {
+ EXPECT_EQ(lease_provider().lease_count(), 0u);
+ fake_chan()->SetSendCallback([](auto) {}, dispatcher());
+
+ const StaticByteBuffer req_data{'h', 'e', 'l', 'l', 'o'};
+ int rx_cb_calls_0 = 0;
+ EXPECT_TRUE(sig()->SendRequest(
+ kEchoRequest,
+ req_data,
+ [this, &rx_cb_calls_0](Status status, const ByteBuffer&) {
+ rx_cb_calls_0++;
+ EXPECT_EQ(Status::kSuccess, status);
+ EXPECT_GT(lease_provider().lease_count(), 0u);
+ return SignalingChannel::ResponseHandlerAction::
+ kCompleteOutboundTransaction;
+ }));
+
+ EXPECT_GT(lease_provider().lease_count(), 0u);
+ RunUntilIdle();
+ EXPECT_EQ(0, rx_cb_calls_0);
+ EXPECT_GT(lease_provider().lease_count(), 0u);
+
+ int rx_cb_calls_1 = 0;
+ EXPECT_TRUE(sig()->SendRequest(
+ kEchoRequest,
+ req_data,
+ [this, &rx_cb_calls_1](Status status, const ByteBuffer&) {
+ rx_cb_calls_1++;
+ EXPECT_EQ(Status::kSuccess, status);
+ EXPECT_GT(lease_provider().lease_count(), 0u);
+ return SignalingChannel::ResponseHandlerAction::
+ kCompleteOutboundTransaction;
+ }));
+
+ EXPECT_GT(lease_provider().lease_count(), 0u);
+ RunUntilIdle();
+ EXPECT_EQ(0, rx_cb_calls_1);
+ EXPECT_GT(lease_provider().lease_count(), 0u);
+
+ const StaticByteBuffer echo_rsp_0(
+ // Echo response with no payload.
+ 0x09,
+ 0x01, // ID (1)
+ 0x00,
+ 0x00);
+ fake_chan()->Receive(echo_rsp_0);
+ EXPECT_EQ(1, rx_cb_calls_0);
+ EXPECT_GT(lease_provider().lease_count(), 0u);
+
+ const StaticByteBuffer echo_rsp_1(
+ // Echo response with no payload.
+ 0x09,
+ 0x02, // ID (2)
+ 0x00,
+ 0x00);
+ fake_chan()->Receive(echo_rsp_1);
+ EXPECT_EQ(1, rx_cb_calls_1);
+ EXPECT_EQ(lease_provider().lease_count(), 0u);
+}
+
} // namespace
} // namespace bt::l2cap::internal