| // Copyright 2019 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/fidl/low_energy_central_server.h" |
| |
| #include <cstddef> |
| |
| #include <gmock/gmock.h> |
| |
| #include "adapter_test_fixture.h" |
| #include "fuchsia/bluetooth/gatt/cpp/fidl.h" |
| #include "fuchsia/bluetooth/le/cpp/fidl.h" |
| #include "lib/fidl/cpp/interface_request.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/status.h" |
| #include "src/connectivity/bluetooth/core/bt-host/fidl/helpers.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/types.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_controller.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_peer.h" |
| |
| namespace bthost { |
| namespace { |
| |
| namespace fble = fuchsia::bluetooth::le; |
| namespace fgatt = fuchsia::bluetooth::gatt; |
| |
| const bt::DeviceAddress kTestAddr(bt::DeviceAddress::Type::kLEPublic, {0x01, 0, 0, 0, 0, 0}); |
| const size_t kLEMaxNumPackets = 10; |
| const bt::hci::DataBufferInfo kLEDataBufferInfo(bt::hci::kMaxACLPayloadSize, kLEMaxNumPackets); |
| |
| using TestingBase = bthost::testing::AdapterTestFixture; |
| |
| class FIDL_LowEnergyCentralServerTest : public TestingBase { |
| public: |
| FIDL_LowEnergyCentralServerTest() = default; |
| ~FIDL_LowEnergyCentralServerTest() override = default; |
| |
| void SetUp() override { |
| AdapterTestFixture::SetUp(); |
| |
| // Create a LowEnergyCentralServer and bind it to a local client. |
| fidl::InterfaceHandle<fble::Central> handle; |
| gatt_ = take_gatt(); |
| server_ = std::make_unique<LowEnergyCentralServer>(adapter(), handle.NewRequest(), |
| gatt_->AsWeakPtr()); |
| proxy_.Bind(std::move(handle)); |
| |
| bt::testing::FakeController::Settings settings; |
| settings.ApplyLegacyLEConfig(); |
| test_device()->set_settings(settings); |
| } |
| |
| void TearDown() override { |
| RunLoopUntilIdle(); |
| |
| proxy_ = nullptr; |
| server_ = nullptr; |
| gatt_ = nullptr; |
| |
| RunLoopUntilIdle(); |
| AdapterTestFixture::TearDown(); |
| } |
| |
| protected: |
| // Returns true if the given gatt.Client handle was closed after the event |
| // loop finishes processing. Returns false if the handle was not closed. |
| // Ownership of |handle| remains with the caller when this method returns. |
| bool IsClientHandleClosedAfterLoop(fidl::InterfaceHandle<fgatt::Client>* handle) { |
| ZX_ASSERT(handle); |
| |
| fgatt::ClientPtr proxy; |
| proxy.Bind(std::move(*handle)); |
| |
| bool closed = false; |
| proxy.set_error_handler([&](zx_status_t s) { |
| EXPECT_EQ(ZX_ERR_PEER_CLOSED, s); |
| closed = true; |
| }); |
| RunLoopUntilIdle(); |
| |
| *handle = proxy.Unbind(); |
| return closed; |
| } |
| |
| // Destroys the FIDL server. The le.Central proxy will be shut down and |
| // subsequent calls to `server()` will return nullptr. |
| void DestroyServer() { server_ = nullptr; } |
| |
| LowEnergyCentralServer* server() const { return server_.get(); } |
| fuchsia::bluetooth::le::Central* central_proxy() const { return proxy_.get(); } |
| |
| private: |
| std::unique_ptr<LowEnergyCentralServer> server_; |
| fble::CentralPtr proxy_; |
| std::unique_ptr<bt::gatt::GATT> gatt_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(FIDL_LowEnergyCentralServerTest); |
| }; |
| |
| // Tests that connecting to a peripheral with LowEnergyConnectionOptions.bondable_mode unset results |
| // in a bondable connection ref being stored in LowEnergyConnectionManager |
| TEST_F(FIDL_LowEnergyCentralServerTest, ConnectDefaultResultsBondableConnectionRef) { |
| auto* const peer = adapter()->peer_cache()->NewPeer(kTestAddr, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| |
| test_device()->AddPeer(std::make_unique<bt::testing::FakePeer>(kTestAddr)); |
| |
| fble::ConnectionOptions options; |
| |
| fidl::InterfaceHandle<fuchsia::bluetooth::gatt::Client> gatt_client; |
| fidl::InterfaceRequest<fuchsia::bluetooth::gatt::Client> gatt_client_req = |
| gatt_client.NewRequest(); |
| |
| auto status = |
| fidl_helpers::NewFidlError(fuchsia::bluetooth::ErrorCode::BAD_STATE, "this should change"); |
| auto callback = [&status](::fuchsia::bluetooth::Status cb_status) { |
| ASSERT_EQ(cb_status.error, nullptr); |
| status = std::move(cb_status); |
| }; |
| central_proxy()->ConnectPeripheral(peer->identifier().ToString(), std::move(options), |
| std::move(gatt_client_req), callback); |
| ASSERT_FALSE(server()->FindConnectionForTesting(peer->identifier())); |
| RunLoopUntilIdle(); |
| auto conn_ref = server()->FindConnectionForTesting(peer->identifier()); |
| ASSERT_EQ(status.error, nullptr); |
| ASSERT_TRUE(conn_ref.has_value()); |
| ASSERT_TRUE(conn_ref.value()); |
| ASSERT_EQ(conn_ref.value()->bondable_mode(), bt::sm::BondableMode::Bondable); |
| } |
| |
| // Tests that setting LowEnergyConnectionOptions.bondable_mode to true and connecting to a peer in |
| // bondable mode results in a bondable connection ref being stored in LowEnergyConnectionManager |
| TEST_F(FIDL_LowEnergyCentralServerTest, ConnectBondableResultsBondableConnectionRef) { |
| auto* const peer = adapter()->peer_cache()->NewPeer(kTestAddr, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| |
| test_device()->AddPeer(std::make_unique<bt::testing::FakePeer>(kTestAddr)); |
| |
| fble::ConnectionOptions options; |
| options.set_bondable_mode(true); |
| |
| fidl::InterfaceHandle<fuchsia::bluetooth::gatt::Client> gatt_client; |
| fidl::InterfaceRequest<fuchsia::bluetooth::gatt::Client> gatt_client_req = |
| gatt_client.NewRequest(); |
| |
| auto status = |
| fidl_helpers::NewFidlError(fuchsia::bluetooth::ErrorCode::BAD_STATE, "this should change"); |
| auto callback = [&status](::fuchsia::bluetooth::Status cb_status) { |
| ASSERT_EQ(cb_status.error, nullptr); |
| status = std::move(cb_status); |
| }; |
| central_proxy()->ConnectPeripheral(peer->identifier().ToString(), std::move(options), |
| std::move(gatt_client_req), callback); |
| ASSERT_FALSE(server()->FindConnectionForTesting(peer->identifier())); |
| RunLoopUntilIdle(); |
| auto conn_ref = server()->FindConnectionForTesting(peer->identifier()); |
| ASSERT_EQ(status.error, nullptr); |
| ASSERT_TRUE(conn_ref.has_value()); |
| ASSERT_TRUE(conn_ref.value()); |
| ASSERT_EQ(conn_ref.value()->bondable_mode(), bt::sm::BondableMode::Bondable); |
| } |
| |
| // Tests that setting LowEnergyConnectionOptions.bondable_mode to false and connecting to a peer |
| // results in a non-bondable connection ref being stored in LowEnergyConnectionManager. |
| TEST_F(FIDL_LowEnergyCentralServerTest, ConnectNonBondableResultsNonBondableConnectionRef) { |
| auto* const peer = adapter()->peer_cache()->NewPeer(kTestAddr, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| |
| test_device()->AddPeer(std::make_unique<bt::testing::FakePeer>(kTestAddr)); |
| |
| fble::ConnectionOptions options; |
| options.set_bondable_mode(false); |
| |
| fidl::InterfaceHandle<fuchsia::bluetooth::gatt::Client> gatt_client; |
| fidl::InterfaceRequest<fuchsia::bluetooth::gatt::Client> gatt_client_req = |
| gatt_client.NewRequest(); |
| |
| auto status = |
| fidl_helpers::NewFidlError(fuchsia::bluetooth::ErrorCode::BAD_STATE, "this should change"); |
| auto callback = [&status](::fuchsia::bluetooth::Status cb_status) { |
| ASSERT_EQ(cb_status.error, nullptr); |
| status = std::move(cb_status); |
| }; |
| central_proxy()->ConnectPeripheral(peer->identifier().ToString(), std::move(options), |
| std::move(gatt_client_req), callback); |
| ASSERT_FALSE(server()->FindConnectionForTesting(peer->identifier())); |
| RunLoopUntilIdle(); |
| auto conn_ref = server()->FindConnectionForTesting(peer->identifier()); |
| ASSERT_EQ(status.error, nullptr); |
| ASSERT_TRUE(conn_ref.has_value()); |
| ASSERT_TRUE(conn_ref.value()); |
| ASSERT_EQ(conn_ref.value()->bondable_mode(), bt::sm::BondableMode::NonBondable); |
| } |
| |
| TEST_F(FIDL_LowEnergyCentralServerTest, DisconnectUnconnectedPeripheralReturnsSuccess) { |
| auto status = |
| fidl_helpers::NewFidlError(fuchsia::bluetooth::ErrorCode::BAD_STATE, "this should change"); |
| auto callback = [&status](::fuchsia::bluetooth::Status cb_status) { |
| status = std::move(cb_status); |
| }; |
| central_proxy()->DisconnectPeripheral(bt::PeerId(1).ToString(), std::move(callback)); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(status.error, nullptr); |
| } |
| |
| TEST_F(FIDL_LowEnergyCentralServerTest, FailedConnectionCleanedUp) { |
| auto* const peer = adapter()->peer_cache()->NewPeer(kTestAddr, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| |
| test_device()->AddPeer(std::make_unique<bt::testing::FakePeer>(kTestAddr)); |
| |
| fble::ConnectionOptions options; |
| |
| fidl::InterfaceHandle<fuchsia::bluetooth::gatt::Client> gatt_client; |
| fidl::InterfaceRequest<fuchsia::bluetooth::gatt::Client> gatt_client_req = |
| gatt_client.NewRequest(); |
| |
| fuchsia::bluetooth::Status status; |
| auto callback = [&status](::fuchsia::bluetooth::Status cb_status) { |
| status = std::move(cb_status); |
| }; |
| |
| test_device()->SetDefaultCommandStatus(bt::hci::kReadRemoteVersionInfo, |
| bt::hci::StatusCode::kConnectionLimitExceeded); |
| |
| ASSERT_FALSE(server()->FindConnectionForTesting(peer->identifier()).has_value()); |
| central_proxy()->ConnectPeripheral(peer->identifier().ToString(), std::move(options), |
| std::move(gatt_client_req), callback); |
| RunLoopUntilIdle(); |
| auto conn = server()->FindConnectionForTesting(peer->identifier()); |
| EXPECT_NE(status.error, nullptr); |
| EXPECT_FALSE(conn.has_value()); |
| } |
| |
| TEST_F(FIDL_LowEnergyCentralServerTest, ConnectPeripheralAlreadyConnectedInLecm) { |
| auto* const peer = adapter()->peer_cache()->NewPeer(kTestAddr, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<bt::testing::FakePeer>(kTestAddr)); |
| |
| std::unique_ptr<bt::gap::LowEnergyConnectionHandle> le_conn; |
| adapter()->le()->Connect( |
| peer->identifier(), |
| [&le_conn](auto result) { |
| ASSERT_TRUE(result.is_ok()); |
| le_conn = result.take_value(); |
| }, |
| bt::gap::LowEnergyConnectionOptions()); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(le_conn); |
| ASSERT_FALSE(server()->FindConnectionForTesting(peer->identifier()).has_value()); |
| |
| fuchsia::bluetooth::Status status; |
| auto callback = [&status](::fuchsia::bluetooth::Status cb_status) { |
| status = std::move(cb_status); |
| }; |
| |
| fble::ConnectionOptions options; |
| fidl::InterfaceHandle<fuchsia::bluetooth::gatt::Client> gatt_client; |
| fidl::InterfaceRequest<fuchsia::bluetooth::gatt::Client> gatt_client_req = |
| gatt_client.NewRequest(); |
| central_proxy()->ConnectPeripheral(peer->identifier().ToString(), std::move(options), |
| std::move(gatt_client_req), callback); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(status.error, nullptr); |
| auto server_conn = server()->FindConnectionForTesting(peer->identifier()); |
| ASSERT_TRUE(server_conn.has_value()); |
| EXPECT_NE(server_conn.value(), nullptr); |
| } |
| |
| TEST_F(FIDL_LowEnergyCentralServerTest, ConnectPeripheralUnknownPeer) { |
| fuchsia::bluetooth::Status status; |
| auto callback = [&status](::fuchsia::bluetooth::Status cb_status) { |
| status = std::move(cb_status); |
| }; |
| |
| const bt::PeerId peer_id(1); |
| |
| fble::ConnectionOptions options; |
| fidl::InterfaceHandle<fuchsia::bluetooth::gatt::Client> gatt_client; |
| fidl::InterfaceRequest<fuchsia::bluetooth::gatt::Client> gatt_client_req = |
| gatt_client.NewRequest(); |
| central_proxy()->ConnectPeripheral(peer_id.ToString(), std::move(options), |
| std::move(gatt_client_req), callback); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(status.error); |
| EXPECT_EQ(status.error->error_code, fuchsia::bluetooth::ErrorCode::NOT_FOUND); |
| auto server_conn = server()->FindConnectionForTesting(peer_id); |
| EXPECT_FALSE(server_conn.has_value()); |
| } |
| |
| TEST_F(FIDL_LowEnergyCentralServerTest, DisconnectPeripheralClosesCorrectGattHandle) { |
| const bt::DeviceAddress kAddr1 = kTestAddr; |
| const bt::DeviceAddress kAddr2(bt::DeviceAddress::Type::kLEPublic, {2, 0, 0, 0, 0, 0}); |
| auto* const peer1 = adapter()->peer_cache()->NewPeer(kAddr1, /*connectable=*/true); |
| auto* const peer2 = adapter()->peer_cache()->NewPeer(kAddr2, /*connectable=*/true); |
| |
| test_device()->AddPeer(std::make_unique<bt::testing::FakePeer>(kAddr1)); |
| test_device()->AddPeer(std::make_unique<bt::testing::FakePeer>(kAddr2)); |
| |
| // Establish two connections. |
| fidl::InterfaceHandle<fgatt::Client> handle1, handle2; |
| central_proxy()->ConnectPeripheral(peer1->identifier().ToString(), fble::ConnectionOptions{}, |
| handle1.NewRequest(), [](auto) {}); |
| central_proxy()->ConnectPeripheral(peer2->identifier().ToString(), fble::ConnectionOptions{}, |
| handle2.NewRequest(), [](auto) {}); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(server()->FindConnectionForTesting(peer1->identifier())); |
| ASSERT_TRUE(server()->FindConnectionForTesting(peer2->identifier())); |
| EXPECT_FALSE(IsClientHandleClosedAfterLoop(&handle1)); |
| EXPECT_FALSE(IsClientHandleClosedAfterLoop(&handle2)); |
| |
| // Disconnect peer1. Only its gatt.Client handle should close. |
| central_proxy()->DisconnectPeripheral(peer1->identifier().ToString(), [](auto) {}); |
| EXPECT_TRUE(IsClientHandleClosedAfterLoop(&handle1)); |
| EXPECT_FALSE(IsClientHandleClosedAfterLoop(&handle2)); |
| |
| // Disconnect peer2. Its handle should close now. |
| central_proxy()->DisconnectPeripheral(peer2->identifier().ToString(), [](auto) {}); |
| EXPECT_TRUE(IsClientHandleClosedAfterLoop(&handle2)); |
| } |
| |
| TEST_F(FIDL_LowEnergyCentralServerTest, PeerDisconnectClosesCorrectHandle) { |
| const bt::DeviceAddress kAddr1 = kTestAddr; |
| const bt::DeviceAddress kAddr2(bt::DeviceAddress::Type::kLEPublic, {2, 0, 0, 0, 0, 0}); |
| auto* const peer1 = adapter()->peer_cache()->NewPeer(kAddr1, /*connectable=*/true); |
| auto* const peer2 = adapter()->peer_cache()->NewPeer(kAddr2, /*connectable=*/true); |
| |
| test_device()->AddPeer(std::make_unique<bt::testing::FakePeer>(kAddr1)); |
| test_device()->AddPeer(std::make_unique<bt::testing::FakePeer>(kAddr2)); |
| |
| // Establish two connections. |
| fidl::InterfaceHandle<fgatt::Client> handle1, handle2; |
| central_proxy()->ConnectPeripheral(peer1->identifier().ToString(), fble::ConnectionOptions{}, |
| handle1.NewRequest(), [](auto) {}); |
| central_proxy()->ConnectPeripheral(peer2->identifier().ToString(), fble::ConnectionOptions{}, |
| handle2.NewRequest(), [](auto) {}); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(server()->FindConnectionForTesting(peer1->identifier())); |
| ASSERT_TRUE(server()->FindConnectionForTesting(peer2->identifier())); |
| EXPECT_FALSE(IsClientHandleClosedAfterLoop(&handle1)); |
| EXPECT_FALSE(IsClientHandleClosedAfterLoop(&handle2)); |
| |
| // Disconnect peer1. Only its gatt.Client handle should close. |
| test_device()->Disconnect(kAddr1); |
| EXPECT_TRUE(IsClientHandleClosedAfterLoop(&handle1)); |
| EXPECT_FALSE(IsClientHandleClosedAfterLoop(&handle2)); |
| |
| // Disconnect peer2. Its handle should close now. |
| test_device()->Disconnect(kAddr2); |
| EXPECT_TRUE(IsClientHandleClosedAfterLoop(&handle2)); |
| } |
| |
| TEST_F(FIDL_LowEnergyCentralServerTest, ClosingCentralHandleClosesAssociatedGattClientHandles) { |
| const bt::DeviceAddress kAddr1 = kTestAddr; |
| const bt::DeviceAddress kAddr2(bt::DeviceAddress::Type::kLEPublic, {2, 0, 0, 0, 0, 0}); |
| auto* const peer1 = adapter()->peer_cache()->NewPeer(kAddr1, /*connectable=*/true); |
| auto* const peer2 = adapter()->peer_cache()->NewPeer(kAddr2, /*connectable=*/true); |
| |
| test_device()->AddPeer(std::make_unique<bt::testing::FakePeer>(kAddr1)); |
| test_device()->AddPeer(std::make_unique<bt::testing::FakePeer>(kAddr2)); |
| |
| // Establish two connections. |
| fidl::InterfaceHandle<fgatt::Client> handle1, handle2; |
| central_proxy()->ConnectPeripheral(peer1->identifier().ToString(), fble::ConnectionOptions{}, |
| handle1.NewRequest(), [](auto) {}); |
| central_proxy()->ConnectPeripheral(peer2->identifier().ToString(), fble::ConnectionOptions{}, |
| handle2.NewRequest(), [](auto) {}); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(server()->FindConnectionForTesting(peer1->identifier())); |
| ASSERT_TRUE(server()->FindConnectionForTesting(peer2->identifier())); |
| EXPECT_FALSE(IsClientHandleClosedAfterLoop(&handle1)); |
| EXPECT_FALSE(IsClientHandleClosedAfterLoop(&handle2)); |
| |
| DestroyServer(); |
| EXPECT_TRUE(IsClientHandleClosedAfterLoop(&handle1)); |
| EXPECT_TRUE(IsClientHandleClosedAfterLoop(&handle2)); |
| } |
| |
| } // namespace |
| } // namespace bthost |