blob: 0d14084633b533c565f713a312e87ec277340172 [file] [log] [blame]
// Copyright 2020 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/gap/peer.h"
#include <lib/inspect/testing/cpp/inspect.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "lib/gtest/test_loop_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/common/advertising_data.h"
#include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/common/manufacturer_names.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/util.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/inspect_util.h"
namespace bt::gap {
namespace {
using namespace inspect::testing;
using bt::testing::GetInspectValue;
using bt::testing::ReadInspect;
constexpr uint16_t kManufacturer = 0x0001;
constexpr uint16_t kSubversion = 0x0002;
const StaticByteBuffer kAdvData(0x05, // Length
0x09, // AD type: Complete Local Name
'T', 'e', 's', 't');
const StaticByteBuffer kInvalidAdvData{
// 32 bit service UUIDs are supposed to be 4 bytes, but the value in this TLV field is only 3
// bytes long, hence the AdvertisingData is not valid.
0x04, static_cast<uint8_t>(DataType::kComplete32BitServiceUuids), 0x01, 0x02, 0x03,
};
const bt::sm::LTK kLTK;
const DeviceAddress kAddrLePublic(DeviceAddress::Type::kLEPublic, {1, 2, 3, 4, 5, 6});
const DeviceAddress kAddrLeRandom(DeviceAddress::Type::kLERandom, {1, 2, 3, 4, 5, 6});
const DeviceAddress kAddrBrEdr(DeviceAddress::Type::kBREDR, {0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA});
// LE Public Device Address that has the same value as a BR/EDR BD_ADDR, e.g. on
// a dual-mode device.
const DeviceAddress kAddrLeAlias(DeviceAddress::Type::kLEPublic,
{0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA});
const bt::sm::LTK kSecureBrEdrKey(sm::SecurityProperties(/*encrypted=*/true, /*authenticated=*/true,
/*secure_connections=*/true,
sm::kMaxEncryptionKeySize),
hci_spec::LinkKey(UInt128{4}, 5, 6));
class PeerTest : public ::gtest::TestLoopFixture {
public:
PeerTest() = default;
void SetUp() override {
TestLoopFixture::SetUp();
// Set up a default peer.
SetUpPeer(/*address=*/kAddrLePublic, /*connectable=*/true);
}
void TearDown() override {
peer_.reset();
TestLoopFixture::TearDown();
}
protected:
// Can be used to override or reset the default peer. Resets metrics to prevent interference
// between peers (e.g. by metrics updated in construction).
void SetUpPeer(const DeviceAddress& address, bool connectable) {
address_ = address;
peer_ = std::make_unique<Peer>(fit::bind_member<&PeerTest::NotifyListenersCallback>(this),
fit::bind_member<&PeerTest::UpdateExpiryCallback>(this),
fit::bind_member<&PeerTest::DualModeCallback>(this),
fit::bind_member<&PeerTest::StoreLowEnergyBondCallback>(this),
PeerId(1), address_, connectable, &metrics_);
peer_->AttachInspect(peer_inspector_.GetRoot());
// Reset metrics as they should only apply to the new peer under test.
metrics_.AttachInspect(metrics_inspector_.GetRoot());
}
Peer& peer() { return *peer_; }
inspect::Hierarchy ReadPeerInspect() { return ReadInspect(peer_inspector_); }
std::string InspectLowEnergyConnectionState() {
std::optional<std::string> val = GetInspectValue<inspect::StringPropertyValue>(
peer_inspector_, {"peer", "le_data", Peer::LowEnergyData::kInspectConnectionStateName});
ZX_ASSERT(val);
return *val;
}
int64_t InspectAdvertisingDataParseFailureCount() {
std::optional<int64_t> val = GetInspectValue<inspect::IntPropertyValue>(
peer_inspector_,
{"peer", "le_data", Peer::LowEnergyData::kInspectAdvertisingDataParseFailureCountName});
ZX_ASSERT(val);
return *val;
}
std::string InspectLastAdvertisingDataParseFailure() {
std::optional<std::string> val = GetInspectValue<inspect::StringPropertyValue>(
peer_inspector_,
{"peer", "le_data", Peer::LowEnergyData::kInspectLastAdvertisingDataParseFailureName});
ZX_ASSERT(val);
return *val;
}
uint64_t MetricsLowEnergyConnections() {
std::optional<uint64_t> val = GetInspectValue<inspect::UintPropertyValue>(
metrics_inspector_, {"metrics", "le", "connection_events"});
ZX_ASSERT(val);
return *val;
}
uint64_t MetricsLowEnergyDisconnections() {
std::optional<uint64_t> val = GetInspectValue<inspect::UintPropertyValue>(
metrics_inspector_, {"metrics", "le", "disconnection_events"});
ZX_ASSERT(val);
return *val;
}
std::string InspectBrEdrConnectionState() {
std::optional<std::string> val = GetInspectValue<inspect::StringPropertyValue>(
peer_inspector_, {"peer", "bredr_data", Peer::BrEdrData::kInspectConnectionStateName});
ZX_ASSERT(val);
return *val;
}
uint64_t MetricsBrEdrConnections() {
std::optional<uint64_t> val = GetInspectValue<inspect::UintPropertyValue>(
metrics_inspector_, {"metrics", "bredr", "connection_events"});
ZX_ASSERT(val);
return *val;
}
uint64_t MetricsBrEdrDisconnections() {
std::optional<uint64_t> val = GetInspectValue<inspect::UintPropertyValue>(
metrics_inspector_, {"metrics", "bredr", "disconnection_events"});
ZX_ASSERT(val);
return *val;
}
void set_notify_listeners_cb(Peer::NotifyListenersCallback cb) {
notify_listeners_cb_ = std::move(cb);
}
void set_update_expiry_cb(Peer::PeerCallback cb) { update_expiry_cb_ = std::move(cb); }
void set_dual_mode_cb(Peer::PeerCallback cb) { dual_mode_cb_ = std::move(cb); }
void set_store_le_bond_cb(Peer::StoreLowEnergyBondCallback cb) {
store_le_bond_cb_ = std::move(cb);
}
private:
void NotifyListenersCallback(const Peer& peer, Peer::NotifyListenersChange change) {
if (notify_listeners_cb_) {
notify_listeners_cb_(peer, change);
}
}
void UpdateExpiryCallback(const Peer& peer) {
if (update_expiry_cb_) {
update_expiry_cb_(peer);
}
}
void DualModeCallback(const Peer& peer) {
if (dual_mode_cb_) {
dual_mode_cb_(peer);
}
}
bool StoreLowEnergyBondCallback(const sm::PairingData& data) {
if (store_le_bond_cb_) {
return store_le_bond_cb_(data);
}
return false;
}
std::unique_ptr<Peer> peer_;
DeviceAddress address_;
Peer::NotifyListenersCallback notify_listeners_cb_;
Peer::PeerCallback update_expiry_cb_;
Peer::PeerCallback dual_mode_cb_;
Peer::StoreLowEnergyBondCallback store_le_bond_cb_;
inspect::Inspector metrics_inspector_;
PeerMetrics metrics_;
inspect::Inspector peer_inspector_;
};
class PeerDeathTest : public PeerTest {};
TEST_F(PeerTest, InspectHierarchy) {
peer().set_version(hci_spec::HCIVersion::k5_0, kManufacturer, kSubversion);
peer().SetName("Sapphire💖");
peer().MutLe();
ASSERT_TRUE(peer().le().has_value());
peer().MutLe().SetFeatures(hci_spec::LESupportedFeatures{0x0000000000000001});
peer().MutBrEdr().AddService(UUID(uint16_t{0x110b}));
// clang-format off
auto bredr_data_matcher = AllOf(
NodeMatches(AllOf(
NameMatches(Peer::BrEdrData::kInspectNodeName),
PropertyList(UnorderedElementsAre(
StringIs(Peer::BrEdrData::kInspectConnectionStateName,
Peer::ConnectionStateToString(peer().bredr()->connection_state())),
BoolIs(Peer::BrEdrData::kInspectLinkKeyName, peer().bredr()->bonded()),
StringIs(Peer::BrEdrData::kInspectServicesName, "{ 0000110b-0000-1000-8000-00805f9b34fb }")
)))));
auto le_data_matcher = AllOf(
NodeMatches(AllOf(
NameMatches(Peer::LowEnergyData::kInspectNodeName),
PropertyList(UnorderedElementsAre(
StringIs(Peer::LowEnergyData::kInspectConnectionStateName,
Peer::ConnectionStateToString(peer().le()->connection_state())),
IntIs(Peer::LowEnergyData::kInspectAdvertisingDataParseFailureCountName, 0),
StringIs(Peer::LowEnergyData::kInspectLastAdvertisingDataParseFailureName, ""),
BoolIs(Peer::LowEnergyData::kInspectBondDataName, peer().le()->bonded()),
StringIs(Peer::LowEnergyData::kInspectFeaturesName, "0x0000000000000001")
)))));
auto peer_matcher = AllOf(
NodeMatches(
PropertyList(UnorderedElementsAre(
StringIs(Peer::kInspectPeerIdName, peer().identifier().ToString()),
StringIs(Peer::kInspectPeerNameName, peer().name().value()),
StringIs(Peer::kInspectTechnologyName, TechnologyTypeToString(peer().technology())),
StringIs(Peer::kInspectAddressName, peer().address().ToString()),
BoolIs(Peer::kInspectConnectableName, peer().connectable()),
BoolIs(Peer::kInspectTemporaryName, peer().temporary()),
StringIs(Peer::kInspectFeaturesName, peer().features().ToString()),
StringIs(Peer::kInspectVersionName, hci_spec::HCIVersionToString(peer().version().value())),
StringIs(Peer::kInspectManufacturerName, GetManufacturerName(kManufacturer))
))),
ChildrenMatch(UnorderedElementsAre(bredr_data_matcher, le_data_matcher)));
// clang-format on
inspect::Hierarchy hierarchy = ReadPeerInspect();
EXPECT_THAT(hierarchy, AllOf(ChildrenMatch(UnorderedElementsAre(peer_matcher))));
}
TEST_F(PeerTest, BrEdrDataAddServiceNotifiesListeners) {
// Initialize BrEdrData.
peer().MutBrEdr();
ASSERT_TRUE(peer().bredr()->services().empty());
bool listener_notified = false;
set_notify_listeners_cb([&](auto&, Peer::NotifyListenersChange change) {
listener_notified = true;
// Non-bonded peer should not update bond
EXPECT_EQ(Peer::NotifyListenersChange::kBondNotUpdated, change);
});
constexpr UUID kServiceUuid;
peer().MutBrEdr().AddService(kServiceUuid);
EXPECT_TRUE(listener_notified);
EXPECT_EQ(1u, peer().bredr()->services().count(kServiceUuid));
// De-duplicate subsequent additions of the same service.
listener_notified = false;
peer().MutBrEdr().AddService(kServiceUuid);
EXPECT_FALSE(listener_notified);
}
TEST_F(PeerTest, BrEdrDataAddServiceOnBondedPeerNotifiesListenersToUpdateBond) {
// Initialize BrEdrData.
peer().MutBrEdr().SetBondData({});
ASSERT_TRUE(peer().bredr()->services().empty());
bool listener_notified = false;
set_notify_listeners_cb([&](auto&, Peer::NotifyListenersChange change) {
listener_notified = true;
// Bonded peer should update bond
EXPECT_EQ(Peer::NotifyListenersChange::kBondUpdated, change);
});
peer().MutBrEdr().AddService(UUID());
EXPECT_TRUE(listener_notified);
}
TEST_F(PeerTest, LowEnergyDataSetAdvDataWithInvalidUtf8NameDoesNotUpdatePeerName) {
peer().MutLe(); // Initialize LowEnergyData.
ASSERT_FALSE(peer().name().has_value());
bool listener_notified = false;
set_notify_listeners_cb([&](auto&, Peer::NotifyListenersChange) { listener_notified = true; });
const StaticByteBuffer kAdvData(0x05, // Length
0x09, // AD type: Complete Local Name
'T', 'e', 's',
0xFF // 0xFF should not appear in a valid UTF-8 string
);
peer().MutLe().SetAdvertisingData(/*rssi=*/0, kAdvData, zx::time());
EXPECT_TRUE(listener_notified); // Fresh AD still results in an update
EXPECT_FALSE(peer().name().has_value());
}
TEST_F(PeerTest, BrEdrDataSetEirDataWithInvalidUtf8NameDoesNotUpdatePeerName) {
peer().MutBrEdr(); // Initialize BrEdrData.
ASSERT_FALSE(peer().name().has_value());
bool listener_notified = false;
set_notify_listeners_cb([&](auto&, Peer::NotifyListenersChange) { listener_notified = true; });
const StaticByteBuffer kEirData(0x05, // Length
0x09, // AD type: Complete Local Name
'T', 'e', 's',
0xFF // 0xFF should not appear in a valid UTF-8 string
);
hci_spec::ExtendedInquiryResultEventParams eirep;
eirep.num_responses = 1;
eirep.bd_addr = peer().address().value();
MutableBufferView(eirep.extended_inquiry_response, sizeof(eirep.extended_inquiry_response))
.Write(kEirData);
peer().MutBrEdr().SetInquiryData(eirep);
EXPECT_TRUE(listener_notified); // Fresh EIR data still results in an update
EXPECT_FALSE(peer().name().has_value());
}
TEST_F(PeerTest, SetNameWithInvalidUtf8NameDoesNotUpdatePeerName) {
ASSERT_FALSE(peer().name().has_value());
bool listener_notified = false;
set_notify_listeners_cb([&](auto&, Peer::NotifyListenersChange) { listener_notified = true; });
const std::string kName = "Tes\xFF\x01"; // 0xFF should not appear in a valid UTF-8 string
peer().SetName(kName);
EXPECT_FALSE(listener_notified);
EXPECT_FALSE(peer().name().has_value());
}
TEST_F(PeerTest, LowEnergyAdvertisingDataTimestamp) {
EXPECT_FALSE(peer().MutLe().parsed_advertising_data_timestamp());
peer().MutLe().SetAdvertisingData(/*rssi=*/0, kAdvData, zx::time(1));
ASSERT_TRUE(peer().MutLe().parsed_advertising_data_timestamp());
EXPECT_EQ(peer().MutLe().parsed_advertising_data_timestamp().value(), zx::time(1));
peer().MutLe().SetAdvertisingData(/*rssi=*/0, kAdvData, zx::time(2));
ASSERT_TRUE(peer().MutLe().parsed_advertising_data_timestamp());
EXPECT_EQ(peer().MutLe().parsed_advertising_data_timestamp().value(), zx::time(2));
// SetAdvertisingData with data that fails to parse should not update the advertising data
// timestamp.
peer().MutLe().SetAdvertisingData(/*rssi=*/0, kInvalidAdvData, zx::time(3));
ASSERT_TRUE(peer().MutLe().parsed_advertising_data_timestamp());
EXPECT_EQ(peer().MutLe().parsed_advertising_data_timestamp().value(), zx::time(2));
}
TEST_F(PeerTest, SettingLowEnergyAdvertisingDataUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(), zx::time(0));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(), zx::time(2));
notify_count++;
});
RunLoopFor(zx::duration(2));
peer().MutLe().SetAdvertisingData(/*rssi=*/0, kAdvData, zx::time(1));
EXPECT_EQ(peer().last_updated(), zx::time(2));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, RegisteringLowEnergyInitializingConnectionUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(), zx::time(0));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(), zx::time(2));
notify_count++;
});
RunLoopFor(zx::duration(2));
Peer::InitializingConnectionToken token = peer().MutLe().RegisterInitializingConnection();
EXPECT_EQ(peer().last_updated(), zx::time(2));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, SettingLowEnergyBondDataUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(), zx::time(0));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(), zx::time(2));
notify_count++;
});
RunLoopFor(zx::duration(2));
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
peer().MutLe().SetBondData(data);
EXPECT_EQ(peer().last_updated(), zx::time(2));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, RegisteringBrEdrInitializingConnectionUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(), zx::time(0));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(), zx::time(2));
notify_count++;
});
RunLoopFor(zx::duration(2));
Peer::InitializingConnectionToken token = peer().MutBrEdr().RegisterInitializingConnection();
EXPECT_EQ(peer().last_updated(), zx::time(2));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, SettingInquiryDataUpdatesLastUpdated) {
SetUpPeer(/*address=*/kAddrLeAlias, /*connectable=*/true);
EXPECT_EQ(peer().last_updated(), zx::time(0));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(), zx::time(2));
notify_count++;
});
RunLoopFor(zx::duration(2));
hci_spec::InquiryResult ir;
ir.bd_addr = kAddrLeAlias.value();
peer().MutBrEdr().SetInquiryData(ir);
EXPECT_EQ(peer().last_updated(), zx::time(2));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, SettingBrEdrBondDataUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(), zx::time(0));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(), zx::time(2));
notify_count++;
});
RunLoopFor(zx::duration(2));
peer().MutBrEdr().SetBondData(kSecureBrEdrKey);
EXPECT_EQ(peer().last_updated(), zx::time(2));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, SettingAddingBrEdrServiceUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(), zx::time(0));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(), zx::time(2));
notify_count++;
});
RunLoopFor(zx::duration(2));
peer().MutBrEdr().AddService(UUID(uint16_t{0x110b}));
EXPECT_EQ(peer().last_updated(), zx::time(2));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, SettingNameUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(), zx::time(0));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(), zx::time(2));
notify_count++;
});
RunLoopFor(zx::duration(2));
peer().SetName("name");
EXPECT_EQ(peer().last_updated(), zx::time(2));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, RegisterAndUnregisterTwoLowEnergyConnections) {
SetUpPeer(/*address=*/kAddrLeRandom, /*connectable=*/true);
int update_expiry_count = 0;
set_update_expiry_cb([&](const Peer&) { update_expiry_count++; });
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) { notify_count++; });
std::optional<Peer::ConnectionToken> token_0 = peer().MutLe().RegisterConnection();
// A notification and expiry update are sent when the peer becomes non-temporary, and a second
// notification and update expiry are sent because the connection is registered.
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
EXPECT_EQ(MetricsLowEnergyConnections(), 1u);
std::optional<Peer::ConnectionToken> token_1 = peer().MutLe().RegisterConnection();
// The second connection should not update expiry or notify.
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
// Although the second connection does not change the high-level connection state, we track it in
// metrics to support multiple connections to the same peer.
EXPECT_EQ(MetricsLowEnergyConnections(), 2u);
EXPECT_EQ(MetricsLowEnergyDisconnections(), 0u);
token_0.reset();
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
EXPECT_EQ(MetricsLowEnergyDisconnections(), 1u);
token_1.reset();
EXPECT_EQ(update_expiry_count, 3);
EXPECT_EQ(notify_count, 3);
EXPECT_TRUE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kNotConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
EXPECT_EQ(MetricsLowEnergyDisconnections(), 2u);
}
TEST_F(PeerTest, RegisterAndUnregisterLowEnergyConnectionsWhenIdentityKnown) {
EXPECT_TRUE(peer().identity_known());
std::optional<Peer::ConnectionToken> token = peer().MutLe().RegisterConnection();
EXPECT_FALSE(peer().temporary());
token.reset();
// The peer's identity is known, so it should stay non-temporary upon disconnection.
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kNotConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
}
TEST_F(PeerTest, RegisterAndUnregisterInitializingLowEnergyConnectionsWhenIdentityKnown) {
EXPECT_TRUE(peer().identity_known());
std::optional<Peer::InitializingConnectionToken> token =
peer().MutLe().RegisterInitializingConnection();
EXPECT_FALSE(peer().temporary());
token.reset();
// The peer's identity is known, so it should stay non-temporary upon disconnection.
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kNotConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
}
TEST_F(PeerTest, RegisterAndUnregisterLowEnergyConnectionDuringInitializingConnection) {
SetUpPeer(/*address=*/kAddrLeRandom, /*connectable=*/true);
int update_expiry_count = 0;
set_update_expiry_cb([&](const Peer&) { update_expiry_count++; });
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) { notify_count++; });
std::optional<Peer::InitializingConnectionToken> init_token =
peer().MutLe().RegisterInitializingConnection();
// A notification and expiry update are sent when the peer becomes non-temporary, and a second
// notification and update expiry are sent because the initializing connection is registered.
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kInitializing);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
std::optional<Peer::ConnectionToken> conn_token = peer().MutLe().RegisterConnection();
EXPECT_EQ(update_expiry_count, 3);
EXPECT_EQ(notify_count, 3);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
conn_token.reset();
EXPECT_EQ(update_expiry_count, 4);
EXPECT_EQ(notify_count, 4);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kInitializing);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
init_token.reset();
EXPECT_EQ(update_expiry_count, 5);
EXPECT_EQ(notify_count, 5);
EXPECT_TRUE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kNotConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
}
TEST_F(PeerTest, RegisterAndUnregisterInitializingLowEnergyConnectionDuringConnection) {
SetUpPeer(/*address=*/kAddrLeRandom, /*connectable=*/true);
int update_expiry_count = 0;
set_update_expiry_cb([&](const Peer&) { update_expiry_count++; });
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) { notify_count++; });
std::optional<Peer::ConnectionToken> conn_token = peer().MutLe().RegisterConnection();
// A notification and expiry update are sent when the peer becomes non-temporary, and a second
// notification and update expiry are sent because the initializing connection is registered.
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
std::optional<Peer::InitializingConnectionToken> init_token =
peer().MutLe().RegisterInitializingConnection();
// Initializing connections should not affect the expiry or notify listeners for peers that are
// already connected.
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
init_token.reset();
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
conn_token.reset();
EXPECT_EQ(update_expiry_count, 3);
EXPECT_EQ(notify_count, 3);
EXPECT_TRUE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kNotConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
}
TEST_F(PeerTest, RegisterAndUnregisterTwoLowEnergyInitializingConnections) {
SetUpPeer(/*address=*/kAddrLeRandom, /*connectable=*/true);
int update_expiry_count = 0;
set_update_expiry_cb([&](const Peer&) { update_expiry_count++; });
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) { notify_count++; });
std::optional<Peer::InitializingConnectionToken> token_0 =
peer().MutLe().RegisterInitializingConnection();
// A notification and expiry update are sent when the peer becomes non-temporary, and a second
// notification and update expiry are sent because the initializing connection is registered.
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kInitializing);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
std::optional<Peer::InitializingConnectionToken> token_1 =
peer().MutLe().RegisterInitializingConnection();
// The second initializing connection should not update expiry or notify.
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kInitializing);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
token_0.reset();
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kInitializing);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
token_1.reset();
EXPECT_EQ(update_expiry_count, 3);
EXPECT_EQ(notify_count, 3);
// The peer's identity is not known, so it should become temporary upon disconnection.
EXPECT_TRUE(peer().temporary());
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kNotConnected);
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
}
TEST_F(PeerTest, MovingLowEnergyConnectionTokenWorksAsExpected) {
std::optional<Peer::ConnectionToken> token_0 = peer().MutLe().RegisterConnection();
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kConnected);
std::optional<Peer::ConnectionToken> token_1 = std::move(token_0);
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kConnected);
token_0.reset();
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kConnected);
token_1.reset();
EXPECT_EQ(peer().le()->connection_state(), Peer::ConnectionState::kNotConnected);
}
TEST_F(PeerTest, SetValidAdvertisingData) {
constexpr const char* kLocalName = "Test";
StaticByteBuffer raw_data{
// Length - Type - Value formatted Local name
0x05, static_cast<uint8_t>(DataType::kCompleteLocalName),
kLocalName[0], kLocalName[1],
kLocalName[2], kLocalName[3],
};
peer().MutLe().SetAdvertisingData(/*rssi=*/32, raw_data, zx::time());
// Setting an AdvertisingData with a local name field should update the peer's local name.
ASSERT_TRUE(peer().name().has_value());
EXPECT_EQ(kLocalName, peer().name().value());
EXPECT_EQ(0, InspectAdvertisingDataParseFailureCount());
EXPECT_EQ("", InspectLastAdvertisingDataParseFailure());
}
TEST_F(PeerTest, SetInvalidAdvertisingData) {
peer().MutLe().SetAdvertisingData(/*rssi=*/32, kInvalidAdvData, zx::time());
EXPECT_EQ(1, InspectAdvertisingDataParseFailureCount());
EXPECT_EQ(AdvertisingData::ParseErrorToString(AdvertisingData::ParseError::kUuidsMalformed),
InspectLastAdvertisingDataParseFailure());
}
TEST_F(PeerDeathTest, RegisterTwoBrEdrConnectionsAsserts) {
SetUpPeer(/*address=*/kAddrBrEdr, /*connectable=*/true);
std::optional<Peer::ConnectionToken> token_0 = peer().MutBrEdr().RegisterConnection();
ASSERT_DEATH_IF_SUPPORTED(
{ std::optional<Peer::ConnectionToken> token_1 = peer().MutBrEdr().RegisterConnection(); },
".*already registered.*");
}
TEST_F(PeerTest, RegisterAndUnregisterInitializingBrEdrConnectionLeavesPeerTemporary) {
SetUpPeer(/*address=*/kAddrBrEdr, /*connectable=*/true);
EXPECT_TRUE(peer().identity_known());
std::optional<Peer::InitializingConnectionToken> token =
peer().MutBrEdr().RegisterInitializingConnection();
EXPECT_FALSE(peer().temporary());
token.reset();
EXPECT_TRUE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kNotConnected);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
}
TEST_F(PeerTest, RegisterAndUnregisterBrEdrConnectionWithoutBonding) {
SetUpPeer(/*address=*/kAddrBrEdr, /*connectable=*/true);
int update_expiry_count = 0;
set_update_expiry_cb([&](const Peer&) { update_expiry_count++; });
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) { notify_count++; });
std::optional<Peer::ConnectionToken> conn_token = peer().MutBrEdr().RegisterConnection();
// A notification and expiry update are sent when the peer becomes non-temporary, and a second
// notification and update expiry are sent because the initializing connection is registered.
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kConnected);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
conn_token.reset();
EXPECT_EQ(update_expiry_count, 3);
EXPECT_EQ(notify_count, 3);
// BR/EDR peers should become non-temporary after disconnecting if not bonded.
EXPECT_TRUE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kNotConnected);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
}
TEST_F(PeerTest, RegisterAndUnregisterBrEdrConnectionWithBonding) {
SetUpPeer(/*address=*/kAddrBrEdr, /*connectable=*/true);
int update_expiry_count = 0;
set_update_expiry_cb([&](const Peer&) { update_expiry_count++; });
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) { notify_count++; });
std::optional<Peer::ConnectionToken> conn_token = peer().MutBrEdr().RegisterConnection();
// A notification and expiry update are sent when the peer becomes non-temporary, and a second
// notification and update expiry are sent because the initializing connection is registered.
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kConnected);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
peer().MutBrEdr().SetBondData(kSecureBrEdrKey);
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 3);
conn_token.reset();
EXPECT_EQ(update_expiry_count, 3);
EXPECT_EQ(notify_count, 4);
// Bonded BR/EDR peers should remain non-temporary after disconnecting.
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kNotConnected);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
}
TEST_F(PeerTest, RegisterAndUnregisterBrEdrConnectionDuringInitializingConnection) {
SetUpPeer(/*address=*/kAddrBrEdr, /*connectable=*/true);
int update_expiry_count = 0;
set_update_expiry_cb([&](const Peer&) { update_expiry_count++; });
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) { notify_count++; });
std::optional<Peer::InitializingConnectionToken> init_token =
peer().MutBrEdr().RegisterInitializingConnection();
// Expiry is updated for state change + becoming non-temporary.
EXPECT_EQ(update_expiry_count, 2);
// 1 notification for becoming non-temporary.
EXPECT_EQ(notify_count, 1);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kInitializing);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
// The connection state should not change when registering a connection because the peer is still
// initializing.
std::optional<Peer::ConnectionToken> conn_token = peer().MutBrEdr().RegisterConnection();
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 1);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kInitializing);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
conn_token.reset();
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 1);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kInitializing);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
init_token.reset();
EXPECT_EQ(update_expiry_count, 3);
EXPECT_EQ(notify_count, 1);
EXPECT_TRUE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kNotConnected);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
}
TEST_F(PeerTest, RegisterBrEdrConnectionDuringInitializingConnectionAndThenCompleteInitialization) {
SetUpPeer(/*address=*/kAddrBrEdr, /*connectable=*/true);
int update_expiry_count = 0;
set_update_expiry_cb([&](const Peer&) { update_expiry_count++; });
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) { notify_count++; });
std::optional<Peer::InitializingConnectionToken> init_token =
peer().MutBrEdr().RegisterInitializingConnection();
// Expiry is updated for state change + becoming non-temporary.
EXPECT_EQ(update_expiry_count, 2);
// 1 notification for becoming non-temporary.
EXPECT_EQ(notify_count, 1);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kInitializing);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
// The connection state should not change when registering a connection because the peer is still
// initializing.
std::optional<Peer::ConnectionToken> conn_token = peer().MutBrEdr().RegisterConnection();
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 1);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kInitializing);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
// When initialization completes, the connection state should become kConnected.
init_token.reset();
EXPECT_EQ(update_expiry_count, 3);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kConnected);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
conn_token.reset();
EXPECT_EQ(update_expiry_count, 4);
EXPECT_EQ(notify_count, 3);
EXPECT_TRUE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kNotConnected);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
}
TEST_F(PeerDeathTest, RegisterInitializingBrEdrConnectionDuringConnectionAsserts) {
SetUpPeer(/*address=*/kAddrBrEdr, /*connectable=*/true);
int update_expiry_count = 0;
set_update_expiry_cb([&](const Peer&) { update_expiry_count++; });
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) { notify_count++; });
std::optional<Peer::ConnectionToken> conn_token = peer().MutBrEdr().RegisterConnection();
// A notification and expiry update are sent when the peer becomes non-temporary, and a second
// notification and update expiry are sent because the initializing connection is registered.
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 2);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kConnected);
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
// Registering an initializing connection when the peer is already connected should assert.
ASSERT_DEATH_IF_SUPPORTED(
{
Peer::InitializingConnectionToken init_token =
peer().MutBrEdr().RegisterInitializingConnection();
},
".*connected.*");
}
TEST_F(PeerTest, RegisterAndUnregisterTwoBrEdrInitializingConnections) {
SetUpPeer(/*address=*/kAddrBrEdr, /*connectable=*/true);
int update_expiry_count = 0;
set_update_expiry_cb([&](const Peer&) { update_expiry_count++; });
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) { notify_count++; });
std::optional<Peer::InitializingConnectionToken> token_0 =
peer().MutBrEdr().RegisterInitializingConnection();
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 1);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kInitializing);
std::optional<std::string> inspect_conn_state = InspectBrEdrConnectionState();
ASSERT_TRUE(inspect_conn_state);
EXPECT_EQ(inspect_conn_state.value(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
std::optional<Peer::InitializingConnectionToken> token_1 =
peer().MutBrEdr().RegisterInitializingConnection();
// The second initializing connection should not update expiry or notify.
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 1);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kInitializing);
inspect_conn_state = InspectBrEdrConnectionState();
ASSERT_TRUE(inspect_conn_state);
EXPECT_EQ(inspect_conn_state.value(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
token_0.reset();
EXPECT_EQ(update_expiry_count, 2);
EXPECT_EQ(notify_count, 1);
EXPECT_FALSE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kInitializing);
inspect_conn_state = InspectBrEdrConnectionState();
ASSERT_TRUE(inspect_conn_state);
EXPECT_EQ(inspect_conn_state.value(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
token_1.reset();
EXPECT_EQ(update_expiry_count, 3);
EXPECT_EQ(notify_count, 1);
EXPECT_TRUE(peer().temporary());
EXPECT_EQ(peer().bredr()->connection_state(), Peer::ConnectionState::kNotConnected);
inspect_conn_state = InspectBrEdrConnectionState();
ASSERT_TRUE(inspect_conn_state);
EXPECT_EQ(inspect_conn_state.value(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
}
TEST_F(PeerTest, SettingLeAdvertisingDataOfBondedPeerDoesNotUpdateName) {
peer().SetName("alice");
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
peer().MutLe().SetBondData(data);
const StaticByteBuffer kAdvData(0x08, // Length
0x09, // AD type: Complete Local Name
'M', 'a', 'l', 'l', 'o', 'r', 'y');
peer().MutLe().SetAdvertisingData(/*rssi=*/0, kAdvData, zx::time(0));
ASSERT_TRUE(peer().name().has_value());
EXPECT_EQ(peer().name().value(), "alice");
}
TEST_F(PeerTest, SettingInquiryDataOfBondedPeerDoesNotUpdateName) {
peer().SetName("alice");
peer().MutBrEdr().SetBondData(kLTK);
const StaticByteBuffer kEirData(0x08, // Length
0x09, // AD type: Complete Local Name
'M', 'a', 'l', 'l', 'o', 'r', 'y');
hci_spec::ExtendedInquiryResultEventParams eirep;
eirep.num_responses = 1;
eirep.bd_addr = peer().address().value();
MutableBufferView(eirep.extended_inquiry_response, sizeof(eirep.extended_inquiry_response))
.Write(kEirData);
peer().MutBrEdr().SetInquiryData(eirep);
ASSERT_TRUE(peer().name().has_value());
EXPECT_EQ(peer().name().value(), "alice");
}
TEST_F(PeerTest, LowEnergyStoreBondCallsCallback) {
int cb_count = 0;
set_store_le_bond_cb([&cb_count](const sm::PairingData& data) {
cb_count++;
return true;
});
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
EXPECT_TRUE(peer().MutLe().StoreBond(data));
EXPECT_EQ(cb_count, 1);
}
} // namespace
} // namespace bt::gap