blob: a345aa7d7321526e952cdf2e8b98a3b058d297e1 [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/host_server.h"
#include <fuchsia/bluetooth/cpp/fidl.h>
#include <fuchsia/bluetooth/sys/cpp/fidl.h>
#include <fuchsia/bluetooth/sys/cpp/fidl_test_base.h>
#include <lib/inspect/testing/cpp/inspect.h>
#include <lib/zx/channel.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "adapter_test_fixture.h"
#include "fuchsia/bluetooth/host/cpp/fidl.h"
#include "helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/common/device_address.h"
#include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/fake_adapter_test_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/gap.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/low_energy_address_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/gatt/fake_layer.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_channel.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_l2cap.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/smp.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/controller_test.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/fake_peer.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/test_packets.h"
namespace fuchsia::bluetooth {
// Make PeerIds equality comparable for advanced testing matchers. ADL rules mandate the namespace.
bool operator==(const PeerId& a, const PeerId& b) { return fidl::Equals(a, b); }
} // namespace fuchsia::bluetooth
namespace bthost {
namespace {
using namespace inspect::testing;
// Limiting the de-scoped aliases here helps test cases be more specific about whether they're using
// FIDL names or bt-host internal names.
using bt::CreateStaticByteBuffer;
using bt::LowerBits;
using bt::UpperBits;
using bt::l2cap::testing::FakeChannel;
using bt::sm::AuthReq;
using bt::sm::KeyDistGen;
using bt::testing::FakePeer;
namespace fbt = fuchsia::bluetooth;
namespace fsys = fuchsia::bluetooth::sys;
const bt::PeerId kTestId(1);
const bt::DeviceAddress kLeTestAddr(bt::DeviceAddress::Type::kLEPublic, {0x01, 0, 0, 0, 0, 0});
const bt::DeviceAddress kBredrTestAddr(bt::DeviceAddress::Type::kBREDR, {0x01, 0, 0, 0, 0, 0});
const fbt::Address kTestFidlAddrPublic{fbt::AddressType::PUBLIC, {1, 0, 0, 0, 0, 0}};
const fbt::Address kTestFidlAddrRandom{fbt::AddressType::RANDOM, {2, 0, 0, 0, 0, 0}};
// TODO(fxbug.dev/64167): Replace GMock usage with homegrown mocks.
class MockPairingDelegate : public fsys::testing::PairingDelegate_TestBase {
public:
MockPairingDelegate(fidl::InterfaceRequest<PairingDelegate> request,
async_dispatcher_t* dispatcher)
: binding_(this, std::move(request), dispatcher) {}
~MockPairingDelegate() override = default;
MOCK_METHOD(void, OnPairingRequest,
(fsys::Peer device, fsys::PairingMethod method, uint32_t displayed_passkey,
OnPairingRequestCallback callback),
(override));
MOCK_METHOD(void, OnPairingComplete, (fbt::PeerId id, bool success), (override));
MOCK_METHOD(void, OnRemoteKeypress, (fbt::PeerId id, fsys::PairingKeypress keypress), (override));
private:
fidl::Binding<PairingDelegate> binding_;
void NotImplemented_(const std::string& name) override {
FAIL() << name << " is not implemented";
}
};
class MockFidlPairingDelegate : public fsys::testing::PairingDelegate_TestBase {
public:
using PairingRequestCallback =
fit::function<void(fsys::Peer, fsys::PairingMethod, uint32_t, OnPairingRequestCallback)>;
MockFidlPairingDelegate(fidl::InterfaceRequest<PairingDelegate> request,
async_dispatcher_t* dispatcher)
: binding_(this, std::move(request), dispatcher) {}
~MockFidlPairingDelegate() override = default;
void OnPairingRequest(fsys::Peer device, fsys::PairingMethod method, uint32_t displayed_passkey,
OnPairingRequestCallback callback) override {
pairing_request_cb_(std::move(device), method, displayed_passkey, std::move(callback));
}
void set_pairing_request_cb(PairingRequestCallback cb) { pairing_request_cb_ = std::move(cb); }
private:
void NotImplemented_(const std::string& name) override {
FAIL() << name << " is not implemented";
}
fidl::Binding<PairingDelegate> binding_;
PairingRequestCallback pairing_request_cb_;
};
class FIDL_HostServerTest : public bthost::testing::AdapterTestFixture {
public:
FIDL_HostServerTest() = default;
~FIDL_HostServerTest() override = default;
void SetUp() override {
AdapterTestFixture::SetUp();
gatt_ = take_gatt();
ResetHostServer();
}
void ResetHostServer() {
fidl::InterfaceHandle<fuchsia::bluetooth::host::Host> host_handle;
host_server_ = std::make_unique<HostServer>(host_handle.NewRequest().TakeChannel(),
adapter()->AsWeakPtr(), gatt_->AsWeakPtr());
host_.Bind(std::move(host_handle));
}
void TearDown() override {
RunLoopUntilIdle();
host_ = nullptr;
host_server_ = nullptr;
gatt_ = nullptr;
AdapterTestFixture::TearDown();
}
protected:
HostServer* host_server() const { return host_server_.get(); }
fuchsia::bluetooth::host::Host* host_client() const { return host_.get(); }
// Mutable reference to the Host client interface pointer.
fuchsia::bluetooth::host::HostPtr& host_client_ptr() { return host_; }
// Create and bind a MockPairingDelegate and attach it to the HostServer under test. It is
// heap-allocated to permit its explicit destruction.
[[nodiscard]] std::unique_ptr<MockPairingDelegate> SetMockPairingDelegate(
fsys::InputCapability input_capability, fsys::OutputCapability output_capability) {
using ::testing::StrictMock;
fidl::InterfaceHandle<fsys::PairingDelegate> pairing_delegate_handle;
auto pairing_delegate = std::make_unique<StrictMock<MockPairingDelegate>>(
pairing_delegate_handle.NewRequest(), dispatcher());
host_client()->SetPairingDelegate(input_capability, output_capability,
std::move(pairing_delegate_handle));
// Wait for the Access/SetPairingDelegate message to process.
RunLoopUntilIdle();
return pairing_delegate;
}
// Create and bind a MockFidlPairingDelegate and attach it to the HostServer under test. It is
// heap-allocated to permit its explicit destruction.
[[nodiscard]] std::unique_ptr<MockFidlPairingDelegate> SetMockFidlPairingDelegate(
fsys::InputCapability input_capability, fsys::OutputCapability output_capability) {
using ::testing::StrictMock;
fidl::InterfaceHandle<fsys::PairingDelegate> pairing_delegate_handle;
auto pairing_delegate = std::make_unique<MockFidlPairingDelegate>(
pairing_delegate_handle.NewRequest(), dispatcher());
host_client()->SetPairingDelegate(input_capability, output_capability,
std::move(pairing_delegate_handle));
// Wait for the Access/SetPairingDelegate message to process.
RunLoopUntilIdle();
return pairing_delegate;
}
bt::gap::Peer* AddFakePeer(const bt::DeviceAddress& address) {
bt::gap::Peer* peer = adapter()->peer_cache()->NewPeer(address, /*connectable=*/true);
ZX_ASSERT(peer);
ZX_ASSERT(peer->temporary());
test_device()->AddPeer(std::make_unique<FakePeer>(address));
return peer;
}
using ConnectResult = fit::result<void, fsys::Error>;
std::optional<ConnectResult> ConnectFakePeer(bt::PeerId id) {
std::optional<ConnectResult> result;
host_client()->Connect(fbt::PeerId{id.value()},
[&](ConnectResult _result) { result = _result; });
RunLoopUntilIdle();
return result;
}
std::tuple<bt::gap::Peer*, FakeChannel*> CreateAndConnectFakePeer(bool connect_le = true) {
auto address = connect_le ? kLeTestAddr : kBredrTestAddr;
bt::gap::Peer* peer = AddFakePeer(address);
// This is to capture the channel created during the Connection process
FakeChannel* fake_chan = nullptr;
l2cap()->set_channel_callback(
[&fake_chan](fbl::RefPtr<FakeChannel> new_fake_chan) { fake_chan = new_fake_chan.get(); });
auto connect_result = ConnectFakePeer(peer->identifier());
if (!connect_result || connect_result->is_error()) {
peer = nullptr;
fake_chan = nullptr;
}
return std::make_tuple(peer, fake_chan);
}
// Calls the RestoreBonds method and verifies that the callback is run with the expected output.
void TestRestoreBonds(std::vector<fsys::BondingData> bonds,
std::vector<fsys::BondingData> expected) {
bool called = false;
host_server()->RestoreBonds(std::move(bonds), [&](auto errors) {
called = true;
ASSERT_EQ(expected.size(), errors.size());
for (size_t i = 0; i < errors.size(); i++) {
SCOPED_TRACE(i);
EXPECT_TRUE(fidl::Equals(errors[i], expected[i]));
}
});
EXPECT_TRUE(called);
}
private:
std::unique_ptr<HostServer> host_server_;
std::unique_ptr<bt::gatt::GATT> gatt_;
fuchsia::bluetooth::host::HostPtr host_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(FIDL_HostServerTest);
};
// The main role of this sub-suite is improved test object lifecycle management (see TearDown for
// more details). An additional convenience it provides is fake peer/channel and mock pairing
// delegate setup, which all tests of the full pairing stack need.
class FIDL_HostServerPairingTest : public FIDL_HostServerTest {
public:
void SetUp() override {
FIDL_HostServerTest::SetUp();
NewPairingTest(fsys::InputCapability::NONE, fsys::OutputCapability::NONE);
}
void NewPairingTest(fsys::InputCapability input_cap, fsys::OutputCapability output_cap,
bool is_le = true) {
pairing_delegate_ = SetMockPairingDelegate(input_cap, output_cap);
if (!fake_peer_ || !fake_chan_) {
ASSERT_FALSE(fake_peer_);
ASSERT_FALSE(fake_chan_);
std::tie(fake_peer_, fake_chan_) = CreateAndConnectFakePeer(is_le);
ASSERT_TRUE(fake_peer_);
ASSERT_TRUE(fake_chan_);
ASSERT_EQ(bt::gap::Peer::ConnectionState::kConnected, fake_peer_->le()->connection_state());
}
}
// With the base HostServerTest, it is too easy to set up callbacks related to fake channels or
// the mock pairing delegate that lead to unexpected failure callbacks, or worse, use-after-
// frees. These failures mostly stem from the Host server notifying the client upon pairing
// delegate destruction, which is not important behavior for many tests.
void TearDown() override {
fake_chan_->SetSendCallback(nullptr, nullptr);
host_client_ptr().Unbind();
FIDL_HostServerTest::TearDown();
}
bt::gap::Peer* peer() { return fake_peer_; }
FakeChannel* fake_chan() { return fake_chan_; }
private:
std::unique_ptr<MockPairingDelegate> pairing_delegate_;
bt::gap::Peer* fake_peer_ = nullptr;
FakeChannel* fake_chan_ = nullptr;
};
// Constructs a vector of a fidl::Clone'able data type that contains a copy of the input |data|.
// This allows move-only FIDL types to be re-used in test cases that need to refer to such data.
//
// Returns an empty vector if |data| could not be copied, e.g. because it contains handles that
// cannot be duplicated.
template <typename T>
std::vector<T> MakeClonedVector(const T& data) {
std::vector<T> output;
T clone;
zx_status_t status = fidl::Clone(data, &clone);
EXPECT_EQ(ZX_OK, status);
if (status == ZX_OK) {
output.push_back(std::move(clone));
}
return output;
}
// Construct bonding data structure for testing using the given ID and address and an empty LE bond
// structure.
fsys::BondingData MakeTestBond(bt::PeerId id, fbt::Address address) {
fsys::BondingData bond;
bond.set_identifier(fbt::PeerId{id.value()});
bond.set_address(address);
bond.set_le_bond(fsys::LeBondData());
return bond;
}
TEST_F(FIDL_HostServerTest, FidlIoCapabilitiesMapToHostIoCapability) {
// Isolate HostServer's private bt::gap::PairingDelegate implementation.
auto host_pairing_delegate = static_cast<bt::gap::PairingDelegate*>(host_server());
// Getter should be safe to call when no PairingDelegate assigned.
EXPECT_EQ(bt::sm::IOCapability::kNoInputNoOutput, host_pairing_delegate->io_capability());
auto fidl_pairing_delegate =
SetMockPairingDelegate(fsys::InputCapability::KEYBOARD, fsys::OutputCapability::DISPLAY);
EXPECT_EQ(bt::sm::IOCapability::kKeyboardDisplay, host_pairing_delegate->io_capability());
}
TEST_F(FIDL_HostServerTest, HostCompletePairingCallsFidlOnPairingComplete) {
using namespace ::testing;
// Isolate HostServer's private bt::gap::PairingDelegate implementation.
auto host_pairing_delegate = static_cast<bt::gap::PairingDelegate*>(host_server());
auto fidl_pairing_delegate =
SetMockPairingDelegate(fsys::InputCapability::KEYBOARD, fsys::OutputCapability::DISPLAY);
// fuchsia::bluetooth::PeerId has no equality operator
EXPECT_CALL(*fidl_pairing_delegate, OnPairingComplete(_, false))
.WillOnce([](fbt::PeerId id, Unused) { EXPECT_EQ(0xc0decafe, id.value); });
host_pairing_delegate->CompletePairing(bt::PeerId(0xc0decafe),
bt::sm::Status(bt::sm::ErrorCode::kConfirmValueFailed));
// Wait for the PairingDelegate/OnPairingComplete message to process.
RunLoopUntilIdle();
}
TEST_F(FIDL_HostServerTest, HostConfirmPairingRequestsConsentPairingOverFidl) {
using namespace ::testing;
auto host_pairing_delegate = static_cast<bt::gap::PairingDelegate*>(host_server());
auto fidl_pairing_delegate =
SetMockPairingDelegate(fsys::InputCapability::KEYBOARD, fsys::OutputCapability::DISPLAY);
auto* const peer = adapter()->peer_cache()->NewPeer(kLeTestAddr, /*connectable=*/true);
ASSERT_TRUE(peer);
EXPECT_CALL(*fidl_pairing_delegate, OnPairingRequest(_, fsys::PairingMethod::CONSENT, 0u, _))
.WillOnce(
[id = peer->identifier()](fsys::Peer peer, Unused, Unused,
fsys::PairingDelegate::OnPairingRequestCallback callback) {
ASSERT_TRUE(peer.has_id());
EXPECT_EQ(id.value(), peer.id().value);
callback(/*accept=*/true, /*entered_passkey=*/0u);
});
bool confirm_cb_value = false;
bt::gap::PairingDelegate::ConfirmCallback confirm_cb = [&confirm_cb_value](bool confirmed) {
confirm_cb_value = confirmed;
};
host_pairing_delegate->ConfirmPairing(peer->identifier(), std::move(confirm_cb));
// Wait for the PairingDelegate/OnPairingRequest message to process.
RunLoopUntilIdle();
EXPECT_TRUE(confirm_cb_value);
}
TEST_F(FIDL_HostServerTest,
HostDisplayPasskeyRequestsPasskeyDisplayOrNumericComparisonPairingOverFidl) {
using namespace ::testing;
auto host_pairing_delegate = static_cast<bt::gap::PairingDelegate*>(host_server());
auto fidl_pairing_delegate =
SetMockPairingDelegate(fsys::InputCapability::KEYBOARD, fsys::OutputCapability::DISPLAY);
auto* const peer = adapter()->peer_cache()->NewPeer(kLeTestAddr, /*connectable=*/true);
ASSERT_TRUE(peer);
// This call should use PASSKEY_DISPLAY to request that the user perform peer passkey entry.
using OnPairingRequestCallback = fsys::PairingDelegate::OnPairingRequestCallback;
EXPECT_CALL(*fidl_pairing_delegate,
OnPairingRequest(_, fsys::PairingMethod::PASSKEY_DISPLAY, 12345, _))
.WillOnce([id = peer->identifier()](fsys::Peer peer, Unused, Unused,
OnPairingRequestCallback callback) {
ASSERT_TRUE(peer.has_id());
EXPECT_EQ(id.value(), peer.id().value);
callback(/*accept=*/false, /*entered_passkey=*/0u);
});
bool confirm_cb_called = false;
auto confirm_cb = [&confirm_cb_called](bool confirmed) {
EXPECT_FALSE(confirmed);
confirm_cb_called = true;
};
using DisplayMethod = bt::gap::PairingDelegate::DisplayMethod;
host_pairing_delegate->DisplayPasskey(peer->identifier(), 12345, DisplayMethod::kPeerEntry,
confirm_cb);
// Wait for the PairingDelegate/OnPairingRequest message to process.
RunLoopUntilIdle();
EXPECT_TRUE(confirm_cb_called);
// This call should use PASSKEY_COMPARISON to request that the user compare the passkeys shown on
// the local and peer devices.
EXPECT_CALL(*fidl_pairing_delegate,
OnPairingRequest(_, fsys::PairingMethod::PASSKEY_COMPARISON, 12345, _))
.WillOnce([id = peer->identifier()](fsys::Peer peer, Unused, Unused,
OnPairingRequestCallback callback) {
ASSERT_TRUE(peer.has_id());
EXPECT_EQ(id.value(), peer.id().value);
callback(/*accept=*/false, /*entered_passkey=*/0u);
});
confirm_cb_called = false;
host_pairing_delegate->DisplayPasskey(peer->identifier(), 12345, DisplayMethod::kComparison,
confirm_cb);
// Wait for the PairingDelegate/OnPairingRequest message to process.
RunLoopUntilIdle();
EXPECT_TRUE(confirm_cb_called);
}
TEST_F(FIDL_HostServerTest, HostRequestPasskeyRequestsPasskeyEntryPairingOverFidl) {
using namespace ::testing;
auto host_pairing_delegate = static_cast<bt::gap::PairingDelegate*>(host_server());
auto fidl_pairing_delegate =
SetMockPairingDelegate(fsys::InputCapability::KEYBOARD, fsys::OutputCapability::DISPLAY);
auto* const peer = adapter()->peer_cache()->NewPeer(kLeTestAddr, /*connectable=*/true);
ASSERT_TRUE(peer);
using OnPairingRequestCallback = fsys::PairingDelegate::OnPairingRequestCallback;
EXPECT_CALL(*fidl_pairing_delegate,
OnPairingRequest(_, fsys::PairingMethod::PASSKEY_ENTRY, 0u, _))
.WillOnce([id = peer->identifier()](fsys::Peer peer, Unused, Unused,
OnPairingRequestCallback callback) {
ASSERT_TRUE(peer.has_id());
EXPECT_EQ(id.value(), peer.id().value);
callback(/*accept=*/false, 12345);
})
.WillOnce([id = peer->identifier()](fsys::Peer peer, Unused, Unused,
OnPairingRequestCallback callback) {
ASSERT_TRUE(peer.has_id());
EXPECT_EQ(id.value(), peer.id().value);
callback(/*accept=*/true, 0u);
})
.WillOnce([id = peer->identifier()](fsys::Peer peer, Unused, Unused,
OnPairingRequestCallback callback) {
ASSERT_TRUE(peer.has_id());
EXPECT_EQ(id.value(), peer.id().value);
callback(/*accept=*/true, 12345);
});
std::optional<int64_t> passkey_response;
auto response_cb = [&passkey_response](int64_t passkey) { passkey_response = passkey; };
// The first request is rejected and should receive a negative passkey value, regardless what was
// passed over FIDL (i.e. 12345).
host_pairing_delegate->RequestPasskey(peer->identifier(), response_cb);
RunLoopUntilIdle();
ASSERT_TRUE(passkey_response.has_value());
EXPECT_LT(passkey_response.value(), 0);
// The second request should be accepted with the passkey set to "0".
passkey_response.reset();
host_pairing_delegate->RequestPasskey(peer->identifier(), response_cb);
RunLoopUntilIdle();
ASSERT_TRUE(passkey_response.has_value());
EXPECT_EQ(0, passkey_response.value());
// The third request should be accepted with the passkey set to "12345".
passkey_response.reset();
host_pairing_delegate->RequestPasskey(peer->identifier(), response_cb);
RunLoopUntilIdle();
ASSERT_TRUE(passkey_response.has_value());
EXPECT_EQ(12345, passkey_response.value());
}
TEST_F(FIDL_HostServerTest, SysDelegateInvokesCallbackMultipleTimesIgnored) {
using namespace ::testing;
auto host_pairing_delegate = static_cast<bt::gap::PairingDelegate*>(host_server());
auto fidl_pairing_delegate =
SetMockFidlPairingDelegate(fsys::InputCapability::KEYBOARD, fsys::OutputCapability::DISPLAY);
auto* const peer = adapter()->peer_cache()->NewPeer(kLeTestAddr, /*connectable=*/true);
ASSERT_TRUE(peer);
using OnPairingRequestCallback = fsys::PairingDelegate::OnPairingRequestCallback;
OnPairingRequestCallback fidl_passkey_req_cb = nullptr, fidl_confirm_req_cb = nullptr;
fidl_pairing_delegate->set_pairing_request_cb(
[id = peer->identifier(), &fidl_passkey_req_cb, &fidl_confirm_req_cb](
fsys::Peer peer, fsys::PairingMethod method, auto /*ignore*/,
OnPairingRequestCallback callback) {
ASSERT_TRUE(peer.has_id());
EXPECT_EQ(id.value(), peer.id().value);
if (method == fsys::PairingMethod::PASSKEY_ENTRY) {
fidl_passkey_req_cb = std::move(callback);
} else if (method == fsys::PairingMethod::CONSENT) {
fidl_confirm_req_cb = std::move(callback);
} else {
FAIL() << "unexpected pairing request method!";
}
});
size_t passkey_req_cb_count = 0, confirm_req_cb_count = 0;
auto passkey_response_cb = [&passkey_req_cb_count](int64_t /*ignore*/) {
passkey_req_cb_count++;
;
};
auto confirm_req_cb = [&confirm_req_cb_count](bool /*ignore*/) { confirm_req_cb_count++; };
host_pairing_delegate->RequestPasskey(peer->identifier(), passkey_response_cb);
host_pairing_delegate->ConfirmPairing(peer->identifier(), confirm_req_cb);
RunLoopUntilIdle();
ASSERT_TRUE(fidl_passkey_req_cb);
ASSERT_TRUE(fidl_confirm_req_cb);
ASSERT_EQ(0u, passkey_req_cb_count);
ASSERT_EQ(0u, confirm_req_cb_count);
fidl_passkey_req_cb(true, 12345);
fidl_confirm_req_cb(true, 0);
RunLoopUntilIdle();
ASSERT_EQ(1u, passkey_req_cb_count);
ASSERT_EQ(1u, confirm_req_cb_count);
fidl_passkey_req_cb(true, 456789);
fidl_confirm_req_cb(true, 0);
RunLoopUntilIdle();
ASSERT_EQ(1u, passkey_req_cb_count);
ASSERT_EQ(1u, confirm_req_cb_count);
}
TEST_F(FIDL_HostServerTest, WatchState) {
std::optional<fsys::HostInfo> info;
host_server()->WatchState([&](auto value) { info = std::move(value); });
ASSERT_TRUE(info.has_value());
ASSERT_TRUE(info->has_id());
ASSERT_TRUE(info->has_technology());
ASSERT_TRUE(info->has_address());
ASSERT_TRUE(info->has_local_name());
ASSERT_TRUE(info->has_discoverable());
ASSERT_TRUE(info->has_discovering());
EXPECT_EQ(adapter()->identifier().value(), info->id().value);
EXPECT_EQ(fsys::TechnologyType::DUAL_MODE, info->technology());
EXPECT_EQ(fbt::AddressType::PUBLIC, info->address().type);
EXPECT_TRUE(
ContainersEqual(adapter()->state().controller_address().bytes(), info->address().bytes));
EXPECT_EQ("fuchsia", info->local_name());
EXPECT_FALSE(info->discoverable());
EXPECT_FALSE(info->discovering());
}
TEST_F(FIDL_HostServerTest, WatchDiscoveryState) {
std::optional<fsys::HostInfo> info;
// Make initial watch call so that subsequent calls remain pending.
host_server()->WatchState([&](auto value) { info = std::move(value); });
ASSERT_TRUE(info.has_value());
info.reset();
// Watch for updates.
host_server()->WatchState([&](auto value) { info = std::move(value); });
EXPECT_FALSE(info.has_value());
host_server()->StartDiscovery([](auto) {});
RunLoopUntilIdle();
ASSERT_TRUE(info.has_value());
ASSERT_TRUE(info->has_discovering());
EXPECT_TRUE(info->discovering());
info.reset();
host_server()->WatchState([&](auto value) { info = std::move(value); });
EXPECT_FALSE(info.has_value());
host_server()->StopDiscovery();
RunLoopUntilIdle();
ASSERT_TRUE(info.has_value());
ASSERT_TRUE(info->has_discovering());
EXPECT_FALSE(info->discovering());
}
TEST_F(FIDL_HostServerTest, WatchDiscoverableState) {
std::optional<fsys::HostInfo> info;
// Make initial watch call so that subsequent calls remain pending.
host_server()->WatchState([&](auto value) { info = std::move(value); });
ASSERT_TRUE(info.has_value());
info.reset();
// Watch for updates.
host_server()->WatchState([&](auto value) { info = std::move(value); });
EXPECT_FALSE(info.has_value());
host_server()->SetDiscoverable(true, [](auto) {});
RunLoopUntilIdle();
ASSERT_TRUE(info.has_value());
ASSERT_TRUE(info->has_discoverable());
EXPECT_TRUE(info->discoverable());
info.reset();
host_server()->WatchState([&](auto value) { info = std::move(value); });
EXPECT_FALSE(info.has_value());
host_server()->SetDiscoverable(false, [](auto) {});
RunLoopUntilIdle();
ASSERT_TRUE(info.has_value());
ASSERT_TRUE(info->has_discoverable());
EXPECT_FALSE(info->discoverable());
}
TEST_F(FIDL_HostServerPairingTest, InitiatePairingLeDefault) {
const auto kExpected = CreateStaticByteBuffer(
0x01, // code: "Pairing Request"
0x04, // IO cap.: KeyboardDisplay
0x00, // OOB: not present
AuthReq::kBondingFlag | AuthReq::kMITM | AuthReq::kSC | AuthReq::kCT2,
0x10, // encr. key size: 16 (default max)
KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys
KeyDistGen::kEncKey | KeyDistGen::kIdKey | KeyDistGen::kLinkKey // responder keys
);
// IOCapabilities must be KeyboardDisplay to support default MITM pairing request.
NewPairingTest(fsys::InputCapability::KEYBOARD, fsys::OutputCapability::DISPLAY);
bool pairing_request_sent = false;
// This test only checks that PairingState kicks off an LE pairing feature exchange correctly, as
// the call to Pair is only responsible for starting pairing, not for completing it.
auto expect_default_bytebuffer = [&pairing_request_sent, kExpected](bt::ByteBufferPtr sent) {
ASSERT_TRUE(sent);
ASSERT_EQ(*sent, kExpected);
pairing_request_sent = true;
};
fake_chan()->SetSendCallback(expect_default_bytebuffer, dispatcher());
std::optional<fit::result<void, fsys::Error>> pair_result;
fsys::PairingOptions opts;
host_client()->Pair(fbt::PeerId{peer()->identifier().value()}, std::move(opts),
[&](auto result) { pair_result = std::move(result); });
RunLoopUntilIdle();
// TODO(fxbug.dev/886): We don't have a good mechanism for driving pairing to completion without
// faking the entire SMP exchange. We should add SMP mocks that allows us to propagate a result up
// to the FIDL layer. For now we assert that pairing has started and remains pending.
ASSERT_FALSE(pair_result); // Pairing request is pending
ASSERT_TRUE(pairing_request_sent);
}
TEST_F(FIDL_HostServerPairingTest, InitiatePairingLeEncrypted) {
const auto kExpected = CreateStaticByteBuffer(
0x01, // code: "Pairing Request"
0x03, // IO cap.: NoInputNoOutput
0x00, // OOB: not present
AuthReq::kBondingFlag | AuthReq::kSC | AuthReq::kCT2,
0x10, // encr. key size: 16 (default max)
KeyDistGen::kEncKey | KeyDistGen::kLinkKey, // initiator keys
KeyDistGen::kEncKey | KeyDistGen::kIdKey | KeyDistGen::kLinkKey // responder keys
);
bool pairing_request_sent = false;
// This test only checks that PairingState kicks off an LE pairing feature exchange correctly, as
// the call to Pair is only responsible for starting pairing, not for completing it.
auto expect_default_bytebuffer = [&pairing_request_sent, kExpected](bt::ByteBufferPtr sent) {
ASSERT_TRUE(sent);
ASSERT_EQ(*sent, kExpected);
pairing_request_sent = true;
};
fake_chan()->SetSendCallback(expect_default_bytebuffer, dispatcher());
std::optional<fit::result<void, fsys::Error>> pair_result;
fsys::PairingOptions opts;
opts.set_le_security_level(fsys::PairingSecurityLevel::ENCRYPTED);
host_client()->Pair(fbt::PeerId{peer()->identifier().value()}, std::move(opts),
[&](auto result) { pair_result = std::move(result); });
RunLoopUntilIdle();
// TODO(fxbug.dev/886): We don't have a good mechanism for driving pairing to completion without
// faking the entire SMP exchange. We should add SMP mocks that allows us to propagate a result up
// to the FIDL layer. For now we assert that pairing has started and remains pending.
ASSERT_FALSE(pair_result); // Pairing request is pending
ASSERT_TRUE(pairing_request_sent);
}
TEST_F(FIDL_HostServerPairingTest, InitiatePairingNonBondableLe) {
const auto kExpected = CreateStaticByteBuffer(0x01, // code: "Pairing Request"
0x04, // IO cap.: KeyboardDisplay
0x00, // OOB: not present
AuthReq::kMITM | AuthReq::kSC | AuthReq::kCT2,
0x10, // encr. key size: 16 (default max)
0x00, // initiator keys: none
0x00 // responder keys: none
);
// IOCapabilities must be KeyboardDisplay to support default MITM pairing request.
NewPairingTest(fsys::InputCapability::KEYBOARD, fsys::OutputCapability::DISPLAY);
bool pairing_request_sent = false;
// This test only checks that PairingState kicks off an LE pairing feature exchange correctly, as
// the call to Pair is only responsible for starting pairing, not for completing it.
auto expect_default_bytebuffer = [&pairing_request_sent, kExpected](bt::ByteBufferPtr sent) {
ASSERT_TRUE(sent);
ASSERT_EQ(*sent, kExpected);
pairing_request_sent = true;
};
fake_chan()->SetSendCallback(expect_default_bytebuffer, dispatcher());
std::optional<fit::result<void, fsys::Error>> pair_result;
fsys::PairingOptions opts;
opts.set_bondable_mode(fsys::BondableMode::NON_BONDABLE);
host_client()->Pair(fbt::PeerId{peer()->identifier().value()}, std::move(opts),
[&](auto result) { pair_result = std::move(result); });
RunLoopUntilIdle();
// TODO(fxbug.dev/886): We don't have a good mechanism for driving pairing to completion without
// faking the entire SMP exchange. We should add SMP mocks that allows us to propagate a result up
// to the FIDL layer. For now we assert that pairing has started and remains pending.
ASSERT_FALSE(pair_result); // Pairing request is pending
ASSERT_TRUE(pairing_request_sent);
}
TEST_F(FIDL_HostServerTest, InitiateBrEdrPairingLePeerFails) {
auto [peer, fake_chan] = CreateAndConnectFakePeer();
ASSERT_TRUE(peer);
ASSERT_TRUE(fake_chan);
ASSERT_EQ(bt::gap::Peer::ConnectionState::kConnected, peer->le()->connection_state());
std::optional<fit::result<void, fsys::Error>> pair_result;
fsys::PairingOptions opts;
// Set pairing option with classic
opts.set_transport(fsys::TechnologyType::CLASSIC);
auto pair_cb = [&](auto result) {
ASSERT_TRUE(result.is_err());
pair_result = std::move(result);
};
host_client()->Pair(fbt::PeerId{peer->identifier().value()}, std::move(opts), std::move(pair_cb));
RunLoopUntilIdle();
ASSERT_TRUE(pair_result);
ASSERT_EQ(pair_result->error(), fsys::Error::PEER_NOT_FOUND);
}
TEST_F(FIDL_HostServerTest, WatchPeersHangsOnFirstCallWithNoExistingPeers) {
// By default the peer cache contains no entries when HostServer is first constructed. The first
// call to WatchPeers should hang.
bool replied = false;
host_server()->WatchPeers([&](auto, auto) { replied = true; });
EXPECT_FALSE(replied);
}
TEST_F(FIDL_HostServerTest, WatchPeersRepliesOnFirstCallWithExistingPeers) {
__UNUSED bt::gap::Peer* peer =
adapter()->peer_cache()->NewPeer(kLeTestAddr, /*connectable=*/true);
ResetHostServer();
// The first call to WatchPeers immediately resolves with the contents of the peer cache.
bool replied = false;
host_server()->WatchPeers([&](auto updated, auto removed) {
EXPECT_EQ(1u, updated.size());
EXPECT_TRUE(removed.empty());
replied = true;
});
EXPECT_TRUE(replied);
}
TEST_F(FIDL_HostServerTest, WatchPeersHandlesNonEnumeratedAppearanceInPeer) {
using namespace ::testing;
bt::gap::Peer* const peer = adapter()->peer_cache()->NewPeer(kLeTestAddr, /*connectable=*/true);
ASSERT_TRUE(peer);
bt::AdvertisingData adv_data;
// TODO(fxbug.dev/66358): fuchsia.bluetooth.Appearance can not store this value.
adv_data.SetAppearance(0xFFFFu);
bt::DynamicByteBuffer write_buf(adv_data.CalculateBlockSize(/*include_flags=*/true));
ASSERT_TRUE(adv_data.WriteBlock(&write_buf, bt::AdvFlag::kLEGeneralDiscoverableMode));
peer->MutLe().SetAdvertisingData(/*rssi=*/0, write_buf);
ResetHostServer();
bool replied = false;
host_client()->WatchPeers([&](auto updated, [[maybe_unused]] auto removed) {
// Client should still receive updates to this peer.
replied = true;
const fbt::PeerId id = {peer->identifier().value()};
ASSERT_THAT(updated, Contains(Property(&fsys::Peer::id, id)));
EXPECT_FALSE(updated.front().has_appearance());
});
RunLoopUntilIdle();
EXPECT_TRUE(replied);
}
TEST_F(FIDL_HostServerTest, WatchPeersStateMachine) {
std::optional<std::vector<fsys::Peer>> updated;
std::optional<std::vector<fbt::PeerId>> removed;
// Initial watch call hangs as the cache is empty.
host_server()->WatchPeers([&](auto updated_arg, auto removed_arg) {
updated = std::move(updated_arg);
removed = std::move(removed_arg);
});
ASSERT_FALSE(updated.has_value());
ASSERT_FALSE(removed.has_value());
// Adding a new peer should resolve the hanging get.
bt::gap::Peer* peer = adapter()->peer_cache()->NewPeer(kLeTestAddr, /*connectable=*/true);
ASSERT_TRUE(updated.has_value());
ASSERT_TRUE(removed.has_value());
EXPECT_EQ(1u, updated->size());
EXPECT_TRUE(fidl::Equals(fidl_helpers::PeerToFidl(*peer), (*updated)[0]));
EXPECT_TRUE(removed->empty());
updated.reset();
removed.reset();
// The next call should hang.
host_server()->WatchPeers([&](auto updated_arg, auto removed_arg) {
updated = std::move(updated_arg);
removed = std::move(removed_arg);
});
ASSERT_FALSE(updated.has_value());
ASSERT_FALSE(removed.has_value());
// Removing the peer should resolve the hanging get.
auto peer_id = peer->identifier();
__UNUSED auto result = adapter()->peer_cache()->RemoveDisconnectedPeer(peer_id);
ASSERT_TRUE(updated.has_value());
ASSERT_TRUE(removed.has_value());
EXPECT_TRUE(updated->empty());
EXPECT_EQ(1u, removed->size());
EXPECT_TRUE(fidl::Equals(fbt::PeerId{peer_id.value()}, (*removed)[0]));
}
TEST_F(FIDL_HostServerTest, WatchPeersUpdatedThenRemoved) {
// Add then remove a peer. The watcher should only report the removal.
bt::PeerId id;
{
bt::gap::Peer* peer = adapter()->peer_cache()->NewPeer(kLeTestAddr, /*connectable=*/true);
id = peer->identifier();
// |peer| becomes a dangling pointer after the call to RemoveDisconnectedPeer. We scoped the
// binding of |peer| so that it doesn't exist beyond this point.
__UNUSED auto result = adapter()->peer_cache()->RemoveDisconnectedPeer(id);
}
bool replied = false;
host_server()->WatchPeers([&replied, id](auto updated, auto removed) {
EXPECT_TRUE(updated.empty());
EXPECT_EQ(1u, removed.size());
EXPECT_TRUE(fidl::Equals(fbt::PeerId{id.value()}, removed[0]));
replied = true;
});
EXPECT_TRUE(replied);
}
TEST_F(FIDL_HostServerTest, SetLeSecurityMode) {
// Set the HostServer to SecureConnectionsOnly mode first
host_client()->SetLeSecurityMode(fsys::LeSecurityMode::SECURE_CONNECTIONS_ONLY);
RunLoopUntilIdle();
ASSERT_EQ(fidl_helpers::LeSecurityModeFromFidl(fsys::LeSecurityMode::SECURE_CONNECTIONS_ONLY),
adapter()->le()->security_mode());
// Set the HostServer back to Mode 1 and verify that the change takes place
host_client()->SetLeSecurityMode(fsys::LeSecurityMode::MODE_1);
RunLoopUntilIdle();
ASSERT_EQ(fidl_helpers::LeSecurityModeFromFidl(fsys::LeSecurityMode::MODE_1),
adapter()->le()->security_mode());
}
TEST_F(FIDL_HostServerTest, ConnectLowEnergy) {
bt::gap::Peer* peer = AddFakePeer(kLeTestAddr);
EXPECT_EQ(bt::gap::TechnologyType::kLowEnergy, peer->technology());
auto result = ConnectFakePeer(peer->identifier());
ASSERT_TRUE(result);
ASSERT_FALSE(result->is_error());
EXPECT_FALSE(peer->bredr());
ASSERT_TRUE(peer->le());
EXPECT_TRUE(peer->le()->connected());
// bt-host should only attempt to connect the LE transport.
EXPECT_EQ(1, test_device()->le_create_connection_command_count());
EXPECT_EQ(0, test_device()->acl_create_connection_command_count());
}
TEST_F(FIDL_HostServerTest, ConnectBredr) {
bt::gap::Peer* peer = AddFakePeer(kBredrTestAddr);
EXPECT_EQ(bt::gap::TechnologyType::kClassic, peer->technology());
auto result = ConnectFakePeer(peer->identifier());
ASSERT_TRUE(result);
ASSERT_FALSE(result->is_error());
EXPECT_FALSE(peer->le());
ASSERT_TRUE(peer->bredr());
EXPECT_TRUE(peer->bredr()->connected());
// bt-host should only attempt to connect the BR/EDR transport.
EXPECT_EQ(0, test_device()->le_create_connection_command_count());
EXPECT_EQ(1, test_device()->acl_create_connection_command_count());
}
TEST_F(FIDL_HostServerTest, ConnectDualMode) {
// Initialize the peer with data for both transport types.
bt::gap::Peer* peer = AddFakePeer(kBredrTestAddr);
peer->MutLe();
ASSERT_TRUE(peer->le());
peer->MutBrEdr();
ASSERT_TRUE(peer->bredr());
EXPECT_EQ(bt::gap::TechnologyType::kDualMode, peer->technology());
auto result = ConnectFakePeer(peer->identifier());
ASSERT_TRUE(result);
ASSERT_FALSE(result->is_error());
// bt-host should only attempt to connect the BR/EDR transport.
EXPECT_TRUE(peer->bredr()->connected());
EXPECT_FALSE(peer->le()->connected());
EXPECT_EQ(0, test_device()->le_create_connection_command_count());
EXPECT_EQ(1, test_device()->acl_create_connection_command_count());
}
TEST_F(FIDL_HostServerTest, RestoreBondsErrorDataMissing) {
fsys::BondingData bond;
// Empty bond.
TestRestoreBonds(MakeClonedVector(bond), MakeClonedVector(bond));
// ID missing.
bond = MakeTestBond(kTestId, kTestFidlAddrPublic);
bond.clear_identifier();
TestRestoreBonds(MakeClonedVector(bond), MakeClonedVector(bond));
// Address missing.
bond = MakeTestBond(kTestId, kTestFidlAddrPublic);
bond.clear_address();
TestRestoreBonds(MakeClonedVector(bond), MakeClonedVector(bond));
// Transport data missing.
bond = MakeTestBond(kTestId, kTestFidlAddrPublic);
bond.clear_le_bond();
bond.clear_bredr_bond();
TestRestoreBonds(MakeClonedVector(bond), MakeClonedVector(bond));
// Transport data missing keys.
bond = MakeTestBond(kTestId, kTestFidlAddrPublic);
TestRestoreBonds(MakeClonedVector(bond), MakeClonedVector(bond));
}
TEST_F(FIDL_HostServerTest, RestoreBondsInvalidAddress) {
// LE Random address on dual-mode or BR/EDR-only bond should not be supported.
fsys::BondingData bond = MakeTestBond(kTestId, kTestFidlAddrRandom);
bond.set_bredr_bond(fsys::BredrBondData());
TestRestoreBonds(MakeClonedVector(bond), MakeClonedVector(bond));
// BR/EDR only
bond.clear_le_bond();
TestRestoreBonds(MakeClonedVector(bond), MakeClonedVector(bond));
}
TEST_F(FIDL_HostServerTest, RestoreBondsLeOnlySuccess) {
fsys::BondingData bond = MakeTestBond(kTestId, kTestFidlAddrRandom);
auto ltk =
fsys::Ltk{.key =
fsys::PeerKey{
.security =
fsys::SecurityProperties{
.authenticated = true,
.secure_connections = true,
.encryption_key_size = 16,
},
.data =
fsys::Key{
.value = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
},
},
.ediv = 0,
.rand = 0};
fsys::LeBondData le;
le.set_peer_ltk(ltk);
le.set_local_ltk(ltk);
bond.set_le_bond(std::move(le));
// This should succeed.
TestRestoreBonds(MakeClonedVector(bond), {} /* no errors expected */);
auto* peer = adapter()->peer_cache()->FindById(kTestId);
ASSERT_TRUE(peer);
EXPECT_TRUE(peer->le());
EXPECT_FALSE(peer->bredr());
EXPECT_EQ(bt::DeviceAddress::Type::kLERandom, peer->address().type());
}
TEST_F(FIDL_HostServerTest, RestoreBondsBredrOnlySuccess) {
fsys::BondingData bond = MakeTestBond(kTestId, kTestFidlAddrPublic);
bond.clear_le_bond();
fsys::BredrBondData bredr;
bredr.set_link_key(fsys::PeerKey{
.security =
fsys::SecurityProperties{
.authenticated = true,
.secure_connections = true,
.encryption_key_size = 16,
},
.data =
fsys::Key{
.value = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
},
});
constexpr bt::UUID kServiceId = bt::sdp::profile::kAudioSink;
bredr.set_services({fidl_helpers::UuidToFidl(kServiceId)});
bond.set_bredr_bond(std::move(bredr));
// This should succeed.
TestRestoreBonds(MakeClonedVector(bond), {} /* no errors expected */);
auto* peer = adapter()->peer_cache()->FindById(kTestId);
ASSERT_TRUE(peer);
ASSERT_TRUE(peer->bredr());
EXPECT_THAT(peer->bredr()->services(), ::testing::ElementsAre(kServiceId));
EXPECT_FALSE(peer->le());
EXPECT_EQ(bt::DeviceAddress::Type::kBREDR, peer->address().type());
}
TEST_F(FIDL_HostServerTest, RestoreBondsDualModeSuccess) {
fsys::BondingData bond = MakeTestBond(kTestId, kTestFidlAddrPublic);
auto key = fsys::PeerKey{
.security =
fsys::SecurityProperties{
.authenticated = true,
.secure_connections = true,
.encryption_key_size = 16,
},
.data =
fsys::Key{
.value = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
},
};
auto ltk = fsys::Ltk{.key = key, .ediv = 0, .rand = 0};
fsys::LeBondData le;
le.set_peer_ltk(ltk);
le.set_local_ltk(ltk);
bond.set_le_bond(std::move(le));
fsys::BredrBondData bredr;
bredr.set_link_key(key);
constexpr bt::UUID kServiceId = bt::sdp::profile::kAudioSink;
bredr.set_services({fidl_helpers::UuidToFidl(kServiceId)});
bond.set_bredr_bond(std::move(bredr));
// This should succeed.
TestRestoreBonds(MakeClonedVector(bond), {} /* no errors expected */);
auto* peer = adapter()->peer_cache()->FindById(kTestId);
ASSERT_TRUE(peer);
EXPECT_TRUE(peer->le());
ASSERT_TRUE(peer->bredr());
EXPECT_THAT(peer->bredr()->services(), ::testing::ElementsAre(kServiceId));
EXPECT_EQ(bt::DeviceAddress::Type::kBREDR, peer->address().type());
}
TEST_F(FIDL_HostServerTest, SetHostData) {
EXPECT_FALSE(adapter()->le()->irk());
fsys::Key irk{{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}};
fsys::HostData data;
data.set_irk(irk);
host_server()->SetLocalData(std::move(data));
ASSERT_TRUE(adapter()->le()->irk());
EXPECT_EQ(irk.value, adapter()->le()->irk().value());
}
TEST_F(FIDL_HostServerTest, OnNewBondingData) {
const std::string kTestName = "florp";
const bt::UInt128 kTestKeyValue{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
const bt::sm::SecurityProperties kTestSecurity(bt::sm::SecurityLevel::kSecureAuthenticated, 16,
true);
const bt::sm::LTK kTestLtk(kTestSecurity, bt::hci::LinkKey(kTestKeyValue, 0, 0));
const fsys::PeerKey kTestKeyFidl{
.security =
fsys::SecurityProperties{
.authenticated = true,
.secure_connections = true,
.encryption_key_size = 16,
},
.data = fsys::Key{.value = kTestKeyValue},
};
const fsys::Ltk kTestLtkFidl{.key = kTestKeyFidl, .ediv = 0, .rand = 0};
std::optional<fsys::BondingData> data;
host_client_ptr().events().OnNewBondingData = [&](auto _data) { data = std::move(_data); };
auto* peer = adapter()->peer_cache()->NewPeer(kBredrTestAddr, /*connectable=*/true);
peer->SetName(kTestName);
adapter()->peer_cache()->StoreLowEnergyBond(peer->identifier(),
bt::sm::PairingData{.peer_ltk = {kTestLtk}});
RunLoopUntilIdle();
ASSERT_TRUE(data);
ASSERT_TRUE(data->has_identifier());
ASSERT_TRUE(data->has_local_address());
ASSERT_TRUE(data->has_address());
ASSERT_TRUE(data->has_name());
EXPECT_TRUE(fidl::Equals((fbt::Address{fbt::AddressType::PUBLIC, std::array<uint8_t, 6>{0}}),
data->local_address()));
EXPECT_TRUE(fidl::Equals(kTestFidlAddrPublic, data->address()));
EXPECT_EQ(kTestName, data->name());
ASSERT_TRUE(data->has_le_bond());
EXPECT_FALSE(data->has_bredr_bond());
ASSERT_TRUE(data->le_bond().has_peer_ltk());
EXPECT_FALSE(data->le_bond().has_local_ltk());
EXPECT_FALSE(data->le_bond().has_irk());
EXPECT_FALSE(data->le_bond().has_csrk());
EXPECT_TRUE(fidl::Equals(kTestLtkFidl, data->le_bond().peer_ltk()));
// Add BR/EDR data.
data.reset();
adapter()->peer_cache()->StoreBrEdrBond(kBredrTestAddr, kTestLtk);
RunLoopUntilIdle();
ASSERT_TRUE(data);
ASSERT_TRUE(data->has_identifier());
ASSERT_TRUE(data->has_local_address());
ASSERT_TRUE(data->has_address());
ASSERT_TRUE(data->has_name());
EXPECT_TRUE(fidl::Equals((fbt::Address{fbt::AddressType::PUBLIC, std::array<uint8_t, 6>{0}}),
data->local_address()));
EXPECT_TRUE(fidl::Equals(kTestFidlAddrPublic, data->address()));
EXPECT_EQ(kTestName, data->name());
ASSERT_TRUE(data->has_le_bond());
ASSERT_TRUE(data->le_bond().has_peer_ltk());
EXPECT_FALSE(data->le_bond().has_local_ltk());
EXPECT_FALSE(data->le_bond().has_irk());
EXPECT_FALSE(data->le_bond().has_csrk());
EXPECT_TRUE(fidl::Equals(kTestLtkFidl, data->le_bond().peer_ltk()));
ASSERT_TRUE(data->has_bredr_bond());
ASSERT_TRUE(data->bredr_bond().has_link_key());
EXPECT_TRUE(fidl::Equals(kTestKeyFidl, data->bredr_bond().link_key()));
}
TEST_F(FIDL_HostServerTest, EnableBackgroundScan) {
host_server()->EnableBackgroundScan(true);
EXPECT_FALSE(test_device()->le_scan_state().enabled);
RunLoopUntilIdle();
EXPECT_TRUE(test_device()->le_scan_state().enabled);
EXPECT_EQ(bt::hci::LEScanType::kPassive, test_device()->le_scan_state().scan_type);
host_server()->EnableBackgroundScan(false);
RunLoopUntilIdle();
EXPECT_FALSE(test_device()->le_scan_state().enabled);
}
TEST_F(FIDL_HostServerTest, EnableBackgroundScanTwiceAtSameTime) {
host_server()->EnableBackgroundScan(true);
host_server()->EnableBackgroundScan(true);
EXPECT_FALSE(test_device()->le_scan_state().enabled);
RunLoopUntilIdle();
EXPECT_TRUE(test_device()->le_scan_state().enabled);
EXPECT_EQ(bt::hci::LEScanType::kPassive, test_device()->le_scan_state().scan_type);
host_server()->EnableBackgroundScan(false);
RunLoopUntilIdle();
EXPECT_FALSE(test_device()->le_scan_state().enabled);
}
TEST_F(FIDL_HostServerTest, EnableBackgroundScanTwiceSequentially) {
host_server()->EnableBackgroundScan(true);
EXPECT_FALSE(test_device()->le_scan_state().enabled);
RunLoopUntilIdle();
EXPECT_TRUE(test_device()->le_scan_state().enabled);
EXPECT_EQ(bt::hci::LEScanType::kPassive, test_device()->le_scan_state().scan_type);
host_server()->EnableBackgroundScan(true);
RunLoopUntilIdle();
EXPECT_TRUE(test_device()->le_scan_state().enabled);
EXPECT_EQ(bt::hci::LEScanType::kPassive, test_device()->le_scan_state().scan_type);
host_server()->EnableBackgroundScan(false);
RunLoopUntilIdle();
EXPECT_FALSE(test_device()->le_scan_state().enabled);
}
TEST_F(FIDL_HostServerTest, CancelEnableBackgroundScan) {
host_server()->EnableBackgroundScan(true);
host_server()->EnableBackgroundScan(false);
RunLoopUntilIdle();
EXPECT_FALSE(test_device()->le_scan_state().enabled);
host_server()->EnableBackgroundScan(true);
RunLoopUntilIdle();
EXPECT_TRUE(test_device()->le_scan_state().enabled);
}
TEST_F(FIDL_HostServerTest, DisableBackgroundScan) {
host_server()->EnableBackgroundScan(false);
RunLoopUntilIdle();
EXPECT_FALSE(test_device()->le_scan_state().enabled);
}
TEST_F(FIDL_HostServerTest, EnableBackgroundScanFailsToStart) {
test_device()->SetDefaultCommandStatus(bt::hci::kLESetScanEnable,
bt::hci::StatusCode::kControllerBusy);
host_server()->EnableBackgroundScan(true);
EXPECT_FALSE(test_device()->le_scan_state().enabled);
RunLoopUntilIdle();
EXPECT_FALSE(test_device()->le_scan_state().enabled);
test_device()->ClearDefaultCommandStatus(bt::hci::kLESetScanEnable);
host_server()->EnableBackgroundScan(true);
RunLoopUntilIdle();
EXPECT_TRUE(test_device()->le_scan_state().enabled);
}
class FIDL_HostServerTest_FakeAdapter : public bt::gap::testing::FakeAdapterTestFixture {
public:
FIDL_HostServerTest_FakeAdapter() = default;
~FIDL_HostServerTest_FakeAdapter() override = default;
void SetUp() override {
FakeAdapterTestFixture::SetUp();
gatt_ = std::make_unique<bt::gatt::testing::FakeLayer>();
fidl::InterfaceHandle<fuchsia::bluetooth::host::Host> host_handle;
host_server_ = std::make_unique<HostServer>(host_handle.NewRequest().TakeChannel(),
adapter()->AsWeakPtr(), gatt_->AsWeakPtr());
host_.Bind(std::move(host_handle));
}
void TearDown() override {
RunLoopUntilIdle();
host_ = nullptr;
host_server_ = nullptr;
gatt_ = nullptr;
FakeAdapterTestFixture::TearDown();
}
protected:
HostServer* host_server() const { return host_server_.get(); }
fuchsia::bluetooth::host::Host* host_client() const { return host_.get(); }
fuchsia::bluetooth::host::HostPtr& host_client_ptr() { return host_; }
private:
std::unique_ptr<HostServer> host_server_;
fuchsia::bluetooth::host::HostPtr host_;
std::unique_ptr<bt::gatt::GATT> gatt_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(FIDL_HostServerTest_FakeAdapter);
};
TEST_F(FIDL_HostServerTest_FakeAdapter, SetLocalNameNotifiesWatchState) {
std::vector<fsys::HostInfo> info;
// Consume initial state value.
host_client()->WatchState([&](auto value) { info.push_back(std::move(value)); });
RunLoopUntilIdle();
EXPECT_EQ(info.size(), 1u);
// Second watch state will hang until state is updated.
host_client()->WatchState([&](auto value) { info.push_back(std::move(value)); });
RunLoopUntilIdle();
EXPECT_EQ(info.size(), 1u);
int cb_count = 0;
host_client()->SetLocalName("test", [&](auto result) {
EXPECT_TRUE(result.is_response());
cb_count++;
});
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
EXPECT_EQ(adapter()->local_name(), "test");
ASSERT_EQ(info.size(), 2u);
ASSERT_TRUE(info.back().has_local_name());
EXPECT_EQ(info.back().local_name(), "test");
}
} // namespace
} // namespace bthost