blob: d4f04b614560a53b844108d61f16975ecc3fb365 [file] [log] [blame]
// 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