blob: bb509f0e7da1503c2ef20f6dd395587211f3a8fd [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/public/pw_bluetooth_sapphire/internal/host/gap/peer.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <pw_async/fake_dispatcher_fixture.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/advertising_data.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/manufacturer_names.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/util.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/inspect_util.h"
namespace bt::gap {
namespace {
#ifndef NINSPECT
using namespace inspect::testing;
using bt::testing::GetInspectValue;
using bt::testing::ReadInspect;
constexpr uint16_t kManufacturer = 0x0001;
constexpr uint16_t kSubversion = 0x0002;
#endif // NINSPECT
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 pw::async::test::FakeDispatcherFixture {
public:
PeerTest() = default;
void SetUp() override {
// Set up a default peer.
SetUpPeer(/*address=*/kAddrLePublic, /*connectable=*/true);
}
void TearDown() override { peer_.reset(); }
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_,
dispatcher());
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_; }
#ifndef NINSPECT
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});
BT_ASSERT(val);
return *val;
}
int64_t InspectAdvertisingDataParseFailureCount() {
std::optional<int64_t> val = GetInspectValue<inspect::IntPropertyValue>(
peer_inspector_,
{"peer",
"le_data",
Peer::LowEnergyData::kInspectAdvertisingDataParseFailureCountName});
BT_ASSERT(val);
return *val;
}
std::string InspectLastAdvertisingDataParseFailure() {
std::optional<std::string> val =
GetInspectValue<inspect::StringPropertyValue>(
peer_inspector_,
{"peer",
"le_data",
Peer::LowEnergyData::kInspectLastAdvertisingDataParseFailureName});
BT_ASSERT(val);
return *val;
}
uint64_t MetricsLowEnergyConnections() {
std::optional<uint64_t> val = GetInspectValue<inspect::UintPropertyValue>(
metrics_inspector_, {"metrics", "le", "connection_events"});
BT_ASSERT(val);
return *val;
}
uint64_t MetricsLowEnergyDisconnections() {
std::optional<uint64_t> val = GetInspectValue<inspect::UintPropertyValue>(
metrics_inspector_, {"metrics", "le", "disconnection_events"});
BT_ASSERT(val);
return *val;
}
std::string InspectBrEdrConnectionState() {
std::optional<std::string> val =
GetInspectValue<inspect::StringPropertyValue>(
peer_inspector_,
{"peer",
"bredr_data",
Peer::BrEdrData::kInspectConnectionStateName});
BT_ASSERT(val);
return *val;
}
uint64_t MetricsBrEdrConnections() {
std::optional<uint64_t> val = GetInspectValue<inspect::UintPropertyValue>(
metrics_inspector_, {"metrics", "bredr", "connection_events"});
BT_ASSERT(val);
return *val;
}
uint64_t MetricsBrEdrDisconnections() {
std::optional<uint64_t> val = GetInspectValue<inspect::UintPropertyValue>(
metrics_inspector_, {"metrics", "bredr", "disconnection_events"});
BT_ASSERT(val);
return *val;
}
#endif // NINSPECT
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 {};
#ifndef NINSPECT
TEST_F(PeerTest, InspectHierarchy) {
peer().set_version(pw::bluetooth::emboss::CoreSpecificationVersion::V5_0,
kManufacturer,
kSubversion);
peer().RegisterName("Sapphiređź’–", Peer::NameSource::kGenericAccessService);
peer().MutLe();
ASSERT_TRUE(peer().le().has_value());
peer().MutLe().SetFeatures(hci_spec::LESupportedFeatures{0x0000000000000001});
peer().MutBrEdr().AddService(UUID(uint16_t{0x110b}));
auto bredr_data_matcher = AllOf(NodeMatches(
AllOf(NameMatches(Peer::BrEdrData::kInspectNodeName),
PropertyList(UnorderedElementsAre(
StringIs(Peer::BrEdrData::kInspectConnectionStateName,
Peer::ConnectionStateToString(
peer().bredr()->connection_state())),
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() + " [source: " +
Peer::NameSourceToString(
Peer::NameSource::kGenericAccessService) +
"]"),
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))));
}
#endif // NINSPECT
#ifndef NINSPECT
TEST_F(PeerTest, SetBrEdrBondDataUpdatesInspectProperties) {
const char* const kInspectLevelPropertyName = "level";
const char* const kInspectEncryptedPropertyName = "encrypted";
const char* const kInspectSecureConnectionsPropertyName =
"secure_connections";
const char* const kInspectAuthenticatedPropertyName = "authenticated";
const char* const kInspectKeyTypePropertyName = "key_type";
peer().set_version(pw::bluetooth::emboss::CoreSpecificationVersion::V5_0,
kManufacturer,
kSubversion);
peer().RegisterName("Sapphiređź’–", Peer::NameSource::kGenericAccessService);
peer().MutLe();
ASSERT_TRUE(peer().le().has_value());
peer().MutLe().SetFeatures(hci_spec::LESupportedFeatures{0x0000000000000001});
peer().MutBrEdr().AddService(UUID(uint16_t{0x110b}));
peer().MutBrEdr().SetBondData(kLTK);
// 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())),
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() + " [source: " + Peer::NameSourceToString(Peer::NameSource::kGenericAccessService) + "]"),
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))));
peer().MutBrEdr().SetBondData(kSecureBrEdrKey);
const sm::SecurityProperties security_properties =
peer().bredr()->link_key().value().security();
auto link_key_matcher = AllOf(NodeMatches(AllOf(
NameMatches("link_key"),
PropertyList(UnorderedElementsAre(
StringIs(kInspectLevelPropertyName,
LevelToString(security_properties.level())),
BoolIs(kInspectEncryptedPropertyName,
security_properties.encrypted()),
BoolIs(kInspectSecureConnectionsPropertyName,
security_properties.secure_connections()),
BoolIs(kInspectAuthenticatedPropertyName,
security_properties.authenticated()),
StringIs(kInspectKeyTypePropertyName,
hci_spec::LinkKeyTypeToString(
security_properties.GetLinkKeyType().value())))))));
auto bredr_data_matcher2 =
AllOf(NodeMatches(AllOf(
NameMatches(Peer::BrEdrData::kInspectNodeName),
PropertyList(UnorderedElementsAre(
StringIs(Peer::BrEdrData::kInspectConnectionStateName,
Peer::ConnectionStateToString(
peer().bredr()->connection_state())),
StringIs(Peer::BrEdrData::kInspectServicesName,
"{ 0000110b-0000-1000-8000-00805f9b34fb }"))))),
ChildrenMatch(UnorderedElementsAre(link_key_matcher)));
auto le_data_matcher2 = 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_matcher2 = AllOf(
NodeMatches(PropertyList(UnorderedElementsAre(
StringIs(Peer::kInspectPeerIdName, peer().identifier().ToString()),
StringIs(Peer::kInspectPeerNameName,
peer().name().value() + " [source: " +
Peer::NameSourceToString(
Peer::NameSource::kGenericAccessService) +
"]"),
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_matcher2, le_data_matcher2)));
// clang-format on
hierarchy = ReadPeerInspect();
EXPECT_THAT(hierarchy,
AllOf(ChildrenMatch(UnorderedElementsAre(peer_matcher2))));
}
#endif // NINSPECT
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, pw::chrono::SystemClock::time_point());
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
);
StaticPacket<pw::bluetooth::emboss::ExtendedInquiryResultEventWriter> eirep;
eirep.view().num_responses().Write(1);
eirep.view().bd_addr().CopyFrom(peer().address().value().view());
eirep.view().extended_inquiry_response().BackingStorage().CopyFrom(
::emboss::support::ReadOnlyContiguousBuffer(&kEirData), kEirData.size());
peer().MutBrEdr().SetInquiryData(eirep.view());
EXPECT_TRUE(listener_notified); // Fresh EIR data still results in an update
EXPECT_FALSE(peer().name().has_value());
}
TEST_F(PeerTest, RegisterNameWithInvalidUtf8NameDoesNotUpdatePeerName) {
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().RegisterName(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,
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(1)));
ASSERT_TRUE(peer().MutLe().parsed_advertising_data_timestamp());
EXPECT_EQ(peer().MutLe().parsed_advertising_data_timestamp().value(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(1)));
peer().MutLe().SetAdvertisingData(
/*rssi=*/0,
kAdvData,
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
ASSERT_TRUE(peer().MutLe().parsed_advertising_data_timestamp());
EXPECT_EQ(peer().MutLe().parsed_advertising_data_timestamp().value(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
// SetAdvertisingData with data that fails to parse should not update the
// advertising data timestamp.
peer().MutLe().SetAdvertisingData(
/*rssi=*/0,
kInvalidAdvData,
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(3)));
ASSERT_TRUE(peer().MutLe().parsed_advertising_data_timestamp());
EXPECT_EQ(peer().MutLe().parsed_advertising_data_timestamp().value(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
}
TEST_F(PeerTest, SettingLowEnergyAdvertisingDataUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(0)));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
notify_count++;
});
RunFor(pw::chrono::SystemClock::duration(2));
peer().MutLe().SetAdvertisingData(
/*rssi=*/0,
kAdvData,
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(1)));
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, RegisteringLowEnergyInitializingConnectionUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(0)));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
notify_count++;
});
RunFor(pw::chrono::SystemClock::duration(2));
Peer::InitializingConnectionToken token =
peer().MutLe().RegisterInitializingConnection();
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, SettingLowEnergyBondDataUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(0)));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
notify_count++;
});
RunFor(pw::chrono::SystemClock::duration(2));
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
peer().MutLe().SetBondData(data);
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, RegisteringBrEdrInitializingConnectionUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(0)));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
notify_count++;
});
RunFor(pw::chrono::SystemClock::duration(2));
Peer::InitializingConnectionToken token =
peer().MutBrEdr().RegisterInitializingConnection();
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, SettingInquiryDataUpdatesLastUpdated) {
SetUpPeer(/*address=*/kAddrLeAlias, /*connectable=*/true);
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(0)));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
notify_count++;
});
RunFor(pw::chrono::SystemClock::duration(2));
StaticPacket<pw::bluetooth::emboss::InquiryResultWriter> ir;
ir.view().bd_addr().CopyFrom(kAddrLeAlias.value().view());
peer().MutBrEdr().SetInquiryData(ir.view());
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, SettingBrEdrBondDataUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(0)));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
notify_count++;
});
RunFor(pw::chrono::SystemClock::duration(2));
peer().MutBrEdr().SetBondData(kSecureBrEdrKey);
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, SettingAddingBrEdrServiceUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(0)));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
notify_count++;
});
RunFor(pw::chrono::SystemClock::duration(2));
peer().MutBrEdr().AddService(UUID(uint16_t{0x110b}));
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
EXPECT_GE(notify_count, 1);
}
TEST_F(PeerTest, RegisteringNameUpdatesLastUpdated) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(0)));
int notify_count = 0;
set_notify_listeners_cb([&](const Peer&, Peer::NotifyListenersChange) {
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(2)));
notify_count++;
});
RunFor(pw::chrono::SystemClock::duration(2));
peer().RegisterName("name");
EXPECT_EQ(peer().last_updated(),
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(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);
#ifndef NINSPECT
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
EXPECT_EQ(MetricsLowEnergyConnections(), 1u);
#endif // NINSPECT
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);
#ifndef NINSPECT
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);
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
EXPECT_EQ(MetricsLowEnergyDisconnections(), 1u);
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
EXPECT_EQ(MetricsLowEnergyDisconnections(), 2u);
#endif // NINSPECT
}
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
#endif // NINSPECT
}
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
#endif // NINSPECT
}
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
#endif // NINSPECT
}
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);
#ifndef NINSPECT
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
#endif // NINSPECT
}
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectLowEnergyConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
#endif // NINSPECT
}
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, RegisterNamesWithVariousSources) {
ASSERT_FALSE(peer().name().has_value());
ASSERT_TRUE(peer().RegisterName("test", Peer::kAdvertisingDataComplete));
// Test that name with lower source priority does not replace stored name with
// higher priority.
ASSERT_FALSE(peer().RegisterName("test", Peer::kUnknown));
// Test that name with higher source priority replaces stored name with lower
// priority.
ASSERT_TRUE(peer().RegisterName("test", Peer::kGenericAccessService));
// Test that stored name is not replaced with an identical name from an
// identical source.
ASSERT_FALSE(peer().RegisterName("test", Peer::kGenericAccessService));
// Test that stored name is replaced by a different name from the same source.
ASSERT_TRUE(
peer().RegisterName("different_name", Peer::kGenericAccessService));
}
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, pw::chrono::SystemClock::time_point());
// 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(Peer::NameSource::kAdvertisingDataComplete, peer().name_source());
#ifndef NINSPECT
EXPECT_EQ(0, InspectAdvertisingDataParseFailureCount());
EXPECT_EQ("", InspectLastAdvertisingDataParseFailure());
#endif // NINSPECT
}
TEST_F(PeerTest, SetShortenedLocalName) {
constexpr const char* kLocalName = "Test";
StaticByteBuffer raw_data{
// Length - Type - Value formatted Local name
0x05,
static_cast<uint8_t>(DataType::kShortenedLocalName),
kLocalName[0],
kLocalName[1],
kLocalName[2],
kLocalName[3],
};
peer().MutLe().SetAdvertisingData(
/*rssi=*/32, raw_data, pw::chrono::SystemClock::time_point());
ASSERT_TRUE(peer().name().has_value());
EXPECT_EQ(kLocalName, peer().name().value());
EXPECT_EQ(Peer::NameSource::kAdvertisingDataShortened, peer().name_source());
}
TEST_F(PeerTest, SetInvalidAdvertisingData) {
peer().MutLe().SetAdvertisingData(
/*rssi=*/32, kInvalidAdvData, pw::chrono::SystemClock::time_point());
#ifndef NINSPECT
EXPECT_EQ(1, InspectAdvertisingDataParseFailureCount());
EXPECT_EQ(AdvertisingData::ParseErrorToString(
AdvertisingData::ParseError::kUuidsMalformed),
InspectLastAdvertisingDataParseFailure());
#endif // NINSPECT
}
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
#endif // NINSPECT
}
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);
#ifndef NINSPECT
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
#endif // NINSPECT
}
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);
#ifndef NINSPECT
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
#endif // NINSPECT
}
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
// 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);
#ifndef NINSPECT
EXPECT_EQ(
InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
#endif // NINSPECT
}
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
// 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);
#ifndef NINSPECT
EXPECT_EQ(
InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
// 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);
#ifndef NINSPECT
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
#endif // NINSPECT
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);
#ifndef NINSPECT
EXPECT_EQ(
InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
#endif // NINSPECT
}
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);
#ifndef NINSPECT
EXPECT_EQ(InspectBrEdrConnectionState(),
Peer::ConnectionStateToString(Peer::ConnectionState::kConnected));
#endif // NINSPECT
// 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);
#ifndef NINSPECT
std::optional<std::string> inspect_conn_state = InspectBrEdrConnectionState();
ASSERT_TRUE(inspect_conn_state);
EXPECT_EQ(
inspect_conn_state.value(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
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);
#ifndef NINSPECT
inspect_conn_state = InspectBrEdrConnectionState();
ASSERT_TRUE(inspect_conn_state);
EXPECT_EQ(
inspect_conn_state.value(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
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);
#ifndef NINSPECT
inspect_conn_state = InspectBrEdrConnectionState();
ASSERT_TRUE(inspect_conn_state);
EXPECT_EQ(
inspect_conn_state.value(),
Peer::ConnectionStateToString(Peer::ConnectionState::kInitializing));
#endif // NINSPECT
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);
#ifndef NINSPECT
inspect_conn_state = InspectBrEdrConnectionState();
ASSERT_TRUE(inspect_conn_state);
EXPECT_EQ(
inspect_conn_state.value(),
Peer::ConnectionStateToString(Peer::ConnectionState::kNotConnected));
#endif // NINSPECT
}
TEST_F(PeerTest, SettingLeAdvertisingDataOfBondedPeerDoesNotUpdateName) {
peer().RegisterName("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,
pw::chrono::SystemClock::time_point(std::chrono::nanoseconds(0)));
ASSERT_TRUE(peer().name().has_value());
EXPECT_EQ(peer().name().value(), "alice");
}
TEST_F(PeerTest, SettingInquiryDataOfBondedPeerDoesNotUpdateName) {
peer().RegisterName("alice");
peer().MutBrEdr().SetBondData(kLTK);
const StaticByteBuffer kEirData(0x08, // Length
0x09, // AD type: Complete Local Name
'M',
'a',
'l',
'l',
'o',
'r',
'y');
StaticPacket<pw::bluetooth::emboss::ExtendedInquiryResultEventWriter> eirep;
eirep.view().num_responses().Write(1);
eirep.view().bd_addr().CopyFrom(peer().address().value().view());
eirep.view().extended_inquiry_response().BackingStorage().CopyFrom(
::emboss::support::ReadOnlyContiguousBuffer(&kEirData), kEirData.size());
peer().MutBrEdr().SetInquiryData(eirep.view());
ASSERT_TRUE(peer().name().has_value());
EXPECT_EQ(peer().name().value(), "alice");
}
TEST_F(PeerTest, BrEdrDataSetEirDataDoesUpdatePeerName) {
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(0x0D, // Length (13)
0x09, // AD type: Complete Local Name
'S',
'a',
'p',
'p',
'h',
'i',
'r',
'e',
0xf0,
0x9f,
0x92,
0x96);
StaticPacket<pw::bluetooth::emboss::ExtendedInquiryResultEventWriter> eirep;
eirep.view().num_responses().Write(1);
eirep.view().bd_addr().CopyFrom(peer().address().value().view());
eirep.view().extended_inquiry_response().BackingStorage().CopyFrom(
::emboss::support::ReadOnlyContiguousBuffer(&kEirData), kEirData.size());
peer().MutBrEdr().SetInquiryData(eirep.view());
EXPECT_TRUE(listener_notified); // Fresh EIR data results in an update
ASSERT_TRUE(peer().name().has_value());
EXPECT_EQ(peer().name().value(), "Sapphiređź’–");
}
TEST_F(PeerTest, SetEirDataUpdatesServiceUUIDs) {
peer().MutBrEdr(); // Initialize BrEdrData.
// clang-format off
const StaticByteBuffer kEirJustServiceUuids{
// One 16-bit UUID: AudioSink
0x03, static_cast<uint8_t>(DataType::kIncomplete16BitServiceUuids), 0x0A, 0x11,
};
StaticPacket<pw::bluetooth::emboss::ExtendedInquiryResultEventWriter> eirep;
eirep.view().num_responses().Write(1);
eirep.view().bd_addr().CopyFrom(peer().address().value().view());
eirep.view().extended_inquiry_response().BackingStorage().CopyFrom(
::emboss::support::ReadOnlyContiguousBuffer(&kEirJustServiceUuids),
kEirJustServiceUuids.size());
peer().MutBrEdr().SetInquiryData(eirep.view());
EXPECT_EQ(peer().bredr()->services().size(), 1u);
EXPECT_EQ(peer().bredr()->services().count(UUID((uint16_t)0x110A)), 1u);
}
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