blob: 2633623a1f8ba6d1c2826fd06314489ba295d86a [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/connectivity/bluetooth/core/bt-host/l2cap/dynamic_channel_registry.h"
#include <lib/async/cpp/task.h>
#include "lib/gtest/test_loop_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h"
namespace bt::l2cap::internal {
namespace {
constexpr uint16_t kNumChannelsAllowed = 256;
constexpr uint16_t kPsm = 0x0001;
constexpr ChannelId kRemoteCId = 0x60a3;
constexpr ChannelParameters kChannelParams;
class FakeDynamicChannel final : public DynamicChannel {
public:
FakeDynamicChannel(DynamicChannelRegistry* registry, PSM psm, ChannelId local_cid,
ChannelId remote_cid)
: DynamicChannel(registry, psm, local_cid, remote_cid) {}
~FakeDynamicChannel() override { EXPECT_FALSE(IsConnected()); }
// DynamicChannel overrides
bool IsConnected() const override { return connected_; }
bool IsOpen() const override { return open_; }
ChannelInfo info() const override { return ChannelInfo::MakeBasicMode(kDefaultMTU, kDefaultMTU); }
void DoConnect(ChannelId remote_cid) {
ASSERT_TRUE(SetRemoteChannelId(remote_cid))
<< "Could not set non-unique remote_cid " << remote_cid;
connected_ = true;
}
void DoOpen(bool new_open = true) {
open_ = new_open;
if (new_open) {
set_opened();
}
open_result_cb_();
}
void DoRemoteClose() {
open_ = false;
connected_ = false;
OnDisconnected();
}
// After calling |set_defer_disconnect_callback|, this returns the callback
// passed to |Disconnect|, or an empty callback if |Disconnect| hasn't been
// called.
DisconnectDoneCallback& disconnect_done_callback() { return disconnect_done_callback_; }
void set_defer_disconnect_done_callback() { defer_disconnect_done_callback_ = true; }
private:
// DynamicChannel overrides
void Open(fit::closure open_result_cb) override { open_result_cb_ = std::move(open_result_cb); }
void Disconnect(DisconnectDoneCallback done_cb) override {
open_ = false;
connected_ = false;
bt_log(DEBUG, "l2cap", "Got Disconnect %#.4x callback: %d", local_cid(), psm());
ASSERT_FALSE(disconnect_done_callback_);
if (defer_disconnect_done_callback_) {
disconnect_done_callback_ = std::move(done_cb);
} else {
done_cb();
}
}
fit::closure open_result_cb_;
DisconnectDoneCallback disconnect_done_callback_;
// If true, the Disconnect call does not immediately signal its callback. The
// test will have to call it explicitly with |disconnect_callback()|.
bool defer_disconnect_done_callback_ = false;
bool connected_ = false;
bool open_ = false;
};
// Fake registry subclass for testing inherited logic. Stubs out |MakeOutbound|
// to vend FakeDynamicChannels.
class TestDynamicChannelRegistry final : public DynamicChannelRegistry {
public:
TestDynamicChannelRegistry(DynamicChannelCallback close_cb,
ServiceRequestCallback service_request_cb)
: DynamicChannelRegistry(kNumChannelsAllowed, std::move(close_cb),
std::move(service_request_cb), /*random_channel_ids=*/true) {}
// Returns previous channel created.
FakeDynamicChannel* last_channel() { return last_channel_; }
// Make public for testing.
using DynamicChannelRegistry::AliveChannelCount;
using DynamicChannelRegistry::FindAvailableChannelId;
using DynamicChannelRegistry::FindChannelByLocalId;
using DynamicChannelRegistry::FindChannelByRemoteId;
using DynamicChannelRegistry::ForEach;
using DynamicChannelRegistry::RequestService;
private:
// DynamicChannelRegistry overrides
DynamicChannelPtr MakeOutbound(PSM psm, ChannelId local_cid, ChannelParameters params) override {
return MakeChannelInternal(psm, local_cid, kInvalidChannelId);
}
DynamicChannelPtr MakeInbound(PSM psm, ChannelId local_cid, ChannelId remote_cid,
ChannelParameters params) override {
auto channel = MakeChannelInternal(psm, local_cid, remote_cid);
channel->DoConnect(remote_cid);
return channel;
}
std::unique_ptr<FakeDynamicChannel> MakeChannelInternal(PSM psm, ChannelId local_cid,
ChannelId remote_cid) {
auto channel = std::make_unique<FakeDynamicChannel>(this, psm, local_cid, remote_cid);
last_channel_ = channel.get();
return channel;
}
FakeDynamicChannel* last_channel_ = nullptr;
};
// DynamicChannelCallback static handler
void DoNothing(const DynamicChannel* channel) {}
// ServiceRequestCallback static handler
std::optional<DynamicChannelRegistry::ServiceInfo> RejectAllServices(PSM /*psm*/) {
return std::nullopt;
}
TEST(L2CAP_DynamicChannelRegistryTest, OpenAndRemoteCloseChannel) {
ChannelId local_cid = kInvalidChannelId;
ChannelId remote_cid = kInvalidChannelId;
bool close_cb_called = false;
auto close_cb = [&](const DynamicChannel* chan) {
EXPECT_FALSE(close_cb_called);
close_cb_called = true;
EXPECT_TRUE(chan);
EXPECT_FALSE(chan->IsConnected());
EXPECT_FALSE(chan->IsOpen());
EXPECT_EQ(local_cid, chan->local_cid());
EXPECT_EQ(remote_cid, chan->remote_cid());
};
TestDynamicChannelRegistry registry(std::move(close_cb), RejectAllServices);
EXPECT_NE(kInvalidChannelId, registry.FindAvailableChannelId());
bool open_result_cb_called = false;
auto open_result_cb = [&](const DynamicChannel* chan) {
EXPECT_FALSE(open_result_cb_called);
open_result_cb_called = true;
EXPECT_TRUE(chan);
EXPECT_EQ(kPsm, chan->psm());
local_cid = chan->local_cid();
remote_cid = chan->remote_cid();
};
registry.OpenOutbound(kPsm, kChannelParams, std::move(open_result_cb));
registry.last_channel()->DoConnect(kRemoteCId);
registry.last_channel()->DoOpen();
EXPECT_TRUE(open_result_cb_called);
EXPECT_FALSE(close_cb_called);
auto channel_by_local_id = registry.FindChannelByLocalId(local_cid);
auto channel_by_remote_id = registry.FindChannelByRemoteId(remote_cid);
EXPECT_TRUE(channel_by_local_id);
EXPECT_TRUE(channel_by_remote_id);
EXPECT_EQ(channel_by_local_id, channel_by_remote_id);
registry.last_channel()->DoRemoteClose();
EXPECT_TRUE(close_cb_called);
EXPECT_FALSE(registry.FindChannelByLocalId(local_cid));
EXPECT_FALSE(registry.FindChannelByRemoteId(remote_cid));
}
TEST(L2CAP_DynamicChannelRegistryTest, OpenAndLocalCloseChannel) {
bool registry_close_cb_called = false;
auto registry_close_cb = [&](const DynamicChannel*) { registry_close_cb_called = true; };
TestDynamicChannelRegistry registry(std::move(registry_close_cb), RejectAllServices);
bool open_result_cb_called = false;
ChannelId local_cid = kInvalidChannelId;
auto open_result_cb = [&](const DynamicChannel* chan) {
EXPECT_FALSE(open_result_cb_called);
open_result_cb_called = true;
EXPECT_TRUE(chan);
local_cid = chan->local_cid();
};
registry.OpenOutbound(kPsm, kChannelParams, std::move(open_result_cb));
registry.last_channel()->DoConnect(kRemoteCId);
registry.last_channel()->DoOpen();
EXPECT_TRUE(open_result_cb_called);
EXPECT_TRUE(registry.FindChannelByLocalId(local_cid));
bool close_cb_called = false;
registry.CloseChannel(local_cid, [&] { close_cb_called = true; });
EXPECT_FALSE(registry_close_cb_called);
EXPECT_TRUE(close_cb_called);
EXPECT_FALSE(registry.FindChannelByLocalId(local_cid));
}
TEST(L2CAP_DynamicChannelRegistryTest, RejectServiceRequest) {
bool service_request_cb_called = false;
auto service_request_cb = [&service_request_cb_called](PSM psm) {
EXPECT_FALSE(service_request_cb_called);
EXPECT_EQ(kPsm, psm);
service_request_cb_called = true;
return std::nullopt;
};
TestDynamicChannelRegistry registry(DoNothing, std::move(service_request_cb));
registry.RequestService(kPsm, registry.FindAvailableChannelId(), kRemoteCId);
EXPECT_TRUE(service_request_cb_called);
EXPECT_FALSE(registry.last_channel());
}
TEST(L2CAP_DynamicChannelRegistryTest, AcceptServiceRequestThenOpenOk) {
bool open_result_cb_called = false;
ChannelId local_cid = kInvalidChannelId;
ChannelId remote_cid = kInvalidChannelId;
DynamicChannelRegistry::DynamicChannelCallback open_result_cb = [&](const DynamicChannel* chan) {
EXPECT_FALSE(open_result_cb_called);
open_result_cb_called = true;
EXPECT_TRUE(chan);
EXPECT_EQ(kPsm, chan->psm());
local_cid = chan->local_cid();
remote_cid = chan->remote_cid();
};
bool service_request_cb_called = false;
auto service_request_cb = [&service_request_cb_called,
open_result_cb = std::move(open_result_cb)](PSM psm) mutable {
EXPECT_FALSE(service_request_cb_called);
EXPECT_EQ(kPsm, psm);
service_request_cb_called = true;
return DynamicChannelRegistry::ServiceInfo{ChannelParameters(), open_result_cb.share()};
};
TestDynamicChannelRegistry registry(DoNothing, std::move(service_request_cb));
registry.RequestService(kPsm, registry.FindAvailableChannelId(), kRemoteCId);
EXPECT_TRUE(service_request_cb_called);
ASSERT_TRUE(registry.last_channel());
registry.last_channel()->DoOpen();
EXPECT_TRUE(open_result_cb_called);
EXPECT_NE(kInvalidChannelId, local_cid);
EXPECT_NE(kInvalidChannelId, remote_cid);
EXPECT_TRUE(registry.FindChannelByLocalId(local_cid));
}
TEST(L2CAP_DynamicChannelRegistryTest, AcceptServiceRequestThenOpenFails) {
bool open_result_cb_called = false;
DynamicChannelRegistry::DynamicChannelCallback open_result_cb =
[&open_result_cb_called](const DynamicChannel* chan) { open_result_cb_called = true; };
bool service_request_cb_called = false;
auto service_request_cb = [&service_request_cb_called,
open_result_cb = std::move(open_result_cb)](PSM psm) mutable {
EXPECT_FALSE(service_request_cb_called);
EXPECT_EQ(kPsm, psm);
service_request_cb_called = true;
return DynamicChannelRegistry::ServiceInfo{ChannelParameters(), open_result_cb.share()};
};
TestDynamicChannelRegistry registry(DoNothing, std::move(service_request_cb));
ChannelId local_cid = registry.FindAvailableChannelId();
EXPECT_NE(kInvalidChannelId, local_cid);
registry.RequestService(kPsm, local_cid, kRemoteCId);
EXPECT_TRUE(service_request_cb_called);
ASSERT_TRUE(registry.last_channel());
registry.last_channel()->DoOpen(false);
// Don't get channels that failed to open.
EXPECT_FALSE(open_result_cb_called);
EXPECT_FALSE(registry.FindChannelByLocalId(local_cid));
// The channel should be released upon this failure.
EXPECT_EQ(0u, registry.AliveChannelCount());
}
TEST(L2CAP_DynamicChannelRegistryTest, DestroyRegistryWithOpenChannelClosesIt) {
bool close_cb_called = false;
auto close_cb = [&close_cb_called](const DynamicChannel* chan) { close_cb_called = true; };
TestDynamicChannelRegistry registry(std::move(close_cb), RejectAllServices);
bool open_result_cb_called = false;
auto open_result_cb = [&open_result_cb_called](const DynamicChannel* chan) {
EXPECT_FALSE(open_result_cb_called);
open_result_cb_called = true;
EXPECT_TRUE(chan);
};
registry.OpenOutbound(kPsm, kChannelParams, std::move(open_result_cb));
registry.last_channel()->DoConnect(kRemoteCId);
registry.last_channel()->DoOpen();
EXPECT_TRUE(open_result_cb_called);
EXPECT_TRUE(registry.FindChannelByRemoteId(kRemoteCId));
EXPECT_FALSE(close_cb_called);
// |registry| goes out of scope and FakeDynamicChannel's dtor checks that it
// is disconnected.
}
TEST(L2CAP_DynamicChannelRegistryTest, ErrorConnectingChannel) {
bool open_result_cb_called = false;
auto open_result_cb = [&open_result_cb_called](const DynamicChannel* chan) {
EXPECT_FALSE(open_result_cb_called);
open_result_cb_called = true;
EXPECT_FALSE(chan);
};
bool close_cb_called = false;
auto close_cb = [&close_cb_called](auto) { close_cb_called = true; };
TestDynamicChannelRegistry registry(std::move(close_cb), RejectAllServices);
registry.OpenOutbound(kPsm, kChannelParams, std::move(open_result_cb));
registry.last_channel()->DoOpen(false);
EXPECT_TRUE(open_result_cb_called);
EXPECT_FALSE(close_cb_called);
// Should be no alive channels anymore.
EXPECT_EQ(0u, registry.AliveChannelCount());
}
TEST(L2CAP_DynamicChannelRegistryTest, ExhaustedChannelIds) {
int open_result_cb_count = 0;
// This callback expects the channel to be creatable.
DynamicChannelRegistry::DynamicChannelCallback success_open_result_cb =
[&open_result_cb_count](const DynamicChannel* chan) {
ASSERT_NE(nullptr, chan);
EXPECT_NE(kInvalidChannelId, chan->local_cid());
open_result_cb_count++;
};
int close_cb_count = 0;
auto close_cb = [&close_cb_count](auto) { close_cb_count++; };
TestDynamicChannelRegistry registry(std::move(close_cb), RejectAllServices);
// Open a lot of channels.
for (int i = 0; i < kNumChannelsAllowed; i++) {
registry.OpenOutbound(kPsm + i, kChannelParams, success_open_result_cb.share());
registry.last_channel()->DoConnect(kRemoteCId + i);
registry.last_channel()->DoOpen();
}
EXPECT_EQ(kNumChannelsAllowed, open_result_cb_count);
EXPECT_EQ(0, close_cb_count);
// Ensure that channel IDs are exhausted.
EXPECT_EQ(kInvalidChannelId, registry.FindAvailableChannelId());
// This callback expects the channel to fail creation.
auto fail_open_result_cb = [&open_result_cb_count](const DynamicChannel* chan) {
EXPECT_FALSE(chan);
open_result_cb_count++;
};
// Try to open a new channel.
registry.OpenOutbound(kPsm, kChannelParams, std::move(fail_open_result_cb));
EXPECT_EQ(kNumChannelsAllowed + 1, open_result_cb_count);
EXPECT_EQ(0, close_cb_count);
// Close the most recently opened channel.
auto last_remote_cid = registry.last_channel()->remote_cid();
registry.last_channel()->DoRemoteClose();
EXPECT_EQ(1, close_cb_count);
EXPECT_NE(kInvalidChannelId, registry.FindAvailableChannelId());
// Try to open a channel again.
registry.OpenOutbound(kPsm, kChannelParams, success_open_result_cb.share());
registry.last_channel()->DoConnect(last_remote_cid);
registry.last_channel()->DoOpen();
EXPECT_EQ(kNumChannelsAllowed + 2, open_result_cb_count);
EXPECT_EQ(1, close_cb_count);
}
TEST(L2CAP_DynamicChannelRegistryTest, ChannelIdNotReusedUntilDisconnectionCompletes) {
TestDynamicChannelRegistry registry(DoNothing, RejectAllServices);
// This callback expects the channel to be creatable.
int open_result_cb_count = 0;
DynamicChannelRegistry::DynamicChannelCallback success_open_result_cb =
[&](const DynamicChannel* chan) {
ASSERT_NE(nullptr, chan);
EXPECT_NE(kInvalidChannelId, chan->local_cid());
open_result_cb_count++;
};
// Open all but one of the available channels.
for (int i = 0; i < kNumChannelsAllowed - 1; i++) {
registry.OpenOutbound(kPsm + i, kChannelParams, success_open_result_cb.share());
registry.last_channel()->DoConnect(kRemoteCId + i);
registry.last_channel()->DoOpen();
}
EXPECT_EQ(kNumChannelsAllowed - 1, open_result_cb_count);
// This callback records the info on channel that was created
ChannelId last_local_cid = kInvalidChannelId;
auto record_open_result_cb = [&](const DynamicChannel* chan) {
ASSERT_TRUE(chan);
last_local_cid = chan->local_cid();
};
// Ensure that channel IDs are not exhausted.
EXPECT_NE(kInvalidChannelId, registry.FindAvailableChannelId());
// Open a the last available channel.
registry.OpenOutbound(kPsm, kChannelParams, std::move(record_open_result_cb));
registry.last_channel()->DoConnect(kRemoteCId + kNumChannelsAllowed - 1);
registry.last_channel()->DoOpen();
EXPECT_NE(kInvalidChannelId, last_local_cid);
ASSERT_TRUE(registry.FindChannelByLocalId(last_local_cid));
// The channels are exhausted now.
ASSERT_EQ(kInvalidChannelId, registry.FindAvailableChannelId());
// Close the channel but don't let the disconnection complete.
FakeDynamicChannel* const last_channel = registry.last_channel();
last_channel->set_defer_disconnect_done_callback();
bool close_cb_called = false;
registry.CloseChannel(last_local_cid, [&] { close_cb_called = true; });
// New channels should not reuse the "mostly disconnected" channel's ID.
EXPECT_FALSE(close_cb_called);
// There should still be no channels left.
EXPECT_EQ(kInvalidChannelId, registry.FindAvailableChannelId());
ASSERT_TRUE(registry.FindChannelByLocalId(last_local_cid));
EXPECT_FALSE(registry.FindChannelByLocalId(last_local_cid)->IsConnected());
// Complete the disconnection for the first channel opened.
ASSERT_TRUE(last_channel->disconnect_done_callback());
last_channel->disconnect_done_callback()();
EXPECT_TRUE(close_cb_called);
EXPECT_EQ(last_local_cid, registry.FindAvailableChannelId());
// Open a new channel and make sure that last ID can be reused now.
bool open_result_cb_called = false;
auto open_result_cb = [&](const DynamicChannel* chan) {
EXPECT_FALSE(open_result_cb_called);
open_result_cb_called = true;
ASSERT_TRUE(chan);
EXPECT_EQ(last_local_cid, chan->local_cid());
};
registry.OpenOutbound(kPsm, kChannelParams, std::move(open_result_cb));
registry.last_channel()->DoConnect(kRemoteCId + kNumChannelsAllowed - 1);
registry.last_channel()->DoOpen();
EXPECT_TRUE(open_result_cb_called);
}
// Removing a channel from the channel map while iterating the channels in ForEach should not cause
// a use-after-free of the invalidated pointer.
TEST(L2CAP_DynamicChannelRegistryTest, CloseChannelInForEachCallback) {
bool registry_close_cb_called = false;
auto registry_close_cb = [&](const DynamicChannel*) { registry_close_cb_called = true; };
TestDynamicChannelRegistry registry(std::move(registry_close_cb), RejectAllServices);
bool open_result_cb_called = false;
ChannelId local_cid = kInvalidChannelId;
auto open_result_cb = [&](const DynamicChannel* chan) {
EXPECT_FALSE(open_result_cb_called);
open_result_cb_called = true;
EXPECT_TRUE(chan);
local_cid = chan->local_cid();
};
registry.OpenOutbound(kPsm, kChannelParams, std::move(open_result_cb));
registry.last_channel()->DoConnect(kRemoteCId);
registry.last_channel()->DoOpen();
EXPECT_TRUE(open_result_cb_called);
EXPECT_TRUE(registry.FindChannelByLocalId(local_cid));
// Even if the next iterator is "end", it would still be unsafe to advance the erased iterator.
registry.ForEach([&](DynamicChannel* chan) { registry.CloseChannel(chan->local_cid(), [] {}); });
EXPECT_FALSE(registry.FindChannelByLocalId(local_cid));
}
} // namespace
} // namespace bt::l2cap::internal