| // 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 "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h" |
| #include "lib/gtest/test_loop_fixture.h" |
| |
| namespace bt { |
| namespace l2cap { |
| namespace internal { |
| namespace { |
| |
| constexpr int kNumChannelsAllowed = 256; |
| constexpr ChannelId kLargestChannelId = |
| kFirstDynamicChannelId + kNumChannelsAllowed - 1; |
| constexpr uint16_t kPsm = 0x0001; |
| constexpr ChannelId kLocalCId = 0x0040; |
| constexpr ChannelId kRemoteCId = 0x60a3; |
| |
| 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_; } |
| |
| void DoConnect(ChannelId remote_cid) { |
| set_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() { |
| Disconnect(); |
| OnDisconnected(); |
| } |
| |
| private: |
| // DynamicChannel overrides |
| void Open(fit::closure open_result_cb) override { |
| open_result_cb_ = std::move(open_result_cb); |
| } |
| void Disconnect() override { |
| open_ = false; |
| connected_ = false; |
| } |
| |
| fit::closure open_result_cb_; |
| 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(kLargestChannelId, std::move(close_cb), |
| std::move(service_request_cb)) {} |
| |
| // Returns previous channel created. |
| FakeDynamicChannel* last_channel() { return last_channel_; } |
| |
| // Make public for testing. |
| using DynamicChannelRegistry::FindAvailableChannelId; |
| using DynamicChannelRegistry::FindChannel; |
| using DynamicChannelRegistry::RequestService; |
| |
| private: |
| // DynamicChannelRegistry overrides |
| DynamicChannelPtr MakeOutbound(PSM psm, ChannelId local_cid) override { |
| return MakeChannelInternal(psm, local_cid, kInvalidChannelId); |
| } |
| |
| DynamicChannelPtr MakeInbound(PSM psm, ChannelId local_cid, |
| ChannelId remote_cid) 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 |
| DynamicChannelRegistry::DynamicChannelCallback RejectAllServices(PSM psm) { |
| return nullptr; |
| } |
| |
| TEST(L2CAP_DynamicChannelRegistryTest, OpenAndRemoteCloseChannel) { |
| bool close_cb_called = false; |
| auto close_cb = [&close_cb_called](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(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| }; |
| |
| TestDynamicChannelRegistry registry(std::move(close_cb), RejectAllServices); |
| EXPECT_EQ(kFirstDynamicChannelId, registry.FindAvailableChannelId()); |
| |
| 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); |
| EXPECT_EQ(kPsm, chan->psm()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| }; |
| |
| registry.OpenOutbound(kPsm, 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); |
| EXPECT_TRUE(registry.FindChannel(kLocalCId)); |
| |
| registry.last_channel()->DoRemoteClose(); |
| EXPECT_TRUE(close_cb_called); |
| EXPECT_FALSE(registry.FindChannel(kLocalCId)); |
| EXPECT_EQ(kFirstDynamicChannelId, registry.FindAvailableChannelId()); |
| } |
| |
| TEST(L2CAP_DynamicChannelRegistryTest, OpenAndLocalCloseChannel) { |
| 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, std::move(open_result_cb)); |
| registry.last_channel()->DoConnect(kRemoteCId); |
| registry.last_channel()->DoOpen(); |
| |
| EXPECT_TRUE(open_result_cb_called); |
| EXPECT_TRUE(registry.FindChannel(kLocalCId)); |
| EXPECT_EQ(kFirstDynamicChannelId + 1, registry.FindAvailableChannelId()); |
| |
| registry.CloseChannel(kLocalCId); |
| EXPECT_FALSE(close_cb_called); |
| EXPECT_FALSE(registry.FindChannel(kLocalCId)); |
| EXPECT_EQ(kFirstDynamicChannelId, registry.FindAvailableChannelId()); |
| } |
| |
| TEST(L2CAP_DynamicChannelRegistryTest, RejectServiceRequest) { |
| bool service_request_cb_called = false; |
| DynamicChannelRegistry::ServiceRequestCallback 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 nullptr; |
| }; |
| |
| 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()); |
| EXPECT_FALSE(registry.FindChannel(kLocalCId)); |
| EXPECT_EQ(kFirstDynamicChannelId, registry.FindAvailableChannelId()); |
| } |
| |
| TEST(L2CAP_DynamicChannelRegistryTest, AcceptServiceRequestThenOpenOk) { |
| bool open_result_cb_called = false; |
| DynamicChannelRegistry::DynamicChannelCallback open_result_cb = |
| [&open_result_cb_called](const DynamicChannel* chan) { |
| EXPECT_FALSE(open_result_cb_called); |
| open_result_cb_called = true; |
| EXPECT_TRUE(chan); |
| EXPECT_EQ(kPsm, chan->psm()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| }; |
| |
| bool service_request_cb_called = false; |
| DynamicChannelRegistry::ServiceRequestCallback 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 open_result_cb.share(); |
| }; |
| |
| TestDynamicChannelRegistry registry(DoNothing, std::move(service_request_cb)); |
| |
| registry.RequestService(kPsm, registry.FindAvailableChannelId(), kRemoteCId); |
| EXPECT_TRUE(service_request_cb_called); |
| EXPECT_EQ(kFirstDynamicChannelId + 1, registry.FindAvailableChannelId()); |
| ASSERT_TRUE(registry.last_channel()); |
| registry.last_channel()->DoOpen(); |
| |
| EXPECT_TRUE(open_result_cb_called); |
| EXPECT_TRUE(registry.FindChannel(kLocalCId)); |
| } |
| |
| 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; |
| DynamicChannelRegistry::ServiceRequestCallback 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 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(false); |
| |
| // Don't get channels that failed to open. |
| EXPECT_FALSE(open_result_cb_called); |
| EXPECT_FALSE(registry.FindChannel(kLocalCId)); |
| |
| // The channel ID should be released upon this failure. |
| EXPECT_EQ(kFirstDynamicChannelId, registry.FindAvailableChannelId()); |
| } |
| |
| 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, std::move(open_result_cb)); |
| registry.last_channel()->DoConnect(kRemoteCId); |
| registry.last_channel()->DoOpen(); |
| |
| EXPECT_TRUE(open_result_cb_called); |
| EXPECT_TRUE(registry.FindChannel(kLocalCId)); |
| 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, std::move(open_result_cb)); |
| registry.last_channel()->DoOpen(false); |
| |
| EXPECT_TRUE(open_result_cb_called); |
| EXPECT_FALSE(close_cb_called); |
| EXPECT_FALSE(registry.FindChannel(kLocalCId)); |
| |
| // Recycle channel ID immediately for the following channel attempt. |
| EXPECT_EQ(kFirstDynamicChannelId, registry.FindAvailableChannelId()); |
| } |
| |
| 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, 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, 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. |
| registry.last_channel()->DoRemoteClose(); |
| EXPECT_EQ(1, close_cb_count); |
| EXPECT_NE(kInvalidChannelId, registry.FindAvailableChannelId()); |
| |
| // Try to open a channel again. |
| registry.OpenOutbound(kPsm, success_open_result_cb.share()); |
| registry.last_channel()->DoConnect(kRemoteCId); |
| registry.last_channel()->DoOpen(); |
| EXPECT_EQ(kNumChannelsAllowed + 2, open_result_cb_count); |
| EXPECT_EQ(1, close_cb_count); |
| } |
| |
| } // namespace |
| } // namespace internal |
| } // namespace l2cap |
| } // namespace bt |