text: // 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
  