blob: eaad05a9f4f09e29d683f09d58c635fb6049ae7c [file] [log] [blame]
// Copyright 2017 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_cache.h"
#include <vector>
#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/device_class.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/random.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/uint128.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/uuid.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/peer.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/link_key.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/low_energy_scanner.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/smp.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/types.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/util.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/inspect.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
namespace bt::gap {
namespace {
using namespace inspect::testing;
// All fields are initialized to zero as they are unused in these tests.
const hci_spec::LEConnectionParameters kTestParams;
// Arbitrary ID value used by the bonding tests below. The actual value of this
// constant does not effect the test logic.
constexpr PeerId kId(100);
constexpr int8_t kTestRSSI = 10;
const DeviceAddress kAddrBrEdr(DeviceAddress::Type::kBREDR,
{0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA});
const DeviceAddress kAddrLePublic(DeviceAddress::Type::kLEPublic,
{6, 5, 4, 3, 2, 1});
// 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});
// TODO(armansito): Make these adhere to privacy specification.
const DeviceAddress kAddrLeRandom(DeviceAddress::Type::kLERandom,
{1, 2, 3, 4, 5, 6});
const DeviceAddress kAddrLeRandom2(DeviceAddress::Type::kLERandom,
{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF});
const DeviceAddress kAddrLeAnon(DeviceAddress::Type::kLEAnonymous,
{1, 2, 3, 4, 5, 6});
// Arbitrary name value used by the bonding tests below. The actual value of
// this constant does not effect the test logic.
const std::string kName = "TestName";
const StaticByteBuffer kAdvData(0x05, // Length
0x09, // AD type: Complete Local Name
'T',
'e',
's',
't');
const auto kEirData = kAdvData;
const bt::sm::LTK kLTK;
const bt::sm::Key kKey{};
const bt::sm::LTK kBrEdrKey;
const bt::sm::LTK kInsecureBrEdrKey(
sm::SecurityProperties(/*encrypted=*/true,
/*authenticated=*/false,
/*secure_connections=*/false,
sm::kMaxEncryptionKeySize),
hci_spec::LinkKey(UInt128{1}, 2, 3));
const bt::sm::LTK kSecureBrEdrKey(
sm::SecurityProperties(/*encrypted=*/true,
/*authenticated=*/true,
/*secure_connections=*/true,
sm::kMaxEncryptionKeySize),
hci_spec::LinkKey(UInt128{4}, 5, 6));
const std::vector<bt::UUID> kBrEdrServices = {UUID(uint16_t{0x110a}),
UUID(uint16_t{0x110b})};
// Phone (Networking)
const DeviceClass kTestDeviceClass({0x06, 0x02, 0x02});
class PeerCacheTest : public pw::async::test::FakeDispatcherFixture {
public:
void SetUp() override { cache_ = std::make_unique<PeerCache>(dispatcher()); }
void TearDown() override {
RunUntilIdle();
cache_.reset();
}
protected:
// Creates a new Peer, and caches a pointer to that peer.
[[nodiscard]] bool NewPeer(const DeviceAddress& addr, bool connectable) {
auto* peer = cache()->NewPeer(addr, connectable);
if (!peer) {
return false;
}
peer_ = peer;
return true;
}
PeerCache* cache() { return cache_.get(); }
// Returns the cached pointer to the peer created in the most recent call to
// NewPeer(). The caller must ensure that the peer has not expired out of
// the cache. (Tests of cache expiration should generally subclass the
// PeerCacheExpirationTest fixture.)
Peer* peer() { return peer_; }
private:
std::unique_ptr<PeerCache> cache_;
Peer* peer_;
};
#ifndef NINSPECT
TEST_F(PeerCacheTest, InspectHierarchyContainsMetrics) {
inspect::Inspector inspector;
cache()->AttachInspect(inspector.GetRoot());
auto le_matcher = AllOf(NodeMatches(AllOf(
NameMatches("le"),
PropertyList(UnorderedElementsAre(UintIs("bond_success_events", 0),
UintIs("bond_failure_events", 0),
UintIs("connection_events", 0),
UintIs("disconnection_events", 0))))));
auto bredr_matcher = AllOf(NodeMatches(AllOf(
NameMatches("bredr"),
PropertyList(UnorderedElementsAre(UintIs("bond_success_events", 0),
UintIs("bond_failure_events", 0),
UintIs("connection_events", 0),
UintIs("disconnection_events", 0))))));
auto metrics_node_matcher =
AllOf(NodeMatches(NameMatches(PeerMetrics::kInspectNodeName)),
ChildrenMatch(UnorderedElementsAre(bredr_matcher, le_matcher)));
auto peer_cache_matcher =
AllOf(NodeMatches(AllOf(PropertyList(testing::IsEmpty()))),
ChildrenMatch(UnorderedElementsAre(metrics_node_matcher)));
auto hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
EXPECT_THAT(hierarchy,
AllOf(ChildrenMatch(UnorderedElementsAre(peer_cache_matcher))));
}
#endif // NINSPECT
#ifndef NINSPECT
TEST_F(PeerCacheTest,
InspectHierarchyContainsAddedPeersAndDoesNotContainRemovedPeers) {
inspect::Inspector inspector;
cache()->AttachInspect(inspector.GetRoot());
Peer* peer0 = cache()->NewPeer(kAddrLePublic, /*connectable=*/true);
auto peer0_matcher = AllOf(NodeMatches(AllOf(NameMatches("peer_0x0"))));
cache()->NewPeer(kAddrBrEdr, /*connectable=*/true);
auto peer1_matcher = AllOf(NodeMatches(AllOf(NameMatches("peer_0x1"))));
auto metrics_matcher =
AllOf(NodeMatches(AllOf(NameMatches(PeerMetrics::kInspectNodeName))));
// Hierarchy should contain peer0 and peer1.
auto hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
auto peer_cache_matcher0 =
AllOf(NodeMatches(AllOf(PropertyList(testing::IsEmpty()))),
ChildrenMatch(UnorderedElementsAre(
peer0_matcher, peer1_matcher, metrics_matcher)));
EXPECT_THAT(hierarchy,
AllOf(ChildrenMatch(UnorderedElementsAre(peer_cache_matcher0))));
// peer0 should be removed from hierarchy after it is removed from the cache
// because its Node is destroyed along with the Peer object.
EXPECT_TRUE(cache()->RemoveDisconnectedPeer(peer0->identifier()));
hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
auto peer_cache_matcher1 = AllOf(
NodeMatches(AllOf(PropertyList(testing::IsEmpty()))),
ChildrenMatch(UnorderedElementsAre(peer1_matcher, metrics_matcher)));
EXPECT_THAT(hierarchy,
AllOf(ChildrenMatch(UnorderedElementsAre(peer_cache_matcher1))));
}
#endif // NINSPECT
TEST_F(PeerCacheTest, LookUp) {
StaticByteBuffer kAdvData0(0x05, 0x09, 'T', 'e', 's', 't');
StaticByteBuffer kAdvData1(
0x0C, 0x09, 'T', 'e', 's', 't', ' ', 'D', 'e', 'v', 'i', 'c', 'e');
// These should return false regardless of the input while the cache is empty.
EXPECT_FALSE(cache()->FindByAddress(kAddrLePublic));
EXPECT_FALSE(cache()->FindById(kId));
auto peer = cache()->NewPeer(kAddrLePublic, /*connectable=*/true);
ASSERT_TRUE(peer);
ASSERT_TRUE(peer->le());
EXPECT_EQ(TechnologyType::kLowEnergy, peer->technology());
EXPECT_TRUE(peer->connectable());
EXPECT_TRUE(peer->temporary());
EXPECT_EQ(kAddrLePublic, peer->address());
EXPECT_FALSE(peer->le()->parsed_advertising_data().has_value());
EXPECT_EQ(hci_spec::kRSSIInvalid, peer->rssi());
// A look up should return the same instance.
EXPECT_EQ(peer, cache()->FindById(peer->identifier()));
EXPECT_EQ(peer, cache()->FindByAddress(peer->address()));
// Adding a peer with the same address should return nullptr.
EXPECT_FALSE(cache()->NewPeer(kAddrLePublic, true));
peer->MutLe().SetAdvertisingData(
kTestRSSI, kAdvData1, pw::chrono::SystemClock::time_point());
EXPECT_TRUE(peer->le()->parsed_advertising_data().has_value());
EXPECT_EQ(kTestRSSI, peer->rssi());
ASSERT_TRUE(peer->name().has_value());
EXPECT_EQ(peer->name().value(), "Test Device");
peer->MutLe().SetAdvertisingData(
kTestRSSI, kAdvData0, pw::chrono::SystemClock::time_point());
EXPECT_TRUE(peer->le()->parsed_advertising_data().has_value());
EXPECT_EQ(kTestRSSI, peer->rssi());
ASSERT_TRUE(peer->name().has_value());
EXPECT_EQ(peer->name().value(), "Test");
}
TEST_F(PeerCacheTest, LookUpBrEdrPeerByLePublicAlias) {
ASSERT_FALSE(cache()->FindByAddress(kAddrLeAlias));
ASSERT_TRUE(NewPeer(kAddrBrEdr, true));
auto* p = cache()->FindByAddress(kAddrBrEdr);
ASSERT_TRUE(p);
EXPECT_EQ(peer(), p);
p = cache()->FindByAddress(kAddrLeAlias);
ASSERT_TRUE(p);
EXPECT_EQ(peer(), p);
EXPECT_EQ(DeviceAddress::Type::kBREDR, p->address().type());
}
TEST_F(PeerCacheTest, LookUpLePeerByBrEdrAlias) {
EXPECT_FALSE(cache()->FindByAddress(kAddrBrEdr));
ASSERT_TRUE(NewPeer(kAddrLeAlias, true));
auto* p = cache()->FindByAddress(kAddrLeAlias);
ASSERT_TRUE(p);
EXPECT_EQ(peer(), p);
p = cache()->FindByAddress(kAddrBrEdr);
ASSERT_TRUE(p);
EXPECT_EQ(peer(), p);
EXPECT_EQ(DeviceAddress::Type::kLEPublic, p->address().type());
}
TEST_F(PeerCacheTest, NewPeerDoesNotCrashWhenNoCallbackIsRegistered) {
cache()->NewPeer(kAddrLePublic, /*connectable=*/true);
}
TEST_F(PeerCacheTest, ForEachEmpty) {
bool found = false;
cache()->ForEach([&](const auto&) { found = true; });
EXPECT_FALSE(found);
}
TEST_F(PeerCacheTest, ForEach) {
int count = 0;
ASSERT_TRUE(NewPeer(kAddrLePublic, true));
cache()->ForEach([&](const auto& p) {
count++;
EXPECT_EQ(peer()->identifier(), p.identifier());
EXPECT_EQ(peer()->address(), p.address());
});
EXPECT_EQ(1, count);
}
TEST_F(PeerCacheTest, NewPeerInvokesCallbackWhenPeerIsFirstRegistered) {
bool was_called = false;
cache()->add_peer_updated_callback(
[&was_called](const auto&) { was_called = true; });
cache()->NewPeer(kAddrLePublic, /*connectable=*/true);
EXPECT_TRUE(was_called);
}
TEST_F(PeerCacheTest, MultiplePeerUpdatedCallbacks) {
size_t updated_count_0 = 0, updated_count_1 = 0;
PeerCache::CallbackId id_0 = cache()->add_peer_updated_callback(
[&](const auto&) { updated_count_0++; });
PeerCache::CallbackId id_1 = cache()->add_peer_updated_callback(
[&](const auto&) { updated_count_1++; });
cache()->NewPeer(kAddrLePublic, /*connectable=*/true);
EXPECT_EQ(updated_count_0, 1u);
EXPECT_EQ(updated_count_1, 1u);
cache()->NewPeer(kAddrLeRandom, /*connectable=*/true);
EXPECT_EQ(updated_count_0, 2u);
EXPECT_EQ(updated_count_1, 2u);
EXPECT_TRUE(cache()->remove_peer_updated_callback(id_0));
EXPECT_FALSE(cache()->remove_peer_updated_callback(id_0));
cache()->NewPeer(kAddrLeRandom2, /*connectable=*/true);
EXPECT_EQ(updated_count_0, 2u);
EXPECT_EQ(updated_count_1, 3u);
EXPECT_TRUE(cache()->remove_peer_updated_callback(id_1));
EXPECT_FALSE(cache()->remove_peer_updated_callback(id_1));
cache()->NewPeer(kAddrBrEdr, /*connectable=*/true);
EXPECT_EQ(updated_count_0, 2u);
EXPECT_EQ(updated_count_1, 3u);
}
TEST_F(PeerCacheTest, NewPeerDoesNotInvokeCallbackWhenPeerIsReRegistered) {
int call_count = 0;
cache()->add_peer_updated_callback(
[&call_count](const auto&) { ++call_count; });
cache()->NewPeer(kAddrLePublic, /*connectable=*/true);
cache()->NewPeer(kAddrLePublic, /*connectable=*/true);
EXPECT_EQ(1, call_count);
}
TEST_F(PeerCacheTest, NewPeerIdentityKnown) {
EXPECT_TRUE(cache()->NewPeer(kAddrBrEdr, true)->identity_known());
EXPECT_TRUE(cache()->NewPeer(kAddrLePublic, true)->identity_known());
EXPECT_FALSE(cache()->NewPeer(kAddrLeRandom, true)->identity_known());
EXPECT_FALSE(cache()->NewPeer(kAddrLeAnon, false)->identity_known());
}
TEST_F(PeerCacheTest, NewPeerInitialTechnologyIsClassic) {
ASSERT_TRUE(NewPeer(kAddrBrEdr, true));
// A peer initialized with a BR/EDR address should start out as a
// classic-only.
ASSERT_TRUE(peer());
EXPECT_TRUE(peer()->bredr());
EXPECT_FALSE(peer()->le());
EXPECT_TRUE(peer()->identity_known());
EXPECT_EQ(TechnologyType::kClassic, peer()->technology());
}
TEST_F(PeerCacheTest, NewPeerInitialTechnologyLowEnergy) {
// LE address types should initialize the peer as LE-only.
auto* le_publ_peer = cache()->NewPeer(kAddrLePublic, /*connectable=*/true);
auto* le_rand_peer = cache()->NewPeer(kAddrLeRandom, /*connectable=*/true);
auto* le_anon_peer = cache()->NewPeer(kAddrLeAnon, /*connectable=*/false);
ASSERT_TRUE(le_publ_peer);
ASSERT_TRUE(le_rand_peer);
ASSERT_TRUE(le_anon_peer);
EXPECT_TRUE(le_publ_peer->le());
EXPECT_TRUE(le_rand_peer->le());
EXPECT_TRUE(le_anon_peer->le());
EXPECT_FALSE(le_publ_peer->bredr());
EXPECT_FALSE(le_rand_peer->bredr());
EXPECT_FALSE(le_anon_peer->bredr());
EXPECT_EQ(TechnologyType::kLowEnergy, le_publ_peer->technology());
EXPECT_EQ(TechnologyType::kLowEnergy, le_rand_peer->technology());
EXPECT_EQ(TechnologyType::kLowEnergy, le_anon_peer->technology());
EXPECT_TRUE(le_publ_peer->identity_known());
EXPECT_FALSE(le_rand_peer->identity_known());
EXPECT_FALSE(le_anon_peer->identity_known());
}
TEST_F(PeerCacheTest, DisallowNewLowEnergyPeerIfBrEdrPeerExists) {
ASSERT_TRUE(NewPeer(kAddrBrEdr, true));
// Try to add new LE peer with a public identity address containing the same
// value as the existing BR/EDR peer's BD_ADDR.
auto* le_alias_peer = cache()->NewPeer(kAddrLeAlias, /*connectable=*/true);
EXPECT_FALSE(le_alias_peer);
}
TEST_F(PeerCacheTest, DisallowNewBrEdrPeerIfLowEnergyPeerExists) {
ASSERT_TRUE(NewPeer(kAddrLeAlias, true));
// Try to add new BR/EDR peer with BD_ADDR containing the same value as the
// existing LE peer's public identity address.
auto* bredr_alias_peer = cache()->NewPeer(kAddrBrEdr, /*connectable=*/true);
ASSERT_FALSE(bredr_alias_peer);
}
TEST_F(PeerCacheTest, BrEdrPeerBecomesDualModeWithAdvertisingData) {
ASSERT_TRUE(NewPeer(kAddrBrEdr, true));
ASSERT_TRUE(peer()->bredr());
ASSERT_FALSE(peer()->le());
peer()->MutLe().SetAdvertisingData(
kTestRSSI, kAdvData, pw::chrono::SystemClock::time_point());
EXPECT_TRUE(peer()->le());
EXPECT_EQ(TechnologyType::kDualMode, peer()->technology());
// Searching by LE address should turn up this peer, which should retain its
// original address type.
auto* const le_peer = cache()->FindByAddress(kAddrLeAlias);
ASSERT_EQ(peer(), le_peer);
EXPECT_EQ(DeviceAddress::Type::kBREDR, peer()->address().type());
}
TEST_F(PeerCacheTest, BrEdrPeerBecomesDualModeWhenConnectedOverLowEnergy) {
ASSERT_TRUE(NewPeer(kAddrBrEdr, true));
ASSERT_TRUE(peer()->bredr());
ASSERT_FALSE(peer()->le());
Peer::ConnectionToken conn_token = peer()->MutLe().RegisterConnection();
EXPECT_TRUE(peer()->le());
EXPECT_EQ(TechnologyType::kDualMode, peer()->technology());
auto* const le_peer = cache()->FindByAddress(kAddrLeAlias);
ASSERT_EQ(peer(), le_peer);
EXPECT_EQ(DeviceAddress::Type::kBREDR, peer()->address().type());
}
TEST_F(PeerCacheTest, BrEdrPeerBecomesDualModeWithLowEnergyConnParams) {
ASSERT_TRUE(NewPeer(kAddrBrEdr, true));
ASSERT_TRUE(peer()->bredr());
ASSERT_FALSE(peer()->le());
peer()->MutLe().SetConnectionParameters({});
EXPECT_TRUE(peer()->le());
EXPECT_EQ(TechnologyType::kDualMode, peer()->technology());
auto* const le_peer = cache()->FindByAddress(kAddrLeAlias);
ASSERT_EQ(peer(), le_peer);
EXPECT_EQ(DeviceAddress::Type::kBREDR, peer()->address().type());
}
TEST_F(PeerCacheTest,
BrEdrPeerBecomesDualModeWithLowEnergyPreferredConnParams) {
ASSERT_TRUE(NewPeer(kAddrBrEdr, true));
ASSERT_TRUE(peer()->bredr());
ASSERT_FALSE(peer()->le());
peer()->MutLe().SetPreferredConnectionParameters({});
EXPECT_TRUE(peer()->le());
EXPECT_EQ(TechnologyType::kDualMode, peer()->technology());
auto* const le_peer = cache()->FindByAddress(kAddrLeAlias);
ASSERT_EQ(peer(), le_peer);
EXPECT_EQ(DeviceAddress::Type::kBREDR, peer()->address().type());
}
TEST_F(PeerCacheTest, LowEnergyPeerBecomesDualModeWithInquiryData) {
ASSERT_TRUE(NewPeer(kAddrLeAlias, true));
ASSERT_TRUE(peer()->le());
ASSERT_FALSE(peer()->bredr());
StaticPacket<pw::bluetooth::emboss::InquiryResultWriter> ir;
ir.view().bd_addr().CopyFrom(kAddrLeAlias.value().view());
peer()->MutBrEdr().SetInquiryData(ir.view());
EXPECT_TRUE(peer()->bredr());
EXPECT_EQ(TechnologyType::kDualMode, peer()->technology());
// Searching by only BR/EDR technology should turn up this peer, which
// should still retain its original address type.
auto* const bredr_peer = cache()->FindByAddress(kAddrBrEdr);
ASSERT_EQ(peer(), bredr_peer);
EXPECT_EQ(DeviceAddress::Type::kLEPublic, peer()->address().type());
EXPECT_EQ(kAddrBrEdr, peer()->bredr()->address());
}
TEST_F(PeerCacheTest, LowEnergyPeerBecomesDualModeWhenConnectedOverClassic) {
ASSERT_TRUE(NewPeer(kAddrLeAlias, true));
ASSERT_TRUE(peer()->le());
ASSERT_FALSE(peer()->bredr());
Peer::ConnectionToken token = peer()->MutBrEdr().RegisterConnection();
EXPECT_TRUE(peer()->bredr());
EXPECT_EQ(TechnologyType::kDualMode, peer()->technology());
auto* const bredr_peer = cache()->FindByAddress(kAddrBrEdr);
ASSERT_EQ(peer(), bredr_peer);
EXPECT_EQ(DeviceAddress::Type::kLEPublic, peer()->address().type());
EXPECT_EQ(kAddrBrEdr, peer()->bredr()->address());
}
TEST_F(PeerCacheTest, InitialAutoConnectBehavior) {
ASSERT_TRUE(NewPeer(kAddrLeAlias, true));
// Peers are not autoconnected before they are bonded.
EXPECT_FALSE(peer()->le()->should_auto_connect());
sm::PairingData data;
data.peer_ltk = sm::LTK();
data.local_ltk = sm::LTK();
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
// Bonded peers should autoconnect
EXPECT_TRUE(peer()->le()->should_auto_connect());
// Connecting peer leaves `should_auto_connect` unaffected.
Peer::ConnectionToken conn_token = peer()->MutLe().RegisterConnection();
EXPECT_TRUE(peer()->le()->should_auto_connect());
}
TEST_F(PeerCacheTest, AutoConnectDisabledAfterIntentionalDisconnect) {
ASSERT_TRUE(NewPeer(kAddrLeAlias, true));
cache()->SetAutoConnectBehaviorForIntentionalDisconnect(peer()->identifier());
EXPECT_FALSE(peer()->le()->should_auto_connect());
}
TEST_F(PeerCacheTest, AutoConnectReenabledAfterSuccessfulConnect) {
ASSERT_TRUE(NewPeer(kAddrLeAlias, true));
// Only bonded peers are eligible for autoconnect.
sm::PairingData data;
data.peer_ltk = sm::LTK();
data.local_ltk = sm::LTK();
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
cache()->SetAutoConnectBehaviorForIntentionalDisconnect(peer()->identifier());
EXPECT_FALSE(peer()->le()->should_auto_connect());
cache()->SetAutoConnectBehaviorForSuccessfulConnection(peer()->identifier());
EXPECT_TRUE(peer()->le()->should_auto_connect());
}
class PeerCacheTestBondingTest : public PeerCacheTest {
public:
void SetUp() override {
PeerCacheTest::SetUp();
ASSERT_TRUE(NewPeer(kAddrLePublic, true));
bonded_callback_count_ = 0;
cache()->set_peer_bonded_callback(
[this](const auto&) { bonded_callback_count_++; });
updated_callback_count_ = 0;
updated_callback_id_ = cache()->add_peer_updated_callback(
[this](auto&) { updated_callback_count_++; });
removed_callback_count_ = 0;
cache()->set_peer_removed_callback(
[this](PeerId) { removed_callback_count_++; });
}
void TearDown() override {
cache()->set_peer_removed_callback(nullptr);
removed_callback_count_ = 0;
EXPECT_TRUE(cache()->remove_peer_updated_callback(updated_callback_id_));
updated_callback_count_ = 0;
cache()->set_peer_bonded_callback(nullptr);
bonded_callback_count_ = 0;
PeerCacheTest::TearDown();
}
protected:
bool bonded_callback_called() const { return bonded_callback_count_ != 0; }
// Returns 0 at the beginning of each test case.
int bonded_callback_count() const { return bonded_callback_count_; }
int updated_callback_count() const { return updated_callback_count_; }
int removed_callback_count() const { return removed_callback_count_; }
private:
int bonded_callback_count_;
int updated_callback_count_;
int removed_callback_count_;
PeerCache::CallbackId updated_callback_id_ = 0;
};
TEST_F(PeerCacheTestBondingTest, AddBondedPeerFailsWithExistingId) {
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
EXPECT_FALSE(
cache()->AddBondedPeer(BondingData{.identifier = peer()->identifier(),
.address = kAddrLeRandom,
.name = {},
.le_pairing_data = data,
.bredr_link_key = {},
.bredr_services = {}}));
EXPECT_FALSE(bonded_callback_called());
}
TEST_F(PeerCacheTestBondingTest, AddBondedPeerFailsWithExistingAddress) {
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
EXPECT_FALSE(cache()->AddBondedPeer(BondingData{.identifier = kId,
.address = peer()->address(),
.name = {},
.le_pairing_data = data,
.bredr_link_key = {},
.bredr_services = {}}));
EXPECT_FALSE(bonded_callback_called());
}
TEST_F(PeerCacheTestBondingTest,
AddBondedLowEnergyPeerFailsWithExistingBrEdrAliasAddress) {
EXPECT_TRUE(NewPeer(kAddrBrEdr, true));
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
EXPECT_FALSE(cache()->AddBondedPeer(BondingData{.identifier = kId,
.address = kAddrLeAlias,
.name = {},
.le_pairing_data = data,
.bredr_link_key = {},
.bredr_services = {}}));
EXPECT_FALSE(bonded_callback_called());
}
TEST_F(PeerCacheTestBondingTest,
AddBondedBrEdrPeerFailsWithExistingLowEnergyAliasAddress) {
EXPECT_TRUE(NewPeer(kAddrLeAlias, true));
EXPECT_FALSE(cache()->AddBondedPeer(BondingData{.identifier = kId,
.address = kAddrBrEdr,
.name = {},
.le_pairing_data = {},
.bredr_link_key = kBrEdrKey,
.bredr_services = {}}));
EXPECT_FALSE(bonded_callback_called());
}
TEST_F(PeerCacheTestBondingTest, AddBondedPeerFailsWithoutMandatoryKeys) {
sm::PairingData data;
EXPECT_FALSE(cache()->AddBondedPeer(BondingData{.identifier = kId,
.address = kAddrLeAlias,
.name = {},
.le_pairing_data = data,
.bredr_link_key = kBrEdrKey,
.bredr_services = {}}));
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
EXPECT_FALSE(cache()->AddBondedPeer(BondingData{.identifier = kId,
.address = kAddrBrEdr,
.name = {},
.le_pairing_data = data,
.bredr_link_key = {},
.bredr_services = {}}));
EXPECT_FALSE(bonded_callback_called());
}
TEST_F(PeerCacheTestBondingTest, AddLowEnergyBondedPeerSuccess) {
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
EXPECT_TRUE(cache()->AddBondedPeer(BondingData{.identifier = kId,
.address = kAddrLeRandom,
.name = kName,
.le_pairing_data = data,
.bredr_link_key = {},
.bredr_services = {}}));
auto* peer = cache()->FindById(kId);
ASSERT_TRUE(peer);
EXPECT_EQ(peer, cache()->FindByAddress(kAddrLeRandom));
EXPECT_EQ(kId, peer->identifier());
EXPECT_EQ(kAddrLeRandom, peer->address());
EXPECT_EQ(kName, peer->name());
EXPECT_EQ(Peer::NameSource::kUnknown, peer->name_source());
EXPECT_TRUE(peer->identity_known());
ASSERT_TRUE(peer->le());
EXPECT_TRUE(peer->le()->bonded());
ASSERT_TRUE(peer->le()->bond_data());
EXPECT_EQ(data, *peer->le()->bond_data());
EXPECT_FALSE(peer->bredr());
EXPECT_EQ(TechnologyType::kLowEnergy, peer->technology());
// The "new bond" callback should not be called when restoring a previously
// bonded peer.
EXPECT_FALSE(bonded_callback_called());
}
TEST_F(PeerCacheTestBondingTest, AddBrEdrBondedPeerSuccess) {
PeerId kId(5);
sm::PairingData data;
EXPECT_TRUE(
cache()->AddBondedPeer(BondingData{.identifier = kId,
.address = kAddrBrEdr,
.name = {},
.le_pairing_data = data,
.bredr_link_key = kBrEdrKey,
.bredr_services = kBrEdrServices}));
auto* peer = cache()->FindById(kId);
ASSERT_TRUE(peer);
EXPECT_EQ(peer, cache()->FindByAddress(kAddrBrEdr));
EXPECT_EQ(kId, peer->identifier());
EXPECT_EQ(kAddrBrEdr, peer->address());
ASSERT_FALSE(peer->name());
EXPECT_TRUE(peer->identity_known());
ASSERT_TRUE(peer->bredr());
EXPECT_TRUE(peer->bredr()->bonded());
ASSERT_TRUE(peer->bredr()->link_key());
EXPECT_EQ(kBrEdrKey, *peer->bredr()->link_key());
EXPECT_THAT(peer->bredr()->services(),
testing::UnorderedElementsAreArray(kBrEdrServices));
EXPECT_FALSE(peer->le());
EXPECT_EQ(TechnologyType::kClassic, peer->technology());
// The "new bond" callback should not be called when restoring a previously
// bonded peer.
EXPECT_FALSE(bonded_callback_called());
}
TEST_F(PeerCacheTest, AddBondedPeerWithIrkIsAddedToResolvingList) {
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>());
data.identity_address = kAddrLeRandom;
EXPECT_TRUE(cache()->AddBondedPeer(BondingData{.identifier = kId,
.address = kAddrLeRandom,
.name = {},
.le_pairing_data = data,
.bredr_link_key = {},
.bredr_services = {}}));
auto* peer = cache()->FindByAddress(kAddrLeRandom);
ASSERT_TRUE(peer);
EXPECT_EQ(kAddrLeRandom, peer->address());
// Looking up the peer by RPA generated using the IRK should return the same
// peer.
DeviceAddress rpa = sm::util::GenerateRpa(data.irk->value());
EXPECT_EQ(peer, cache()->FindByAddress(rpa));
}
TEST_F(PeerCacheTest, AddBondedPeerWithIrkButWithoutIdentityAddressPanics) {
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>());
EXPECT_DEATH_IF_SUPPORTED(
cache()->AddBondedPeer(BondingData{.identifier = kId,
.address = kAddrLeRandom,
.name = {},
.le_pairing_data = data,
.bredr_link_key = {},
.bredr_services = {}}),
".*identity_address.*");
}
TEST_F(PeerCacheTest,
StoreLowEnergyBondWithIrkButWithoutIdentityAddressPanics) {
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>());
EXPECT_DEATH_IF_SUPPORTED(cache()->StoreLowEnergyBond(kId, data),
".*identity_address.*");
}
TEST_F(PeerCacheTestBondingTest, StoreLowEnergyBondFailsWithNoKeys) {
sm::PairingData data;
EXPECT_FALSE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
}
TEST_F(PeerCacheTestBondingTest, StoreLowEnergyBondPeerUnknown) {
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
EXPECT_FALSE(cache()->StoreLowEnergyBond(kId, data));
}
TEST_F(PeerCacheTestBondingTest, StoreLowEnergyBondWithLtk) {
ASSERT_TRUE(peer()->temporary());
ASSERT_TRUE(peer()->le());
ASSERT_FALSE(peer()->le()->bonded());
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
EXPECT_TRUE(bonded_callback_called());
EXPECT_FALSE(peer()->temporary());
EXPECT_TRUE(peer()->le()->bonded());
EXPECT_TRUE(peer()->le()->bond_data());
EXPECT_EQ(data, *peer()->le()->bond_data());
}
TEST_F(PeerCacheTestBondingTest, StoreLowEnergyBondWithCsrk) {
ASSERT_TRUE(peer()->temporary());
ASSERT_TRUE(peer()->le());
ASSERT_FALSE(peer()->le()->bonded());
sm::PairingData data;
data.csrk = kKey;
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
EXPECT_TRUE(bonded_callback_called());
EXPECT_FALSE(peer()->temporary());
EXPECT_TRUE(peer()->le()->bonded());
EXPECT_TRUE(peer()->le()->bond_data());
EXPECT_EQ(data, *peer()->le()->bond_data());
}
// StoreLowEnergyBond fails if it contains the address of a different,
// previously known peer.
TEST_F(PeerCacheTestBondingTest,
StoreLowEnergyBondWithExistingDifferentIdentity) {
auto* p = cache()->NewPeer(kAddrLeRandom, /*connectable=*/true);
// Assign the other peer's address as identity.
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>());
data.identity_address = peer()->address();
EXPECT_FALSE(cache()->StoreLowEnergyBond(p->identifier(), data));
EXPECT_FALSE(p->le()->bonded());
EXPECT_TRUE(p->temporary());
}
// StoreLowEnergyBond fails if the new identity is the address of a "different"
// (another peer record with a distinct ID) BR/EDR peer.
TEST_F(PeerCacheTestBondingTest,
StoreLowEnergyBondWithNewIdentityMatchingExistingBrEdrPeer) {
ASSERT_TRUE(NewPeer(kAddrBrEdr, true));
ASSERT_TRUE(NewPeer(kAddrLeRandom, true));
ASSERT_FALSE(peer()->identity_known());
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>());
// new identity address is same as another peer's BR/EDR identity
data.identity_address = kAddrLeAlias;
const auto old_address = peer()->address();
ASSERT_EQ(peer(), cache()->FindByAddress(old_address));
ASSERT_NE(peer(), cache()->FindByAddress(*data.identity_address));
EXPECT_FALSE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
EXPECT_FALSE(peer()->identity_known());
}
// StoreLowEnergyBond succeeds if it contains an identity address that already
// matches the target peer.
TEST_F(PeerCacheTestBondingTest,
StoreLowEnergyBondWithExistingMatchingIdentity) {
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>());
data.identity_address = peer()->address();
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
EXPECT_TRUE(peer()->le()->bonded());
EXPECT_EQ(peer(), cache()->FindByAddress(*data.identity_address));
}
TEST_F(PeerCacheTestBondingTest, StoreLowEnergyBondWithNewIdentity) {
ASSERT_TRUE(NewPeer(kAddrLeRandom, true));
ASSERT_FALSE(peer()->identity_known());
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>());
data.identity_address = kAddrLeRandom2; // assign a new identity address
const auto old_address = peer()->address();
ASSERT_EQ(peer(), cache()->FindByAddress(old_address));
ASSERT_EQ(nullptr, cache()->FindByAddress(*data.identity_address));
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
EXPECT_TRUE(peer()->le()->bonded());
// Address should have been updated.
ASSERT_NE(*data.identity_address, old_address);
EXPECT_EQ(*data.identity_address, peer()->address());
EXPECT_TRUE(peer()->identity_known());
EXPECT_EQ(peer(), cache()->FindByAddress(*data.identity_address));
// The old address should still map to |peer|.
ASSERT_EQ(peer(), cache()->FindByAddress(old_address));
}
TEST_F(PeerCacheTestBondingTest,
StoreLowEnergyBondWithIrkIsAddedToResolvingList) {
ASSERT_TRUE(NewPeer(kAddrLeRandom, true));
ASSERT_FALSE(peer()->identity_known());
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.identity_address = kAddrLeRandom;
data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>());
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
ASSERT_TRUE(peer()->le()->bonded());
ASSERT_TRUE(peer()->identity_known());
// Looking up the peer by RPA generated using the IRK should return the same
// peer.
DeviceAddress rpa = sm::util::GenerateRpa(data.irk->value());
EXPECT_EQ(peer(), cache()->FindByAddress(rpa));
}
TEST_F(PeerCacheTestBondingTest, RemovingPeerRemovesIrkFromResolvingList) {
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.identity_address = kAddrLePublic;
data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>());
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
// Removing peer should remove IRK from resolving list, allowing a new peer to
// be created with an RPA corresponding to the removed IRK. Because the
// resolving list is empty, FindByAddress should look up the peer by the RPA
// address, not the resolved address, and return the new peer.
EXPECT_TRUE(cache()->RemoveDisconnectedPeer(peer()->identifier()));
DeviceAddress rpa = sm::util::GenerateRpa(data.irk->value());
EXPECT_EQ(nullptr, cache()->FindByAddress(rpa));
ASSERT_TRUE(NewPeer(rpa, true));
EXPECT_EQ(peer(), cache()->FindByAddress(rpa));
// Subsequent calls to create a peer with the same RPA should fail.
EXPECT_FALSE(NewPeer(rpa, true));
}
TEST_F(PeerCacheTestBondingTest, StoreLowEnergyBondWithXTransportKeyNoBrEdr) {
// There's no preexisting BR/EDR data, the LE peer already exists.
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.cross_transport_key = kSecureBrEdrKey;
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
EXPECT_TRUE(peer()->le()->bonded());
// Storing an LE bond with a cross-transport BR/EDR key shouldn't
// automatically mark the peer as dual-mode.
EXPECT_FALSE(peer()->bredr().has_value());
// Make the peer dual-mode, and verify that the peer is already bonded over
// BR/EDR with the stored cross-transport key.
peer()->MutBrEdr();
EXPECT_TRUE(peer()->bredr()->bonded());
EXPECT_EQ(kSecureBrEdrKey, peer()->bredr()->link_key().value());
}
TEST_F(PeerCacheTestBondingTest,
StoreLowEnergyBondWithInsecureXTransportKeyExistingBrEdr) {
// The peer is already dual-mode with a secure BR/EDR key.
peer()->MutBrEdr().SetBondData(kSecureBrEdrKey);
EXPECT_TRUE(peer()->bredr()->bonded());
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.cross_transport_key = kInsecureBrEdrKey;
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
// Verify that the existing BR/EDR key is not overwritten by a key of lesser
// security
sm::LTK current_bredr_key = peer()->bredr()->link_key().value();
EXPECT_NE(kInsecureBrEdrKey, current_bredr_key);
EXPECT_EQ(kSecureBrEdrKey, current_bredr_key);
}
TEST_F(PeerCacheTestBondingTest,
StoreLowEnergyBondWithXTransportKeyExistingBrEdr) {
// The peer is already dual-mode with an insecure BR/EDR key.
peer()->MutBrEdr().SetBondData(kInsecureBrEdrKey);
EXPECT_TRUE(peer()->bredr()->bonded());
sm::LTK kDifferentInsecureBrEdrKey(kInsecureBrEdrKey.security(),
hci_spec::LinkKey(UInt128{8}, 9, 10));
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
data.cross_transport_key = kDifferentInsecureBrEdrKey;
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
// Verify that the existing BR/EDR key is overwritten by a key of the same
// security ("if the key
// [...] already exists, then the devices shall not overwrite that existing
// key with a key that is weaker" v5.2 Vol. 3 Part C 14.1).
sm::LTK current_bredr_key = peer()->bredr()->link_key().value();
EXPECT_NE(kInsecureBrEdrKey, current_bredr_key);
EXPECT_EQ(kDifferentInsecureBrEdrKey, current_bredr_key);
// Verify that the existing BR/EDR key is also overwritten by a key of greater
// security.
data.cross_transport_key = kSecureBrEdrKey;
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
current_bredr_key = peer()->bredr()->link_key().value();
EXPECT_NE(kDifferentInsecureBrEdrKey, current_bredr_key);
EXPECT_EQ(kSecureBrEdrKey, current_bredr_key);
}
TEST_F(PeerCacheTestBondingTest, StoreBrEdrBondWithUnknownAddress) {
ASSERT_EQ(nullptr, cache()->FindByAddress(kAddrBrEdr));
EXPECT_FALSE(cache()->StoreBrEdrBond(kAddrBrEdr, kBrEdrKey));
}
TEST_F(PeerCacheTestBondingTest, StoreBrEdrBond) {
ASSERT_TRUE(NewPeer(kAddrBrEdr, true));
ASSERT_EQ(peer(), cache()->FindByAddress(kAddrBrEdr));
ASSERT_TRUE(peer()->temporary());
ASSERT_FALSE(peer()->bonded());
ASSERT_TRUE(peer()->bredr());
ASSERT_FALSE(peer()->bredr()->bonded());
EXPECT_TRUE(cache()->StoreBrEdrBond(kAddrBrEdr, kBrEdrKey));
EXPECT_FALSE(peer()->temporary());
EXPECT_TRUE(peer()->bonded());
EXPECT_TRUE(peer()->bredr()->bonded());
EXPECT_TRUE(peer()->bredr()->link_key());
EXPECT_EQ(kBrEdrKey, *peer()->bredr()->link_key());
}
TEST_F(PeerCacheTestBondingTest, StoreBondsForBothTech) {
ASSERT_TRUE(NewPeer(kAddrBrEdr, true));
ASSERT_EQ(peer(), cache()->FindByAddress(kAddrBrEdr));
ASSERT_TRUE(peer()->temporary());
ASSERT_FALSE(peer()->bonded());
peer()->MutLe().SetAdvertisingData(
kTestRSSI, kAdvData, pw::chrono::SystemClock::time_point());
ASSERT_EQ(TechnologyType::kDualMode, peer()->technology());
// Without Secure Connections cross-transport key generation, bonding on one
// technology does not bond on the other.
ASSERT_FALSE(kBrEdrKey.security().secure_connections());
EXPECT_TRUE(cache()->StoreBrEdrBond(kAddrBrEdr, kBrEdrKey));
EXPECT_TRUE(peer()->bonded());
EXPECT_FALSE(peer()->le()->bonded());
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
EXPECT_TRUE(cache()->StoreLowEnergyBond(peer()->identifier(), data));
EXPECT_FALSE(peer()->temporary());
EXPECT_TRUE(peer()->bonded());
EXPECT_TRUE(peer()->bredr()->bonded());
EXPECT_TRUE(peer()->le()->bonded());
}
TEST_F(PeerCacheTestBondingTest, BondsUpdatedWhenNewServicesAdded) {
ASSERT_TRUE(NewPeer(kAddrBrEdr, true));
ASSERT_EQ(peer(), cache()->FindByAddress(kAddrBrEdr));
ASSERT_FALSE(peer()->bonded());
ASSERT_FALSE(kBrEdrKey.security().secure_connections());
EXPECT_TRUE(cache()->StoreBrEdrBond(kAddrBrEdr, kBrEdrKey));
EXPECT_TRUE(peer()->bredr()->bonded());
EXPECT_EQ(1, bonded_callback_count());
peer()->MutBrEdr().AddService(UUID());
EXPECT_EQ(2, bonded_callback_count());
}
TEST_F(PeerCacheTestBondingTest, RemoveDisconnectedPeerOnUnknownPeer) {
const PeerId id(0x9999);
ASSERT_FALSE(cache()->FindById(id));
EXPECT_TRUE(cache()->RemoveDisconnectedPeer(id));
EXPECT_EQ(0, updated_callback_count());
}
TEST_F(PeerCacheTestBondingTest, RemoveDisconnectedPeerOnUnconnectedPeer) {
ASSERT_FALSE(peer()->connected());
const PeerId id = peer()->identifier();
EXPECT_TRUE(cache()->RemoveDisconnectedPeer(id));
EXPECT_EQ(1, removed_callback_count());
EXPECT_FALSE(cache()->FindById(id));
}
TEST_F(PeerCacheTestBondingTest, RemoveDisconnectedPeerOnConnectedPeer) {
Peer::ConnectionToken conn_token = peer()->MutLe().RegisterConnection();
ASSERT_TRUE(peer()->connected());
const PeerId id = peer()->identifier();
EXPECT_FALSE(cache()->RemoveDisconnectedPeer(id));
EXPECT_EQ(0, removed_callback_count());
EXPECT_TRUE(cache()->FindById(id));
}
// Fixture parameterized by peer address
class DualModeBondingTest
: public PeerCacheTestBondingTest,
public ::testing::WithParamInterface<DeviceAddress> {};
TEST_P(DualModeBondingTest, AddBondedPeerSuccess) {
PeerId kId(5);
sm::PairingData data;
data.peer_ltk = kLTK;
data.local_ltk = kLTK;
const DeviceAddress& address = GetParam();
EXPECT_TRUE(
cache()->AddBondedPeer(BondingData{.identifier = kId,
.address = address,
.name = kName,
.le_pairing_data = data,
.bredr_link_key = kBrEdrKey,
.bredr_services = kBrEdrServices}));
auto* peer = cache()->FindById(kId);
ASSERT_TRUE(peer);
EXPECT_EQ(peer, cache()->FindByAddress(kAddrLeAlias));
EXPECT_EQ(peer, cache()->FindByAddress(kAddrBrEdr));
EXPECT_EQ(kId, peer->identifier());
EXPECT_EQ(address, peer->address());
EXPECT_EQ(kName, peer->name());
EXPECT_EQ(Peer::NameSource::kUnknown, peer->name_source());
EXPECT_TRUE(peer->identity_known());
EXPECT_TRUE(peer->bonded());
ASSERT_TRUE(peer->le());
EXPECT_TRUE(peer->le()->bonded());
ASSERT_TRUE(peer->le()->bond_data());
EXPECT_EQ(data, *peer->le()->bond_data());
ASSERT_TRUE(peer->bredr());
EXPECT_TRUE(peer->bredr()->bonded());
ASSERT_TRUE(peer->bredr()->link_key());
EXPECT_EQ(kBrEdrKey, *peer->bredr()->link_key());
EXPECT_THAT(peer->bredr()->services(),
testing::UnorderedElementsAreArray(kBrEdrServices));
EXPECT_EQ(TechnologyType::kDualMode, peer->technology());
// The "new bond" callback should not be called when restoring a previously
// bonded peer.
EXPECT_FALSE(bonded_callback_called());
}
// Test dual-mode character of peer using the same address of both types.
INSTANTIATE_TEST_SUITE_P(PeerCacheTest,
DualModeBondingTest,
::testing::Values(kAddrBrEdr, kAddrLeAlias));
template <const DeviceAddress* DevAddr>
class PeerCacheTest_UpdateCallbackTest : public PeerCacheTest {
public:
void SetUp() override {
PeerCacheTest::SetUp();
was_called_ = false;
ASSERT_TRUE(NewPeer(*DevAddr, true));
cache()->add_peer_updated_callback(
[this](const auto&) { was_called_ = true; });
ir_.view().bd_addr().CopyFrom(peer()->address().value().view());
irr_.view().bd_addr().CopyFrom(peer()->address().value().view());
extended_inquiry_result_event_.view().bd_addr().CopyFrom(
peer()->address().value().view());
eir_data().SetToZeros();
EXPECT_FALSE(was_called_);
}
void TearDown() override { PeerCacheTest::TearDown(); }
protected:
pw::bluetooth::emboss::InquiryResultWriter ir() { return ir_.view(); }
pw::bluetooth::emboss::InquiryResultWithRssiWriter irr() {
return irr_.view();
}
pw::bluetooth::emboss::ExtendedInquiryResultEventWriter
extended_inquiry_result_event() {
return extended_inquiry_result_event_.view();
}
MutableBufferView eir_data() {
return MutableBufferView(extended_inquiry_result_event_.view()
.extended_inquiry_response()
.BackingStorage()
.data(),
extended_inquiry_result_event_.view()
.extended_inquiry_response()
.SizeInBytes());
}
bool was_called() const { return was_called_; }
void ClearWasCalled() { was_called_ = false; }
private:
bool was_called_;
StaticPacket<pw::bluetooth::emboss::InquiryResultWriter> ir_;
StaticPacket<pw::bluetooth::emboss::InquiryResultWithRssiWriter> irr_;
StaticPacket<pw::bluetooth::emboss::ExtendedInquiryResultEventWriter>
extended_inquiry_result_event_;
};
using PeerCacheBrEdrUpdateCallbackTest =
PeerCacheTest_UpdateCallbackTest<&kAddrBrEdr>;
using PeerCacheLowEnergyUpdateCallbackTest =
PeerCacheTest_UpdateCallbackTest<&kAddrLeAlias>;
TEST_F(PeerCacheLowEnergyUpdateCallbackTest,
ChangingLEConnectionStateTriggersUpdateCallback) {
Peer::ConnectionToken conn_token = peer()->MutLe().RegisterConnection();
EXPECT_TRUE(was_called());
}
TEST_F(PeerCacheLowEnergyUpdateCallbackTest,
SetAdvertisingDataTriggersUpdateCallbackOnNameSet) {
peer()->MutLe().SetAdvertisingData(
kTestRSSI, kAdvData, pw::chrono::SystemClock::time_point());
EXPECT_TRUE(was_called());
ASSERT_TRUE(peer()->name());
EXPECT_EQ("Test", *peer()->name());
EXPECT_EQ(Peer::NameSource::kAdvertisingDataComplete, *peer()->name_source());
}
TEST_F(PeerCacheLowEnergyUpdateCallbackTest,
SetLowEnergyAdvertisingDataUpdateCallbackProvidesUpdatedPeer) {
ASSERT_NE(peer()->rssi(), kTestRSSI);
cache()->add_peer_updated_callback([&](const auto& updated_peer) {
ASSERT_TRUE(updated_peer.le());
ASSERT_TRUE(updated_peer.name().has_value());
EXPECT_EQ(updated_peer.name().value(), "Test");
EXPECT_EQ(updated_peer.rssi(), kTestRSSI);
});
peer()->MutLe().SetAdvertisingData(
kTestRSSI, kAdvData, pw::chrono::SystemClock::time_point());
}
TEST_F(PeerCacheLowEnergyUpdateCallbackTest,
SetAdvertisingDataTriggersUpdateCallbackOnSameNameAndRssi) {
peer()->MutLe().SetAdvertisingData(
kTestRSSI, kAdvData, pw::chrono::SystemClock::time_point());
ASSERT_TRUE(was_called());
ClearWasCalled();
peer()->MutLe().SetAdvertisingData(
kTestRSSI, kAdvData, pw::chrono::SystemClock::time_point());
EXPECT_TRUE(was_called());
}
TEST_F(PeerCacheLowEnergyUpdateCallbackTest,
SetLowEnergyConnectionParamsDoesNotTriggerUpdateCallback) {
peer()->MutLe().SetConnectionParameters({});
EXPECT_FALSE(was_called());
}
TEST_F(PeerCacheLowEnergyUpdateCallbackTest,
SetLowEnergyPreferredConnectionParamsDoesNotTriggerUpdateCallback) {
peer()->MutLe().SetPreferredConnectionParameters({});
EXPECT_FALSE(was_called());
}
TEST_F(PeerCacheLowEnergyUpdateCallbackTest,
BecomingDualModeTriggersUpdateCallBack) {
EXPECT_EQ(TechnologyType::kLowEnergy, peer()->technology());
size_t call_count = 0;
cache()->add_peer_updated_callback([&](const auto&) { ++call_count; });
peer()->MutBrEdr();
EXPECT_EQ(TechnologyType::kDualMode, peer()->technology());
EXPECT_EQ(call_count, 1U);
// Calling MutBrEdr again on doesn't trigger additional callbacks.
peer()->MutBrEdr();
EXPECT_EQ(call_count, 1U);
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
EXPECT_EQ(call_count, 2U);
}
TEST_F(PeerCacheBrEdrUpdateCallbackTest,
ChangingBrEdrConnectionStateTriggersUpdateCallback) {
Peer::ConnectionToken token = peer()->MutBrEdr().RegisterConnection();
EXPECT_TRUE(was_called());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromInquiryResultTriggersUpdateCallbackOnPeerClassSet) {
ir().class_of_device().BackingStorage().WriteUInt(kTestDeviceClass.to_int());
peer()->MutBrEdr().SetInquiryData(ir());
EXPECT_TRUE(was_called());
}
TEST_F(PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromInquiryResultUpdateCallbackProvidesUpdatedPeer) {
ir().class_of_device().BackingStorage().WriteUInt(kTestDeviceClass.to_int());
cache()->add_peer_updated_callback([](const auto& updated_peer) {
ASSERT_TRUE(updated_peer.bredr());
ASSERT_TRUE(updated_peer.bredr()->device_class());
EXPECT_EQ(DeviceClass::MajorClass(0x02),
updated_peer.bredr()->device_class()->major_class());
});
peer()->MutBrEdr().SetInquiryData(ir());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromInquiryResultDoesNotTriggerUpdateCallbackOnSameDeviceClass) {
ir().class_of_device().BackingStorage().WriteUInt(kTestDeviceClass.to_int());
peer()->MutBrEdr().SetInquiryData(ir());
ASSERT_TRUE(was_called());
ClearWasCalled();
peer()->MutBrEdr().SetInquiryData(ir());
EXPECT_FALSE(was_called());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromInquiryResultRSSITriggersUpdateCallbackOnDeviceClassSet) {
irr().class_of_device().major_device_class().Write(
pw::bluetooth::emboss::MajorDeviceClass::PHONE);
peer()->MutBrEdr().SetInquiryData(irr());
EXPECT_TRUE(was_called());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromInquiryResultRSSIUpdateCallbackProvidesUpdatedPeer) {
irr().class_of_device().major_device_class().Write(
pw::bluetooth::emboss::MajorDeviceClass::PHONE);
cache()->add_peer_updated_callback([](const auto& updated_peer) {
ASSERT_TRUE(updated_peer.bredr()->device_class());
EXPECT_EQ(DeviceClass::MajorClass::kPhone,
updated_peer.bredr()->device_class()->major_class());
});
peer()->MutBrEdr().SetInquiryData(irr());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromInquiryResultRSSIDoesNotTriggerUpdateCallbackOnSameDeviceClass) {
irr().class_of_device().major_device_class().Write(
pw::bluetooth::emboss::MajorDeviceClass::PHONE);
peer()->MutBrEdr().SetInquiryData(irr());
ASSERT_TRUE(was_called());
ClearWasCalled();
peer()->MutBrEdr().SetInquiryData(irr());
EXPECT_FALSE(was_called());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromInquiryResultRSSIDoesNotTriggerUpdateCallbackOnRSSI) {
irr().rssi().Write(1);
peer()->MutBrEdr().SetInquiryData(irr());
ASSERT_TRUE(was_called()); // Callback due to |class_of_device|.
ClearWasCalled();
irr().rssi().Write(20);
peer()->MutBrEdr().SetInquiryData(irr());
EXPECT_FALSE(was_called());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromExtendedInquiryResultEventParamsTriggersUpdateCallbackOnDeviceClassSet) {
extended_inquiry_result_event().class_of_device().major_device_class().Write(
pw::bluetooth::emboss::MajorDeviceClass::PHONE);
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
EXPECT_TRUE(was_called());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromExtendedInquiryResultEventParamsTriggersUpdateCallbackOnNameSet) {
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
ASSERT_TRUE(was_called()); // Callback due to |class_of_device|.
ClearWasCalled();
eir_data().Write(kEirData);
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
EXPECT_TRUE(was_called());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromExtendedInquiryResultEventParamsUpdateCallbackProvidesUpdatedPeer) {
extended_inquiry_result_event().clock_offset().valid().Write(true);
extended_inquiry_result_event().clock_offset().clock_offset().Write(1);
extended_inquiry_result_event().page_scan_repetition_mode().Write(
pw::bluetooth::emboss::PageScanRepetitionMode::R1_);
extended_inquiry_result_event().rssi().Write(kTestRSSI);
extended_inquiry_result_event().class_of_device().major_device_class().Write(
pw::bluetooth::emboss::MajorDeviceClass::PHONE);
eir_data().Write(kEirData);
ASSERT_FALSE(peer()->name().has_value());
ASSERT_EQ(peer()->rssi(), hci_spec::kRSSIInvalid);
cache()->add_peer_updated_callback([](const auto& updated_peer) {
const auto& data = updated_peer.bredr();
ASSERT_TRUE(data);
ASSERT_TRUE(data->clock_offset().has_value());
ASSERT_TRUE(data->page_scan_repetition_mode().has_value());
ASSERT_TRUE(data->device_class().has_value());
ASSERT_TRUE(updated_peer.name().has_value());
EXPECT_EQ(*data->clock_offset(), 0x01);
EXPECT_EQ(*data->page_scan_repetition_mode(),
pw::bluetooth::emboss::PageScanRepetitionMode::R1_);
EXPECT_EQ(DeviceClass::MajorClass(0x02),
updated_peer.bredr()->device_class()->major_class());
EXPECT_EQ(updated_peer.rssi(), kTestRSSI);
EXPECT_EQ(*updated_peer.name(), "Test");
EXPECT_EQ(*updated_peer.name_source(),
Peer::NameSource::kInquiryResultComplete);
});
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromExtendedInquiryResultEventParamsGeneratesExactlyOneUpdateCallbackRegardlessOfNumberOfFieldsChanged) {
extended_inquiry_result_event().clock_offset().valid().Write(true);
extended_inquiry_result_event().clock_offset().clock_offset().Write(1);
extended_inquiry_result_event().page_scan_repetition_mode().Write(
pw::bluetooth::emboss::PageScanRepetitionMode::R1_);
extended_inquiry_result_event().rssi().Write(kTestRSSI);
extended_inquiry_result_event().class_of_device().major_device_class().Write(
pw::bluetooth::emboss::MajorDeviceClass::PHONE);
size_t call_count = 0;
cache()->add_peer_updated_callback([&](const auto&) { ++call_count; });
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
EXPECT_EQ(call_count, 1U);
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromExtendedInquiryResultEventParamsDoesNotTriggerUpdateCallbackOnSamePeerClass) {
extended_inquiry_result_event().class_of_device().major_device_class().Write(
pw::bluetooth::emboss::MajorDeviceClass::PHONE);
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
ASSERT_TRUE(was_called());
ClearWasCalled();
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
EXPECT_FALSE(was_called());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromExtendedInquiryResultEventParamsDoesNotTriggerUpdateCallbackOnSameName) {
eir_data().Write(kEirData);
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
ASSERT_TRUE(was_called());
ClearWasCalled();
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
EXPECT_FALSE(was_called());
}
TEST_F(
PeerCacheBrEdrUpdateCallbackTest,
SetBrEdrInquiryDataFromExtendedInquiryResultEventParamsDoesNotTriggerUpdateCallbackOnRSSI) {
extended_inquiry_result_event().rssi().Write(1);
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
ASSERT_TRUE(was_called()); // Callback due to |class_of_device|.
ClearWasCalled();
extended_inquiry_result_event().rssi().Write(20);
peer()->MutBrEdr().SetInquiryData(extended_inquiry_result_event());
EXPECT_FALSE(was_called());
}
TEST_F(PeerCacheBrEdrUpdateCallbackTest, RegisterNameTriggersUpdateCallback) {
peer()->RegisterName("nombre");
EXPECT_TRUE(was_called());
}
TEST_F(PeerCacheBrEdrUpdateCallbackTest,
RegisterNameDoesNotTriggerUpdateCallbackOnSameName) {
peer()->RegisterName("nombre");
ASSERT_TRUE(was_called());
bool was_called_again = false;
cache()->add_peer_updated_callback(
[&](const auto&) { was_called_again = true; });
peer()->RegisterName("nombre");
EXPECT_FALSE(was_called_again);
}
TEST_F(PeerCacheBrEdrUpdateCallbackTest,
BecomingDualModeTriggersUpdateCallBack) {
EXPECT_EQ(TechnologyType::kClassic, peer()->technology());
size_t call_count = 0;
cache()->add_peer_updated_callback([&](const auto&) { ++call_count; });
peer()->MutLe();
EXPECT_EQ(TechnologyType::kDualMode, peer()->technology());
EXPECT_EQ(call_count, 1U);
// Calling MutLe again doesn't trigger additional callbacks.
peer()->MutLe();
EXPECT_EQ(call_count, 1U);
peer()->MutLe().SetAdvertisingData(
kTestRSSI, kAdvData, pw::chrono::SystemClock::time_point());
EXPECT_EQ(call_count, 2U);
}
class PeerCacheExpirationTest : public pw::async::test::FakeDispatcherFixture {
public:
PeerCacheExpirationTest() = default;
void SetUp() override {
cache_.set_peer_removed_callback([this](PeerId) { peers_removed_++; });
auto* peer = cache_.NewPeer(kAddrLeAlias, /*connectable=*/true);
ASSERT_TRUE(peer);
ASSERT_TRUE(peer->temporary());
peer_addr_ = peer->address();
peer_addr_alias_ = kAddrBrEdr;
peer_id_ = peer->identifier();
peers_removed_ = 0;
}
void TearDown() override {
cache_.set_peer_removed_callback(nullptr);
RunUntilIdle();
}
Peer* GetDefaultPeer() { return cache_.FindById(peer_id_); }
Peer* GetPeerById(PeerId id) { return cache_.FindById(id); }
bool IsDefaultPeerAddressInCache() const {
return cache_.FindByAddress(peer_addr_);
}
bool IsOtherTransportAddressInCache() const {
return cache_.FindByAddress(peer_addr_alias_);
}
bool IsDefaultPeerPresent() { return GetDefaultPeer(); }
Peer* NewPeer(const DeviceAddress& address, bool connectable) {
return cache_.NewPeer(address, connectable);
}
int peers_removed() const { return peers_removed_; }
private:
PeerCache cache_{dispatcher()};
DeviceAddress peer_addr_;
DeviceAddress peer_addr_alias_;
PeerId peer_id_;
int peers_removed_;
};
TEST_F(PeerCacheExpirationTest, TemporaryDiesSixtySecondsAfterBirth) {
RunFor(kCacheTimeout);
EXPECT_FALSE(IsDefaultPeerPresent());
EXPECT_EQ(1, peers_removed());
}
TEST_F(PeerCacheExpirationTest, TemporaryLivesForSixtySecondsAfterBirth) {
RunFor(kCacheTimeout - std::chrono::milliseconds(1));
EXPECT_TRUE(IsDefaultPeerPresent());
EXPECT_EQ(0, peers_removed());
}
TEST_F(PeerCacheExpirationTest, TemporaryLivesForSixtySecondsSinceLastSeen) {
RunFor(kCacheTimeout - std::chrono::milliseconds(1));
ASSERT_TRUE(IsDefaultPeerPresent());
// Tickle peer, and verify it sticks around for another cache timeout.
GetDefaultPeer()->RegisterName("nombre");
RunFor(kCacheTimeout - std::chrono::milliseconds(1));
EXPECT_TRUE(IsDefaultPeerPresent());
}
TEST_F(PeerCacheExpirationTest, TemporaryDiesSixtySecondsAfterLastSeen) {
RunFor(kCacheTimeout - std::chrono::milliseconds(1));
ASSERT_TRUE(IsDefaultPeerPresent());
// Tickle peer, and verify it expires after cache timeout.
GetDefaultPeer()->RegisterName("nombre");
RunFor(kCacheTimeout);
EXPECT_FALSE(IsDefaultPeerPresent());
}
TEST_F(PeerCacheExpirationTest, CanMakeNonTemporaryJustBeforeSixtySeconds) {
// At last possible moment, make peer non-temporary,
RunFor(kCacheTimeout - std::chrono::milliseconds(1));
ASSERT_TRUE(IsDefaultPeerPresent());
Peer::ConnectionToken conn_token =
GetDefaultPeer()->MutLe().RegisterConnection();
ASSERT_FALSE(GetDefaultPeer()->temporary());
// Verify that the peer survives.
RunFor(kCacheTimeout * 10);
EXPECT_TRUE(IsDefaultPeerPresent());
}
TEST_F(PeerCacheExpirationTest, LEConnectedPeerLivesMuchMoreThanSixtySeconds) {
ASSERT_TRUE(IsDefaultPeerPresent());
Peer::ConnectionToken conn_token =
GetDefaultPeer()->MutLe().RegisterConnection();
RunFor(kCacheTimeout * 10);
ASSERT_TRUE(IsDefaultPeerPresent());
EXPECT_FALSE(GetDefaultPeer()->temporary());
}
TEST_F(PeerCacheExpirationTest,
BREDRConnectedPeerLivesMuchMoreThanSixtySeconds) {
ASSERT_TRUE(IsDefaultPeerPresent());
Peer::ConnectionToken token =
GetDefaultPeer()->MutBrEdr().RegisterConnection();
RunFor(kCacheTimeout * 10);
ASSERT_TRUE(IsDefaultPeerPresent());
EXPECT_FALSE(GetDefaultPeer()->temporary());
}
TEST_F(PeerCacheExpirationTest, LePeerBecomesNonTemporaryWhenConnecting) {
ASSERT_TRUE(IsDefaultPeerPresent());
ASSERT_EQ(kAddrLeAlias, GetDefaultPeer()->address());
ASSERT_TRUE(GetDefaultPeer()->temporary());
Peer::InitializingConnectionToken init_token =
GetDefaultPeer()->MutLe().RegisterInitializingConnection();
EXPECT_FALSE(GetDefaultPeer()->temporary());
RunFor(kCacheTimeout);
ASSERT_TRUE(IsDefaultPeerPresent());
}
TEST_F(PeerCacheExpirationTest, LEPublicPeerRemainsNonTemporaryOnDisconnect) {
ASSERT_TRUE(IsDefaultPeerPresent());
ASSERT_EQ(kAddrLeAlias, GetDefaultPeer()->address());
{
Peer::ConnectionToken conn_token =
GetDefaultPeer()->MutLe().RegisterConnection();
ASSERT_FALSE(GetDefaultPeer()->temporary());
RunFor(kCacheTimeout + std::chrono::seconds(1));
ASSERT_TRUE(IsDefaultPeerPresent());
ASSERT_TRUE(GetDefaultPeer()->identity_known());
// Destroy conn_token at end of scope
}
EXPECT_FALSE(GetDefaultPeer()->temporary());
RunFor(kCacheTimeout);
EXPECT_TRUE(IsDefaultPeerPresent());
}
TEST_F(PeerCacheExpirationTest, LERandomPeerBecomesTemporaryOnDisconnect) {
// Create our Peer, and get it into the kConnected state.
PeerId custom_peer_id;
std::optional<Peer::ConnectionToken> conn_token;
{
auto* custom_peer = NewPeer(kAddrLeRandom, /*connectable=*/true);
ASSERT_TRUE(custom_peer);
ASSERT_TRUE(custom_peer->temporary());
ASSERT_FALSE(custom_peer->identity_known());
custom_peer_id = custom_peer->identifier();
conn_token = custom_peer->MutLe().RegisterConnection();
ASSERT_FALSE(custom_peer->temporary());
ASSERT_FALSE(custom_peer->identity_known());
}
// Verify that the connected peer does not expire out of the cache.
// Then disconnect the peer, in preparation for the next stage of our test.
{
EXPECT_EQ(0, peers_removed());
RunFor(std::chrono::seconds(61));
EXPECT_EQ(1, peers_removed()); // Default peer timed out.
auto* custom_peer = GetPeerById(custom_peer_id);
ASSERT_TRUE(custom_peer);
ASSERT_FALSE(custom_peer->identity_known());
conn_token.reset();
EXPECT_TRUE(custom_peer->temporary());
EXPECT_FALSE(custom_peer->identity_known());
}
// Verify that the disconnected peer expires out of the cache.
RunFor(std::chrono::seconds(61));
EXPECT_FALSE(GetPeerById(custom_peer_id));
EXPECT_EQ(2, peers_removed());
}
TEST_F(PeerCacheExpirationTest, BrEdrPeerRemainsNonTemporaryOnDisconnect) {
// Create our Peer, and get it into the kConnected state.
PeerId custom_peer_id;
std::optional<Peer::ConnectionToken> conn_token;
{
auto* custom_peer = NewPeer(kAddrLePublic, /*connectable=*/true);
ASSERT_TRUE(custom_peer);
conn_token = custom_peer->MutLe().RegisterConnection();
custom_peer_id = custom_peer->identifier();
}
// Verify that the connected peer does not expire out of the cache.
// Then disconnect the peer, in preparation for the next stage of our test.
{
EXPECT_EQ(0, peers_removed());
RunFor(kCacheTimeout * 10);
EXPECT_EQ(1, peers_removed()); // Default peer timed out.
auto* custom_peer = GetPeerById(custom_peer_id);
ASSERT_TRUE(custom_peer);
ASSERT_TRUE(custom_peer->identity_known());
EXPECT_FALSE(custom_peer->temporary());
conn_token.reset();
ASSERT_TRUE(GetPeerById(custom_peer_id));
EXPECT_FALSE(custom_peer->temporary());
}
// Verify that the disconnected peer does _not_ expire out of the cache.
// We expect the peer to remain, because BrEdr peers are non-temporary
// even when disconnected.
RunFor(kCacheTimeout);
EXPECT_TRUE(GetPeerById(custom_peer_id));
EXPECT_EQ(1, peers_removed());
}
TEST_F(PeerCacheExpirationTest, ExpirationUpdatesAddressMap) {
ASSERT_TRUE(IsDefaultPeerAddressInCache());
ASSERT_TRUE(IsOtherTransportAddressInCache());
RunFor(kCacheTimeout);
EXPECT_FALSE(IsDefaultPeerAddressInCache());
EXPECT_FALSE(IsOtherTransportAddressInCache());
}
TEST_F(PeerCacheExpirationTest, SetAdvertisingDataUpdatesExpiration) {
RunFor(kCacheTimeout - std::chrono::milliseconds(1));
ASSERT_TRUE(IsDefaultPeerPresent());
GetDefaultPeer()->MutLe().SetAdvertisingData(
kTestRSSI, StaticByteBuffer<1>{}, pw::chrono::SystemClock::time_point());
RunFor(kCacheTimeout - std::chrono::milliseconds(1));
EXPECT_TRUE(IsDefaultPeerPresent());
// Setting advertising data with the same rssi & name should also update the
// expiry.
GetDefaultPeer()->MutLe().SetAdvertisingData(
kTestRSSI, StaticByteBuffer<1>{}, pw::chrono::SystemClock::time_point());
RunFor(std::chrono::milliseconds(1));
EXPECT_TRUE(IsDefaultPeerPresent());
}
TEST_F(PeerCacheExpirationTest,
SetBrEdrInquiryDataFromInquiryResultUpdatesExpiration) {
StaticPacket<pw::bluetooth::emboss::InquiryResultWriter> ir;
ASSERT_TRUE(IsDefaultPeerPresent());
ir.view().bd_addr().CopyFrom(GetDefaultPeer()->address().value().view());
RunFor(kCacheTimeout - std::chrono::milliseconds(1));
ASSERT_TRUE(IsDefaultPeerPresent());
GetDefaultPeer()->MutBrEdr().SetInquiryData(ir.view());
RunFor(std::chrono::milliseconds(1));
EXPECT_TRUE(IsDefaultPeerPresent());
}
TEST_F(PeerCacheExpirationTest,
SetBrEdrInquiryDataFromInquiryResultRSSIUpdatesExpiration) {
StaticPacket<pw::bluetooth::emboss::InquiryResultWithRssiWriter> irr;
ASSERT_TRUE(IsDefaultPeerPresent());
irr.view().bd_addr().CopyFrom(GetDefaultPeer()->address().value().view());
RunFor(kCacheTimeout - std::chrono::milliseconds(1));
ASSERT_TRUE(IsDefaultPeerPresent());
GetDefaultPeer()->MutBrEdr().SetInquiryData(irr.view());
RunFor(std::chrono::milliseconds(1));
EXPECT_TRUE(IsDefaultPeerPresent());
}
TEST_F(
PeerCacheExpirationTest,
SetBrEdrInquiryDataFromExtendedInquiryResultEventParamsUpdatesExpiration) {
StaticPacket<pw::bluetooth::emboss::ExtendedInquiryResultEventWriter> event;
ASSERT_TRUE(IsDefaultPeerPresent());
event.view().bd_addr().CopyFrom(GetDefaultPeer()->address().value().view());
RunFor(kCacheTimeout - std::chrono::milliseconds(1));
ASSERT_TRUE(IsDefaultPeerPresent());
GetDefaultPeer()->MutBrEdr().SetInquiryData(event.view());
RunFor(std::chrono::milliseconds(1));
EXPECT_TRUE(IsDefaultPeerPresent());
}
TEST_F(PeerCacheExpirationTest, RegisterNameUpdatesExpiration) {
RunFor(kCacheTimeout - std::chrono::milliseconds(1));
ASSERT_TRUE(IsDefaultPeerPresent());
GetDefaultPeer()->RegisterName({});
RunFor(std::chrono::milliseconds(1));
EXPECT_TRUE(IsDefaultPeerPresent());
}
} // namespace
} // namespace bt::gap