[BLE Refactor] Implements DiscoveredPeripheralTracker.

PiperOrigin-RevId: 441524475
This commit is contained in:
edwinwu
2022-04-13 10:56:16 -07:00
committed by Copybara-Service
parent 3aa6b2f175
commit 3050f9432c
6 changed files with 1921 additions and 3 deletions
@@ -22,6 +22,7 @@ cc_library(
"ble_packet.cc",
"ble_utils.cc",
"bloom_filter.cc",
"discovered_peripheral_tracker.cc",
],
hdrs = [
"advertisement_read_result.h",
@@ -32,6 +33,7 @@ cc_library(
"ble_utils.h",
"bloom_filter.h",
"discovered_peripheral_callback.h",
"discovered_peripheral_tracker.h",
],
copts = ["-DCORE_ADAPTER_DLL"],
visibility = [
@@ -41,6 +43,7 @@ cc_library(
"//connections:core_types",
"//connections/implementation/mediums:utils",
"//internal/platform:base",
"//internal/platform:comm",
"//internal/platform:logging",
"//internal/platform:types",
"//internal/platform:util",
@@ -49,6 +52,7 @@ cc_library(
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/numeric:int128",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/time",
],
)
@@ -63,11 +67,13 @@ cc_test(
"ble_peripheral_test.cc",
"ble_utils_test.cc",
"bloom_filter_test.cc",
"discovered_peripheral_tracker_test.cc",
],
deps = [
":ble_v2",
"//internal/platform:base",
"//internal/platform:comm",
"//internal/platform:types",
"//internal/platform/implementation/g3", # buildcleaner: keep
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_absl//absl/hash:hash_testing",
@@ -34,6 +34,20 @@ namespace {
constexpr std::int64_t kAdvertisementUuidMsb = 0x0000000000003000;
constexpr std::int64_t kAdvertisementUuidLsb = 0x8000000000000000;
// Creates a string as a space separated listing of hex bytes with [] at the
// beginning and the end.
//
// This is the legacy input for hash in version (kV1). It is for testing only.
std::string StringToPrintableHexString(const std::string& source) {
// Print out the byte array as a space separated listing of hex bytes.
std::string out = "[ ";
for (const char& c : source) {
absl::StrAppend(&out, absl::StrFormat("%#04x ", c));
}
absl::StrAppend(&out, "]");
return out;
}
} // namespace
const absl::string_view kCopresenceServiceUuid =
@@ -43,8 +57,21 @@ ByteArray GenerateHash(const std::string& source, size_t size) {
return Utils::Sha256Hash(source, size);
}
ByteArray GenerateServiceIdHash(const std::string& service_id) {
return Utils::Sha256Hash(service_id, BlePacket::kServiceIdHashLength);
ByteArray GenerateServiceIdHash(const std::string& service_id,
BleAdvertisement::Version version) {
switch (version) {
// legacy hash for testing only.
case BleAdvertisement::Version::kV1:
return Utils::Sha256Hash(StringToPrintableHexString(service_id),
BlePacket::kServiceIdHashLength);
case BleAdvertisement::Version::kV2:
[[fallthrough]];
case BleAdvertisement::Version::kUndefined:
[[fallthrough]];
default:
// Use the latest known hashing scheme.
return Utils::Sha256Hash(service_id, BlePacket::kServiceIdHashLength);
}
}
ByteArray GenerateDeviceToken() {
@@ -17,6 +17,7 @@
#include <string>
#include "absl/strings/str_format.h"
#include "connections/implementation/mediums/ble_v2//ble_advertisement.h"
#include "connections/implementation/mediums/ble_v2/ble_advertisement_header.h"
#include "connections/implementation/mediums/ble_v2/ble_packet.h"
@@ -32,10 +33,34 @@ namespace bleutils {
ABSL_CONST_INIT extern const absl::string_view kCopresenceServiceUuid;
// Return SHA256 hash.
//
// source - the string to be hashed.
// size - size of returned byte array.
ByteArray GenerateHash(const std::string& source, size_t size);
ByteArray GenerateServiceIdHash(const std::string& service_id);
// Return SHA256 hash of service ID.
//
// source - service id.
// version - BleAdvertisement::Version. kV1 has been deprecated and just used
// for testing.
ByteArray GenerateServiceIdHash(
const std::string& service_id,
BleAdvertisement::Version version = BleAdvertisement::Version::kV2);
// Returns device token generated by SHA256 hash in random uint32 and
// size of mediums::BleAdvertisement::kDeviceTokenLength
ByteArray GenerateDeviceToken();
// Return SHA256 hash of advertisement byte array.
//
// advertisement_bytes - advertisement byte array.
// The generated hash size is determined by byte array size of input.
ByteArray GenerateAdvertisementHash(const ByteArray& advertisement_bytes);
// Generates a BLE characteristic UUID for an advertisement at the given slot.
//
// slot - the advertisement slot to generate a UUID for.
std::string GenerateAdvertisementUuid(int slot);
} // namespace bleutils
@@ -0,0 +1,638 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "connections/implementation/mediums/ble_v2/discovered_peripheral_tracker.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "absl/strings/escaping.h"
#include "connections/implementation/mediums/ble_v2/advertisement_read_result.h"
#include "connections/implementation/mediums/ble_v2/ble_advertisement.h"
#include "connections/implementation/mediums/ble_v2/ble_advertisement_header.h"
#include "connections/implementation/mediums/ble_v2/ble_utils.h"
#include "connections/implementation/mediums/ble_v2/bloom_filter.h"
#include "internal/platform/mutex_lock.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
void DiscoveredPeripheralTracker::StartTracking(
const std::string& service_id,
const DiscoveredPeripheralCallback& discovered_peripheral_callback,
const std::string& fast_advertisement_service_uuid) {
MutexLock lock(&mutex_);
ServiceIdInfo service_id_info = {
.discovered_peripheral_callback =
std::move(discovered_peripheral_callback),
.lost_entity_tracker =
absl::make_unique<LostEntityTracker<BleAdvertisement>>(),
.fast_advertisement_service_uuid = fast_advertisement_service_uuid};
// Replace if key exists.
service_id_infos_.insert_or_assign(service_id, std::move(service_id_info));
// Clear all of the GATT read results. With this cleared, we will now attempt
// to reconnect to every peripheral we see, giving us a chance to search for
// the new service we're now tracking.
// See the documentation of advertisementReadResult for more information.
advertisement_read_results_.clear();
// Remove stale data from any previous sessions.
ClearDataForServiceId(service_id);
}
void DiscoveredPeripheralTracker::StopTracking(const std::string& service_id) {
MutexLock lock(&mutex_);
service_id_infos_.erase(service_id);
}
void DiscoveredPeripheralTracker::ProcessFoundBleAdvertisement(
BleV2Peripheral peripheral,
const ::location::nearby::api::ble_v2::BleAdvertisementData&
advertisement_data,
AdvertisementFetcher advertisement_fetcher) {
MutexLock lock(&mutex_);
if (service_id_infos_.empty()) {
NEARBY_LOGS(INFO) << "Ignoring BLE advertisement header because we are not "
"tracking any service IDs.";
return;
}
if (!peripheral.IsValid() || advertisement_data.service_data.empty()) {
NEARBY_LOGS(INFO)
<< "Ignoring BLE advertisement header because the peripheral is "
"invalid or the given service data is empty.";
return;
}
HandleAdvertisement(peripheral, advertisement_data);
HandleAdvertisementHeader(advertisement_data, /*mutated=*/peripheral,
std::move(advertisement_fetcher));
}
void DiscoveredPeripheralTracker::ProcessLostGattAdvertisements() {
MutexLock lock(&mutex_);
for (const auto& it : service_id_infos_) {
const std::string& service_id = it.first;
const ServiceIdInfo& service_id_info = it.second;
DiscoveredPeripheralCallback discovered_peripheral_callback =
service_id_info.discovered_peripheral_callback;
BleAdvertisementSet lost_gatt_advertisements =
service_id_info.lost_entity_tracker->ComputeLostEntities();
// Clear the map state for each lost GATT advertisement and report it to the
// client.
for (const auto& gatt_advertisement : lost_gatt_advertisements) {
ClearGattAdvertisement(gatt_advertisement);
BlePeripheral peripheral = GenerateBlePeripheral(gatt_advertisement);
if (peripheral.IsValid()) {
discovered_peripheral_callback.peripheral_lost_cb(peripheral,
service_id);
}
}
}
}
void DiscoveredPeripheralTracker::ClearDataForServiceId(
const std::string& service_id) {
for (const auto& it : gatt_advertisement_infos_) {
if (it.second.service_id != service_id) {
continue;
}
ClearGattAdvertisement(it.first);
}
}
void DiscoveredPeripheralTracker::ClearGattAdvertisement(
const BleAdvertisement& gatt_advertisement) {
const auto gai_it = gatt_advertisement_infos_.find(gatt_advertisement);
if (gai_it == gatt_advertisement_infos_.end()) {
return;
}
auto item = gatt_advertisement_infos_.extract(gai_it);
GattAdvertisementInfo& gatt_advertisement_info = item.mapped();
const auto ga_it =
gatt_advertisements_.find(gatt_advertisement_info.advertisement_header);
if (ga_it != gatt_advertisements_.end()) {
// Remove the GATT advertisement from the advertisement header it's
// associated with.
BleAdvertisementSet& gatt_advertisement_set = ga_it->second;
gatt_advertisement_set.erase(gatt_advertisement);
// Unconditionally remove the header from advertisement_read_results_ so we
// can attempt to reread the GATT advertisement if they return.
advertisement_read_results_.erase(
gatt_advertisement_info.advertisement_header);
// If there are no more tracked GATT advertisements under this header, go
// ahead and remove it from gatt_advertisements_.
if (gatt_advertisement_set.empty()) {
gatt_advertisements_.erase(ga_it);
}
}
}
void DiscoveredPeripheralTracker::HandleAdvertisement(
const BleV2Peripheral& peripheral,
const location::nearby::api::ble_v2::BleAdvertisementData&
advertisement_data) {
ByteArray advertisement_bytes =
ExtractInterestingAdvertisementBytes(advertisement_data);
if (advertisement_bytes.Empty()) {
return;
}
// The UUID that the Fast/Regular Advertisement was found on. For regular
// advertisement, it's always kCopresenceServiceUuid. For fast
// advertisement, we may have below 2 UUIDs: 1. kCopresenceServiceUuid 2.
// Caller UUID.
// First filter out kCopresenceServiceUuid and see if any Caller UUID
// existed; if not then just take the kCopresenceServiceUuid as
// |service_uuid|.
std::vector<std::string> extracted_uuids;
// Filter out kCoprsence service uuid.
std::remove_copy_if(advertisement_data.service_uuids.begin(),
advertisement_data.service_uuids.end(),
std::back_inserter(extracted_uuids),
[](const std::string& advertisement_data_service_uuid) {
return advertisement_data_service_uuid ==
bleutils::kCopresenceServiceUuid;
});
std::string service_uuid;
if (!extracted_uuids.empty()) {
service_uuid = extracted_uuids.front();
} else {
service_uuid = std::string(bleutils::kCopresenceServiceUuid);
}
// Create a header tied to this fast advertisement. This helps us track the
// advertisement when reporting it as lost or connecting.
BleAdvertisementHeader advertisement_header =
CreateAdvertisementHeader(advertisement_bytes);
// Process the fast advertisement like we would a GATT advertisement and
// insert a placeholder AdvertisementReadResult.
advertisement_read_results_.insert(
{advertisement_header, absl::make_unique<AdvertisementReadResult>()});
BleAdvertisementHeader new_advertisement_header = HandleRawGattAdvertisements(
advertisement_header, {&advertisement_bytes}, service_uuid);
UpdateCommonStateForFoundBleAdvertisement(new_advertisement_header,
/*mac_address=*/peripheral.GetId());
}
ByteArray DiscoveredPeripheralTracker::ExtractInterestingAdvertisementBytes(
const location::nearby::api::ble_v2::BleAdvertisementData&
advertisement_data) {
// Iterate through all tracked service IDs to see if any of their fast
// advertisements are contained within this BLE advertisement.
for (const auto& item : service_id_infos_) {
const ServiceIdInfo& service_id_info = item.second;
// Check if there's service data for this fast advertisement
// service UUID. If so, we can short-circuit since all BLE
// advertisements can contain at most ONE fast advertisement.
const auto sd_it = advertisement_data.service_data.find(
service_id_info.fast_advertisement_service_uuid);
if (sd_it != advertisement_data.service_data.end()) {
return sd_it->second;
}
}
return {};
}
BleAdvertisementHeader DiscoveredPeripheralTracker::CreateAdvertisementHeader(
const ByteArray& advertisement_bytes) {
// Our end goal is to have a fully zeroed-out byte array of the correct
// length representing an empty bloom filter.
BloomFilter bloom_filter(
std::make_unique<BitSetImpl<
BleAdvertisementHeader::kServiceIdBloomFilterByteLength>>());
return BleAdvertisementHeader(
BleAdvertisementHeader::Version::kV2, /*extended_advertisement=*/false,
/*num_slots=*/1, ByteArray(bloom_filter),
bleutils::GenerateAdvertisementHash(advertisement_bytes),
/*psm=*/BleAdvertisementHeader::kDefaultPsmValue);
}
BleAdvertisementHeader DiscoveredPeripheralTracker::HandleRawGattAdvertisements(
const BleAdvertisementHeader& advertisement_header,
const std::vector<const ByteArray*>& gatt_advertisement_bytes_list,
const std::string& service_uuid) {
absl::flat_hash_map<std::string, BleAdvertisement>
parsed_gatt_advertisements = ParseRawGattAdvertisements(
gatt_advertisement_bytes_list, service_uuid);
// Update state for each GATT advertisement.
BleAdvertisementSet ble_advertisement_set;
BleAdvertisementHeader new_advertisement_header = advertisement_header;
for (const auto& item : parsed_gatt_advertisements) {
const std::string& service_id = item.first;
const BleAdvertisement& gatt_advertisement = item.second;
BleAdvertisementHeader old_advertisement_header;
const auto gai_it = gatt_advertisement_infos_.find(gatt_advertisement);
if (gai_it != gatt_advertisement_infos_.end()) {
old_advertisement_header = gai_it->second.advertisement_header;
}
ble_advertisement_set.insert(gatt_advertisement);
int new_psm = new_advertisement_header.GetPsm();
if (gatt_advertisement.GetPsm() !=
BleAdvertisementHeader::kDefaultPsmValue &&
gatt_advertisement.GetPsm() != new_psm) {
new_psm = gatt_advertisement.GetPsm();
// Also set header with new psm value to compare next time.
new_advertisement_header.SetPsm(new_psm);
}
// If the device received first fast advertisement is legacy one after then
// received extended one, should replace legacy with extended one which has
// psm value.
if (!old_advertisement_header.IsValid() ||
ShouldNotifyForNewPsm(old_advertisement_header.GetPsm(), new_psm)) {
// The GATT advertisement has never been seen before. Report it up to the
// client.
const auto sii_it = service_id_infos_.find(service_id);
if (sii_it == service_id_infos_.end()) {
NEARBY_LOGS(WARNING) << "HandleRawGattAdvertisements, failed to find "
"callback for service_id="
<< service_id;
continue;
}
BlePeripheral discovered_ble_peripheral =
GenerateBlePeripheral(gatt_advertisement, new_psm);
if (discovered_ble_peripheral.IsValid()) {
sii_it->second.discovered_peripheral_callback.peripheral_discovered_cb(
discovered_ble_peripheral, service_id, gatt_advertisement.GetData(),
gatt_advertisement.IsFastAdvertisement());
}
} else if (old_advertisement_header.GetPsm() !=
BleAdvertisementHeader::kDefaultPsmValue &&
new_advertisement_header.GetPsm() ==
BleAdvertisementHeader::kDefaultPsmValue) {
// Don't replace it in advertisementHeaders if this one without PSM but
// older has, it's the case that we received extended fast advertisement
// after then received legacy one.
continue;
} else if (ShouldRemoveHeader(old_advertisement_header,
new_advertisement_header)) {
// The GATT advertisement has been seen on a different advertisement
// header. Remove info about the old advertisement header since it's stale
// now.
advertisement_read_results_.erase(old_advertisement_header);
gatt_advertisements_.erase(old_advertisement_header);
}
GattAdvertisementInfo gatt_advertisement_info = {
.service_id = service_id,
.advertisement_header = new_advertisement_header,
.mac_address = {}};
gatt_advertisement_infos_.insert_or_assign(
gatt_advertisement, std::move(gatt_advertisement_info));
}
// Insert the list of read GATT advertisements for this advertisement
// header.
gatt_advertisements_.insert(
{new_advertisement_header, std::move(ble_advertisement_set)});
return new_advertisement_header;
}
absl::flat_hash_map<std::string, BleAdvertisement>
DiscoveredPeripheralTracker::ParseRawGattAdvertisements(
const std::vector<const ByteArray*>& gatt_advertisement_bytes_list,
const std::string& service_uuid) {
absl::flat_hash_map<std::string, BleAdvertisement>
parsed_gatt_advertisements = {};
// TODO(edwinwu): Refactor this big loop as subroutines.
for (const auto gatt_advertisement_bytes : gatt_advertisement_bytes_list) {
// First, parse the raw bytes into a BleAdvertisement.
BleAdvertisement gatt_advertisement(*gatt_advertisement_bytes);
if (!gatt_advertisement.IsValid()) {
NEARBY_LOGS(INFO) << "Unable to parse raw GATT advertisement:"
<< absl::BytesToHexString(
gatt_advertisement_bytes->data());
continue;
}
// Make sure the advertisement belongs to a service ID we're tracking.
for (const auto& item : service_id_infos_) {
const std::string& service_id = item.first;
// If we already found a higher version advertisement for this service ID,
// there's no point in comparing this advertisement against it.
const auto pga_it = parsed_gatt_advertisements.find(service_id);
if (pga_it != parsed_gatt_advertisements.end()) {
if (pga_it->second.GetVersion() > gatt_advertisement.GetVersion()) {
continue;
}
}
// service_id_hash is null here (mediums advertisement) because we already
// have a UUID in the fast advertisement.
if (gatt_advertisement.IsFastAdvertisement() && !service_uuid.empty()) {
const auto sii_it = service_id_infos_.find(service_id);
if (sii_it != service_id_infos_.end()) {
if (sii_it->second.fast_advertisement_service_uuid == service_uuid) {
NEARBY_LOGS(INFO)
<< "This GATT advertisement:"
<< absl::BytesToHexString(gatt_advertisement_bytes->data())
<< " is a fast advertisement and matched UUID=" << service_uuid
<< " in a map with service_id=" << service_id;
parsed_gatt_advertisements.insert({service_id, gatt_advertisement});
}
}
continue;
}
// Map the service ID to the advertisement if the service_id_hash match.
if (bleutils::GenerateServiceIdHash(service_id) ==
gatt_advertisement.GetServiceIdHash()) {
NEARBY_LOGS(INFO) << "Matched service_id=" << service_id
<< " to GATT advertisement="
<< absl::BytesToHexString(
gatt_advertisement_bytes->data());
parsed_gatt_advertisements.insert({service_id, gatt_advertisement});
break;
}
}
}
return parsed_gatt_advertisements;
}
bool DiscoveredPeripheralTracker::ShouldNotifyForNewPsm(int old_psm,
int new_psm) const {
return new_psm != BleAdvertisementHeader::kDefaultPsmValue &&
old_psm != new_psm;
}
bool DiscoveredPeripheralTracker::ShouldRemoveHeader(
const BleAdvertisementHeader& old_advertisement_header,
const BleAdvertisementHeader& new_advertisement_header) {
if (old_advertisement_header == new_advertisement_header) {
return false;
}
// We received the physical from legacy advertisement and create a mock one
// when receive a regular advertisement from extended advertisements. Avoid to
// remove the physical header for the new incoming regular extended
// advertisement. Otherwise, it make the device to fetch advertisement when
// received a physical header again.
// TODO(b/213835576) : Implement API to fetch the support for extended
// advertisement from platform impl.
bool is_extended_advertisement_available = false;
if (is_extended_advertisement_available) {
if (!IsDummyAdvertisementHeader(old_advertisement_header) &&
IsDummyAdvertisementHeader(new_advertisement_header)) {
return false;
}
}
return true;
}
bool DiscoveredPeripheralTracker::IsDummyAdvertisementHeader(
const BleAdvertisementHeader& advertisement_header) {
// Do not count advertisementHash and psm value here, for L2CAP feature, the
// regular advertisement has different value, it will include PSM value if
// received it from extended advertisement protocol and it will not has PSM
// value if it fetcted from GATT connection.
BloomFilter bloom_filter(
std::make_unique<BitSetImpl<
BleAdvertisementHeader::kServiceIdBloomFilterByteLength>>());
return advertisement_header.GetVersion() ==
BleAdvertisementHeader::Version::kV2 &&
advertisement_header.GetNumSlots() == 1 &&
advertisement_header.GetServiceIdBloomFilter() ==
ByteArray(bloom_filter);
}
void DiscoveredPeripheralTracker::HandleAdvertisementHeader(
const location::nearby::api::ble_v2::BleAdvertisementData&
advertisement_data,
BleV2Peripheral& peripheral, AdvertisementFetcher advertisement_fetcher) {
// Attempt to parse the advertisement header.
BleAdvertisementHeader advertisement_header(
ExtractAdvertisementHeaderBytes(advertisement_data));
if (!advertisement_header.IsValid()) {
NEARBY_LOGS(INFO)
<< "Failed to deserialize BLE advertisement header. Ignoring.";
return;
}
// Check if the advertisement header contains a service ID we're tracking.
if (!IsInterestingAdvertisementHeader(advertisement_header)) {
NEARBY_LOGS(VERBOSE) << "Ignoring BLE advertisement header="
<< absl::BytesToHexString(
ByteArray(advertisement_header).data())
<< " because it does not contain any service IDs "
"we're interested in.";
return;
}
// Determine whether or not we need to read a fresh GATT advertisement.
if (ShouldReadRawAdvertisementFromServer(advertisement_header)) {
// Determine whether or not we need to read a fresh GATT advertisement.
std::vector<const ByteArray*> gatt_advertisement_bytes_list =
FetchRawAdvertisements(advertisement_header,
/*mutated=*/peripheral,
std::move(advertisement_fetcher));
if (!gatt_advertisement_bytes_list.empty()) {
HandleRawGattAdvertisements(advertisement_header,
gatt_advertisement_bytes_list, "");
}
}
// Regardless of whether or not we read a new GATT advertisement, the maps
// should now be up-to-date. With this information, do some general
// housekeeping.
UpdateCommonStateForFoundBleAdvertisement(advertisement_header,
/*mac_address=*/peripheral.GetId());
}
ByteArray DiscoveredPeripheralTracker::ExtractAdvertisementHeaderBytes(
const location::nearby::api::ble_v2::BleAdvertisementData&
advertisement_data) {
const auto it =
advertisement_data.service_data.find(bleutils::kCopresenceServiceUuid);
if (it != advertisement_data.service_data.end()) {
const ByteArray& advertisement_header_bytes = it->second;
if (!advertisement_header_bytes.Empty()) {
return advertisement_header_bytes;
}
}
return {};
}
bool DiscoveredPeripheralTracker::IsInterestingAdvertisementHeader(
const BleAdvertisementHeader& advertisement_header) {
BloomFilter bloom_filter(
std::make_unique<BitSetImpl<
BleAdvertisementHeader::kServiceIdBloomFilterByteLength>>(),
advertisement_header.GetServiceIdBloomFilter());
for (const auto& item : service_id_infos_) {
const std::string& service_id = item.first;
if (bloom_filter.PossiblyContains(service_id)) {
return true;
}
}
return false;
}
bool DiscoveredPeripheralTracker::ShouldReadRawAdvertisementFromServer(
const BleAdvertisementHeader& advertisement_header) {
// Check if we have never seen this header. New headers should always be
// read.
ByteArray advertisement_header_bytes(advertisement_header);
const auto it = advertisement_read_results_.find(advertisement_header);
if (it == advertisement_read_results_.end()) {
NEARBY_LOGS(INFO) << "Received advertisement header="
<< absl::BytesToHexString(
advertisement_header_bytes.data())
<< ", but we have never seen it before. Caller should "
"try reading its GATT advertisement.";
return true;
}
// Extract the last read result for this particular header.
AdvertisementReadResult* advertisement_read_result = it->second.get();
// Now evaluate if we should retry reading.
switch (advertisement_read_result->EvaluateRetryStatus()) {
case AdvertisementReadResult::RetryStatus::kRetry:
NEARBY_LOGS(INFO)
<< "Received advertisement header="
<< absl::BytesToHexString(advertisement_header_bytes.data())
<< ". Caller should retry reading its GATT advertisement.";
return true;
case AdvertisementReadResult::RetryStatus::kPreviouslySucceeded:
NEARBY_LOGS(INFO) << "Received advertisement header="
<< absl::BytesToHexString(
advertisement_header_bytes.data())
<< ", but we have already read its GATT advertisement.";
return false;
case AdvertisementReadResult::RetryStatus::kTooSoon:
NEARBY_LOGS(INFO)
<< "Received advertisement header="
<< absl::BytesToHexString(advertisement_header_bytes.data())
<< ", but we have recently failed to read its GATT advertisement.";
return false;
case AdvertisementReadResult::RetryStatus::kUnknown:
// Fall through.
break;
}
NEARBY_LOGS(INFO)
<< "Received advertisement header="
<< absl::BytesToHexString(advertisement_header_bytes.data())
<< ", but we do not know whether or not to retry reading "
"its GATT advertisement. Caller should retry to be safe.";
return true;
}
std::vector<const ByteArray*>
DiscoveredPeripheralTracker::FetchRawAdvertisements(
const BleAdvertisementHeader& advertisement_header,
BleV2Peripheral& peripheral, AdvertisementFetcher advertisement_fetcher) {
// Fetch the raw GATT advertisements and store the results.
AdvertisementReadResult* advertisement_read_result = nullptr;
const auto it = advertisement_read_results_.find(advertisement_header);
if (it != advertisement_read_results_.end()) {
advertisement_read_result = it->second.get();
}
std::vector<std::string> service_ids;
std::transform(service_id_infos_.begin(), service_id_infos_.end(),
std::back_inserter(service_ids),
[](auto& kv) { return kv.first; });
std::unique_ptr<AdvertisementReadResult> read_result =
advertisement_fetcher.fetch_advertisements(
advertisement_header.GetNumSlots(), advertisement_header.GetPsm(),
service_ids, advertisement_read_result,
/*mutated=*/peripheral);
if (!read_result) {
return {};
}
auto iterator_and_result_pair = advertisement_read_results_.insert_or_assign(
advertisement_header, std::move(read_result));
// Take those results and return all the advertisements we were able to read.
std::vector<const ByteArray*> advertisement_bytes_list;
if (iterator_and_result_pair.second) {
advertisement_bytes_list =
iterator_and_result_pair.first->second->GetAdvertisements();
}
return advertisement_bytes_list;
}
void DiscoveredPeripheralTracker::UpdateCommonStateForFoundBleAdvertisement(
const BleAdvertisementHeader& advertisement_header,
const std::string& mac_address) {
const auto ga_it = gatt_advertisements_.find(advertisement_header);
if (ga_it == gatt_advertisements_.end()) {
NEARBY_LOGS(INFO)
<< "No GATT advertisements found for advertisement header="
<< absl::BytesToHexString(ByteArray(advertisement_header).data());
return;
}
BleAdvertisementSet saved_gatt_advertisements = ga_it->second;
for (const auto& gatt_advertisement : saved_gatt_advertisements) {
const auto gai_it = gatt_advertisement_infos_.find(gatt_advertisement);
if (gai_it == gatt_advertisement_infos_.end()) {
continue;
}
GattAdvertisementInfo& gatt_advertisement_info = gai_it->second;
const auto sii_it =
service_id_infos_.find(gatt_advertisement_info.service_id);
if (sii_it != service_id_infos_.end()) {
auto* lost_entity_tracker = sii_it->second.lost_entity_tracker.get();
if (!lost_entity_tracker) {
NEARBY_LOGS(WARNING) << "UpdateCommonStateForFoundBleAdvertisement, "
"failed to find entity "
"tracker for service_id="
<< gatt_advertisement_info.service_id;
continue;
}
lost_entity_tracker->RecordFoundEntity(gatt_advertisement);
gatt_advertisement_info.mac_address = mac_address;
gatt_advertisement_infos_[gatt_advertisement] = gatt_advertisement_info;
}
}
}
BlePeripheral DiscoveredPeripheralTracker::GenerateBlePeripheral(
const BleAdvertisement& gatt_advertisement, int psm) {
return BlePeripheral(ByteArray(gatt_advertisement), psm);
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,282 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_DISCOVERED_PERIPHERAL_TRACKER_H_
#define CORE_INTERNAL_MEDIUMS_BLE_V2_DISCOVERED_PERIPHERAL_TRACKER_H_
#include <functional>
#include <memory>
#include <string>
#include "connections/implementation/mediums//lost_entity_tracker.h"
#include "connections/implementation/mediums/ble_v2/advertisement_read_result.h"
#include "connections/implementation/mediums/ble_v2/ble_advertisement.h"
#include "connections/implementation/mediums/ble_v2/ble_advertisement_header.h"
#include "connections/implementation/mediums/ble_v2/discovered_peripheral_callback.h"
#include "connections/implementation/mediums/lost_entity_tracker.h"
#include "internal/platform/bluetooth_adapter.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/mutex.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// Manages all discovered peripheral logic for {@link BluetoothLowEnergy}. This
// includes tracking found peripherals, lost peripherals, and MAC addresses
// associated with those peripherals.
//
// See go/ble-on-lost for more information. It includes the algorithms used to
// compute found and lost peripherals.
class DiscoveredPeripheralTracker {
public:
// GATT advertisement fetcher.
struct AdvertisementFetcher {
// Fetches relevant GATT advertisements for the peripheral found in {@link
// DiscoveredPeripheralTracker#ProcessFoundBleAdvertisement(}.
std::function<std::unique_ptr<AdvertisementReadResult>(
int num_slots, int psm,
const std::vector<std::string>& interesting_service_ids,
AdvertisementReadResult* advertisement_read_result,
BleV2Peripheral& peripheral)>
fetch_advertisements = [](int, int, const std::vector<std::string>&,
AdvertisementReadResult*, BleV2Peripheral&)
-> std::unique_ptr<AdvertisementReadResult> { return nullptr; };
};
// Starts tracking discoveries for a particular service Id.
//
// service_id - The service ID to track.
// discovered_peripheral_callback - The callback to invoke for discovery
// events.
// fast_advertisement_service_uuid - The service UUID to look for fast
// advertisements on.
// Note: fast_advertisement_service_uuid can be empty string to indicate
// that `fast_advertisement_service_uuid` will be ignored for regular
// advertisement.
void StartTracking(
const std::string& service_id,
const DiscoveredPeripheralCallback& discovered_peripheral_callback,
const std::string& fast_advertisement_service_uuid)
ABSL_LOCKS_EXCLUDED(mutex_);
// Stops tracking discoveries for a particular service Id.
void StopTracking(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
// Processes a found BLE advertisement.
//
// peripheral - To access the status of the operation. The ownership
// is moved into 'discovered_peripheral_tracker' object.
// advertisement_data - The found BLE advertisement data.
// advertisement_fetcher - The advertisement fetcher callback for regular
// advertisement if the advertisement header is been
// processed.
void ProcessFoundBleAdvertisement(
BleV2Peripheral peripheral,
const api::ble_v2::BleAdvertisementData& advertisement_data,
AdvertisementFetcher advertisement_fetcher) ABSL_LOCKS_EXCLUDED(mutex_);
// Processes the set of lost GATT advertisements and notifies the client of
// any lost peripherals.
void ProcessLostGattAdvertisements() ABSL_LOCKS_EXCLUDED(mutex_);
private:
using BleAdvertisementSet = absl::flat_hash_set<BleAdvertisement>;
// A container to hold callback or other informations that bring from BLE
// medium when `StartTracking`.
struct ServiceIdInfo {
// Tracks what service IDs are currently active and gives us client
// callbacks to call.
DiscoveredPeripheralCallback discovered_peripheral_callback;
// Used to periodically compute lost GATT advertisements.
std::unique_ptr<LostEntityTracker<BleAdvertisement>> lost_entity_tracker;
// Used to check for fast advertisements delivered through BLE advertisement
// service data, under the given UUID.
std::string fast_advertisement_service_uuid;
};
// A container to hold the related informations for a GATT advertisement.
struct GattAdvertisementInfo {
// GATT advertisement to the service ID it's associated with. Tracks what
// GATT advertisements are currently active. Used to determine which
// LostEntityTracker to invoke when advertisements are rediscovered.
std::string service_id;
// Used to efficiently find advertisement headers to delete when GATT
// advertisements are updated. This is a reverse map of
// gatt_advertisements_.
BleAdvertisementHeader advertisement_header;
// Used when we need to make a socket connection based off of the GATT
// advertisement alone. Entries are modified every time a GATT
// advertisement's advertisement header is seen.
std::string mac_address;
};
// Clears stale data from any previous sessions.
void ClearDataForServiceId(const std::string& service_id)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Clears out all data related to the provided GATT advertisement. This
// includes:
// 1. Removing the corresponding GATT advertisement from
// gatt_advertisement_infos_.
// 2. Removing the corresponding advertisement header from
// advertisement_read_results.
// 3. Removing the corresponding advertisement header from
// gatt_advertisements_, only if there are no remaining GATT
// advertisements related to that header.
void ClearGattAdvertisement(const BleAdvertisement& gatt_advertisement)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Handles the legacy fast advertisement or the extended fast/regular
// advertisement.
void HandleAdvertisement(
const BleV2Peripheral& peripheral,
const api::ble_v2::BleAdvertisementData& advertisement_data)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Extracts the advertisement byte array from `AdvertisementData`.
ByteArray ExtractInterestingAdvertisementBytes(
const api::ble_v2::BleAdvertisementData& advertisement_data)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Creates an advertisement header that's purely a hash of the fast/regular
// advertisement, since they come with no header.
BleAdvertisementHeader CreateAdvertisementHeader(
const ByteArray& advertisement_bytes);
// Returns BleAdvertisementHeader, it may be replaced if the header is mock
// and there's a psm value in advertisement.
BleAdvertisementHeader HandleRawGattAdvertisements(
const BleAdvertisementHeader& advertisement_header,
const std::vector<const ByteArray*>& gatt_advertisement_bytes_list,
const std::string& service_uuid) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Returns a map of service IDs to GATT advertisements who belong to a tracked
// service ID.
absl::flat_hash_map<std::string, BleAdvertisement> ParseRawGattAdvertisements(
const std::vector<const ByteArray*>& gatt_advertisement_bytes_list,
const std::string& service_uuid) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Returns true if `new_psm` is not default value and different with
// `old_psm`.
bool ShouldNotifyForNewPsm(int old_psm, int new_psm) const;
// Returns true if two headers are not the same.
bool ShouldRemoveHeader(
const BleAdvertisementHeader& old_advertisement_header,
const BleAdvertisementHeader& new_advertisement_header);
// Returns true if it is a faked advertisement header which is purely a hash
// of the fast/regular advertisement, since they come with no header.
bool IsDummyAdvertisementHeader(
const BleAdvertisementHeader& advertisement_header);
// Handles the advertisement header for regular advertisement.
void HandleAdvertisementHeader(
const api::ble_v2::BleAdvertisementData& advertisement_data,
BleV2Peripheral& peripheral, AdvertisementFetcher advertisement_fetcher)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Extracts the advertisement header byte array from `AdvertisementData`.
ByteArray ExtractAdvertisementHeaderBytes(
const api::ble_v2::BleAdvertisementData& advertisement_data)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Returns true if the advertisement header contains a service ID we're
// tracking.
bool IsInterestingAdvertisementHeader(
const BleAdvertisementHeader& advertisement_header)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Returns true if the `advertisement_header` is allowd to callbck to fecth
// advertisement.
bool ShouldReadRawAdvertisementFromServer(
const BleAdvertisementHeader& advertisement_header)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Fetches advertsiement from BLE medium if advertisement header is read in
// AdvertisementData.
//
// advertisement_fetcher : a fetcher passed from BLE medium to read the
// advertisemeent from BLE characteristics by GATT server.
std::vector<const ByteArray*> FetchRawAdvertisements(
const BleAdvertisementHeader& advertisement_header,
BleV2Peripheral& peripheral, AdvertisementFetcher advertisement_fetcher)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Updates `gatt_advertisement_infos_` map no matter whether we read a new
// GATT advertisement by the input `advertisement_header` and 'mac_address`.
void UpdateCommonStateForFoundBleAdvertisement(
const BleAdvertisementHeader& advertisement_header,
const std::string& mac_address) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Creates BlePeripheral based on the input of advertisement and psm value.
BlePeripheral GenerateBlePeripheral(
const BleAdvertisement& gatt_advertisement,
int psm = BleAdvertisementHeader::kDefaultPsmValue);
Mutex mutex_;
// ------------ SERVICE ID MAPS ------------
// Entries in these maps all follow the same lifecycle. Entries are added in
// StartTracking, and removed in StopTracking.
absl::flat_hash_map<std::string, ServiceIdInfo> service_id_infos_
ABSL_GUARDED_BY(mutex_);
// ------------ ADVERTISEMENT HEADER MAPS ------------
// Maps advertisement headers to AdvertisementReadResult. Tells us when to
// retry reading a GATT advertisement. If no entry exists for a particular
// header, we should try reading a GATT advertisement. Entries are added
// whenever a GATT advertisement read is attempted, and removed when GATT
// advertisements are lost. Entries are also removed whenever
// gattAdvertisements removes its entry.
//
// The map is also cleared whenever StartTracking is called, due to client
// changes. For example, say clients A and B start scanning and discover
// advertisements A and B (for both clients) on advertisement header 1. Then,
// A restarts scanning, causing us to clear stale advertisement A. However,
// since B was still scanning, we don't remove advertisement header 1 from the
// map. This causes us to never re-read advertisement A.
absl::flat_hash_map<BleAdvertisementHeader,
std::unique_ptr<AdvertisementReadResult>>
advertisement_read_results_ ABSL_GUARDED_BY(mutex_);
// Maps advertisement headers to a set of GATT advertisements from a single
// peripheral. Used to retrieve GATT advertisements that we need to reprocess
// every time a header is seen. Entries are added when GATT advertisements are
// read, removed when all associated GATT advertisements are lost or become
// stale, and replaced when the advertisement header is updated for a single
// remote peripheral.
absl::flat_hash_map<BleAdvertisementHeader, BleAdvertisementSet>
gatt_advertisements_ ABSL_GUARDED_BY(mutex_);
// ------------ GATT ADVERTISEMENT MAPS ------------
// Entries are added when Gatt advertisements are read, and removed when Gatt
// advertisements are lost or become stale.
absl::flat_hash_map<BleAdvertisement, GattAdvertisementInfo>
gatt_advertisement_infos_ ABSL_GUARDED_BY(mutex_);
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_DISCOVERED_PERIPHERAL_TRACKER_H_
@@ -0,0 +1,940 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "connections/implementation/mediums/ble_v2/discovered_peripheral_tracker.h"
#include <memory>
#include <string>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "connections/implementation/mediums/ble_v2/ble_utils.h"
#include "connections/implementation/mediums/ble_v2/bloom_filter.h"
#include "internal/platform/bluetooth_adapter.h"
#include "internal/platform/count_down_latch.h"
#include "internal/platform/mutex.h"
#include "internal/platform/mutex_lock.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace {
constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000);
constexpr absl::string_view kCopresenceServiceUuid =
"0000FEF3-0000-1000-8000-00805F9B34FB";
constexpr absl::string_view kFastAdvertisementServiceUuid =
"0000FE2C-0000-1000-8000-00805F9B34FB";
constexpr absl::string_view kServiceIdA = "A";
constexpr absl::string_view kServiceIdB = "B";
constexpr absl::string_view kMacAddress1 = "4C:8B:1D:CE:BA:D1";
constexpr absl::string_view kData = "\x04\x02\x00";
constexpr absl::string_view kData2 = "\x07\x00\x07";
constexpr absl::string_view kDeviceToken = "\x04\x20";
ByteArray CreateFastBleAdvertisement(const ByteArray& data,
const ByteArray& device_token) {
return ByteArray(BleAdvertisement(
BleAdvertisement::Version::kV2, BleAdvertisement::SocketVersion::kV2,
/*service_id_hash=*/ByteArray{}, data, device_token,
BleAdvertisementHeader::kDefaultPsmValue));
}
ByteArray CreateBleAdvertisement(const std::string& service_id,
const ByteArray& data,
const ByteArray& device_token) {
return ByteArray(BleAdvertisement(
BleAdvertisement::Version::kV2, BleAdvertisement::SocketVersion::kV2,
bleutils::GenerateServiceIdHash(service_id), data, device_token,
BleAdvertisementHeader::kDefaultPsmValue));
}
// Legacy advertisement is not supported any more but we fake the legacy one
// by setting version and socket version as kV1 and using legacy hash function.
ByteArray CreateLegacyBleAdvertisement(const std::string& service_id,
const ByteArray& data,
const ByteArray& device_token) {
return ByteArray(BleAdvertisement(
BleAdvertisement::Version::kV1, BleAdvertisement::SocketVersion::kV1,
bleutils::GenerateServiceIdHash(service_id,
BleAdvertisement::Version::kV1),
data, device_token, BleAdvertisementHeader::kDefaultPsmValue));
}
ByteArray CreateBleAdvertisementHeader(const ByteArray& advertisement_hash,
int psm,
std::vector<std::string>& service_ids) {
BloomFilter service_id_bloom_filter(
std::make_unique<BitSetImpl<
BleAdvertisementHeader::kServiceIdBloomFilterByteLength>>());
for (const std::string& service_id : service_ids) {
service_id_bloom_filter.Add(service_id);
}
return ByteArray(BleAdvertisementHeader(BleAdvertisementHeader::Version::kV2,
/*extended_advertisement=*/false,
/*num_slots=*/service_ids.size(),
ByteArray(service_id_bloom_filter),
advertisement_hash, psm));
}
ByteArray CreateBleAdvertisementHeader(const ByteArray& advertisement_hash,
std::vector<std::string>& service_ids) {
return CreateBleAdvertisementHeader(advertisement_hash,
BleAdvertisementHeader::kDefaultPsmValue,
service_ids);
}
ByteArray GenerateRandomAdvertisementHash() {
ByteArray random_advertisement_hash = Utils::GenerateRandomBytes(
BleAdvertisementHeader::kAdvertisementHashByteLength);
return random_advertisement_hash;
}
// A stub BlePeripheral implementation.
class BlePeripheralStub : public api::ble_v2::BlePeripheral {
public:
explicit BlePeripheralStub(absl::string_view mac_address) {
mac_address_ = mac_address;
}
std::string GetId() const override { return mac_address_; }
private:
std::string mac_address_;
};
class DiscoveredPeripheralTrackerTest : public testing::Test {
public:
void SetUp() override {}
BleV2Peripheral CreateBlePeripheral(absl::string_view mac_address) {
ble_peripheral_ = std::make_unique<BlePeripheralStub>(mac_address);
return BleV2Peripheral(ble_peripheral_.get());
}
// Simulates to see a fast advertisement.
void FindFastAdvertisement(
const api::ble_v2::BleAdvertisementData& advertisement_data,
const std::vector<ByteArray>& advertisement_bytes_list,
CountDownLatch& fetch_latch) {
BleV2Peripheral peripheral = CreateBlePeripheral(kMacAddress1);
discovered_peripheral_tracker_.ProcessFoundBleAdvertisement(
peripheral, advertisement_data,
GetAdvertisementFetcher(fetch_latch, advertisement_bytes_list));
}
// Simulates to see a regular advertisement.
void FindAdvertisement(
const api::ble_v2::BleAdvertisementData& advertisement_data,
const std::vector<ByteArray>& advertisement_bytes_list,
CountDownLatch& fetch_latch) {
BleV2Peripheral peripheral = CreateBlePeripheral(kMacAddress1);
discovered_peripheral_tracker_.ProcessFoundBleAdvertisement(
peripheral, advertisement_data,
GetAdvertisementFetcher(fetch_latch, advertisement_bytes_list));
}
int GetFetchAdvertisementCallbackCount() const {
MutexLock lock(&mutex_);
return fetch_count_;
}
protected:
// A stub Advertisement fetcher.
DiscoveredPeripheralTracker::AdvertisementFetcher GetAdvertisementFetcher(
CountDownLatch& fetch_latch,
const std::vector<ByteArray>& advertisement_bytes_list) {
return {
.fetch_advertisements =
[this, &fetch_latch, &advertisement_bytes_list](
int num_slots, int psm,
const std::vector<std::string>& interesting_service_ids,
AdvertisementReadResult* arr, BleV2Peripheral& peripheral)
-> std::unique_ptr<AdvertisementReadResult> {
MutexLock lock(&mutex_);
fetch_count_++;
auto advertisement_read_result =
std::make_unique<AdvertisementReadResult>();
int slot = 0;
for (const auto& advertisement_bytes : advertisement_bytes_list) {
advertisement_read_result->AddAdvertisement(slot++,
advertisement_bytes);
}
advertisement_read_result->RecordLastReadStatus(/*isSuccess=*/true);
fetch_latch.CountDown();
return advertisement_read_result;
},
};
}
mutable Mutex mutex_;
int fetch_count_ ABSL_GUARDED_BY(mutex_) = 0;
std::unique_ptr<BlePeripheralStub> ble_peripheral_;
DiscoveredPeripheralTracker discovered_peripheral_tracker_;
};
TEST_F(DiscoveredPeripheralTrackerTest,
FoundFastAdvertisementPeripheralDiscovered) {
ByteArray fast_advertisement_bytes = CreateFastBleAdvertisement(
ByteArray(std::string(kData)), ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(1);
CountDownLatch fetch_latch(1);
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&found_latch](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_TRUE(fast_advertisement);
found_latch.CountDown();
},
},
std::string(kFastAdvertisementServiceUuid));
api::ble_v2::BleAdvertisementData advertisement_data;
if (!fast_advertisement_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kFastAdvertisementServiceUuid));
advertisement_data.service_data.insert(
{std::string(kFastAdvertisementServiceUuid), fast_advertisement_bytes});
}
FindFastAdvertisement(advertisement_data, {}, fetch_latch);
// We should receive a client callback of a peripheral discovery without a
// GATT read.
fetch_latch.Await(kWaitDuration);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 0);
}
TEST_F(DiscoveredPeripheralTrackerTest,
FoundFastAdvertisementDuplicateAdvertisements) {
ByteArray fast_advertisement_bytes = CreateFastBleAdvertisement(
ByteArray(std::string(kData)), ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(3);
CountDownLatch fetch_latch(3);
int callback_times = 0;
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&callback_times, &found_latch](
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
callback_times++;
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_TRUE(fast_advertisement);
found_latch.CountDown();
},
},
std::string(kFastAdvertisementServiceUuid));
api::ble_v2::BleAdvertisementData advertisement_data;
if (!fast_advertisement_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kFastAdvertisementServiceUuid));
advertisement_data.service_data.insert(
{std::string(kFastAdvertisementServiceUuid), fast_advertisement_bytes});
}
FindFastAdvertisement(advertisement_data, {}, fetch_latch);
FindFastAdvertisement(advertisement_data, {}, fetch_latch);
FindFastAdvertisement(advertisement_data, {}, fetch_latch);
// We should only receive ONE client callback of a peripheral discovery with
// ZERO GATT reads.
fetch_latch.Await(kWaitDuration * 3);
EXPECT_EQ(callback_times, 1);
// The count down won't be finished since the callback only been called once.
// Let it time out.
EXPECT_FALSE(found_latch.Await(3 * kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 0);
}
TEST_F(DiscoveredPeripheralTrackerTest,
FoundFastAdvertisementUntrackedFastAdvertisementServiceUuid) {
ByteArray fast_advertisement_bytes = CreateFastBleAdvertisement(
ByteArray(std::string(kData)), ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(1);
CountDownLatch fetch_latch(1);
// Start tracking a service ID and then process a discovery containing a valid
// fast advertisement, but under a different service UUID.
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&found_latch](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_TRUE(fast_advertisement);
found_latch.CountDown();
},
},
"0000FE2C-0000-1000-8000-00805F9B34FC");
api::ble_v2::BleAdvertisementData advertisement_data;
if (!fast_advertisement_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kFastAdvertisementServiceUuid));
advertisement_data.service_data.insert(
{std::string(kFastAdvertisementServiceUuid), fast_advertisement_bytes});
}
FindFastAdvertisement(advertisement_data, {}, fetch_latch);
// We should get no new discoveries.
fetch_latch.Await(kWaitDuration);
EXPECT_FALSE(found_latch.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 0);
}
TEST_F(DiscoveredPeripheralTrackerTest,
FoundFastAdvertisementAndGattAdvertisementSimultaneously) {
std::vector<std::string> service_ids = {std::string(kServiceIdB)};
ByteArray advertisement_header_bytes = CreateBleAdvertisementHeader(
GenerateRandomAdvertisementHash(), service_ids);
ByteArray fast_advertisement_bytes = CreateFastBleAdvertisement(
ByteArray(std::string(kData)), ByteArray(std::string(kDeviceToken)));
ByteArray advertisement_bytes = CreateBleAdvertisement(
std::string(kServiceIdB), ByteArray(std::string(kData)),
ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch_a(1);
CountDownLatch found_latch_b(1);
CountDownLatch fetch_latch(1);
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&found_latch_a](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_TRUE(fast_advertisement);
found_latch_a.CountDown();
},
},
std::string(kFastAdvertisementServiceUuid));
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdB),
{
.peripheral_discovered_cb =
[&found_latch_b](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_FALSE(fast_advertisement);
found_latch_b.CountDown();
},
},
"");
api::ble_v2::BleAdvertisementData advertisement_data;
if (!advertisement_header_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kCopresenceServiceUuid));
advertisement_data.service_data.insert(
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
}
if (!fast_advertisement_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kFastAdvertisementServiceUuid));
advertisement_data.service_data.insert(
{std::string(kFastAdvertisementServiceUuid), fast_advertisement_bytes});
}
FindFastAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch);
// We should receive two separate peripheral discoveries.
fetch_latch.Await(kWaitDuration);
EXPECT_TRUE(found_latch_a.Await(kWaitDuration).result());
EXPECT_TRUE(found_latch_b.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1);
}
TEST_F(DiscoveredPeripheralTrackerTest,
FoundBleAdvertisementPeripheralDiscovered) {
std::vector<std::string> service_ids = {std::string(kServiceIdA)};
ByteArray advertisement_header_bytes = CreateBleAdvertisementHeader(
GenerateRandomAdvertisementHash(), service_ids);
ByteArray advertisement_bytes = CreateBleAdvertisement(
std::string(kServiceIdA), ByteArray(std::string(kData)),
ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(1);
CountDownLatch fetch_latch(1);
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&found_latch](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_FALSE(fast_advertisement);
found_latch.CountDown();
},
},
"");
api::ble_v2::BleAdvertisementData advertisement_data;
if (!advertisement_header_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kCopresenceServiceUuid));
advertisement_data.service_data.insert(
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
}
FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch);
// We should receive a client callback of a peripheral discovery.
fetch_latch.Await(kWaitDuration);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1);
}
TEST_F(DiscoveredPeripheralTrackerTest,
FoundBleAdvertisementLegacyPeripheralDiscovered) {
std::vector<std::string> service_ids = {std::string(kServiceIdA)};
ByteArray advertisement_header_bytes = CreateBleAdvertisementHeader(
GenerateRandomAdvertisementHash(), service_ids);
ByteArray legacy_advertisement_bytes = CreateLegacyBleAdvertisement(
std::string(kServiceIdA), ByteArray(std::string(kData)),
ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(1);
CountDownLatch fetch_latch(1);
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&found_latch](
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) { found_latch.CountDown(); },
},
"");
api::ble_v2::BleAdvertisementData advertisement_data;
if (!advertisement_header_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kCopresenceServiceUuid));
advertisement_data.service_data.insert(
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
}
FindAdvertisement(advertisement_data, {legacy_advertisement_bytes},
fetch_latch);
// We should not receive a client callback with the Version/SocketVersion kV1.
fetch_latch.Await(kWaitDuration);
EXPECT_FALSE(found_latch.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1);
}
TEST_F(DiscoveredPeripheralTrackerTest,
FoundBleAdvertisementFavorLatestPeripheral) {
std::vector<std::string> service_ids = {std::string(kServiceIdA)};
ByteArray advertisement_header_bytes = CreateBleAdvertisementHeader(
GenerateRandomAdvertisementHash(), service_ids);
ByteArray advertisement_bytes = CreateBleAdvertisement(
std::string(kServiceIdA), ByteArray(std::string(kData2)),
ByteArray(std::string(kDeviceToken)));
ByteArray legacy_advertisement_bytes = CreateLegacyBleAdvertisement(
std::string(kServiceIdA), ByteArray(std::string(kData)),
ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(1);
CountDownLatch fetch_latch(1);
int callback_times = 0;
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&callback_times, &found_latch](
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
callback_times++;
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData2)));
EXPECT_FALSE(fast_advertisement);
found_latch.CountDown();
},
},
"");
api::ble_v2::BleAdvertisementData advertisement_data;
if (!advertisement_header_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kCopresenceServiceUuid));
advertisement_data.service_data.insert(
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
}
FindAdvertisement(advertisement_data,
{advertisement_bytes, legacy_advertisement_bytes},
fetch_latch);
// We should only receive one callback with data from the V2 GATT
// advertisement.
fetch_latch.Await(kWaitDuration);
EXPECT_EQ(callback_times, 1);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1);
}
TEST_F(DiscoveredPeripheralTrackerTest,
FoundBleAdvertisementDuplicateAdvertisements) {
std::vector<std::string> service_ids = {std::string(kServiceIdA)};
ByteArray advertisement_header_bytes = CreateBleAdvertisementHeader(
GenerateRandomAdvertisementHash(), service_ids);
ByteArray advertisement_bytes = CreateBleAdvertisement(
std::string(kServiceIdA), ByteArray(std::string(kData)),
ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(3);
CountDownLatch fetch_latch(3);
int callback_times = 0;
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&callback_times, &found_latch](
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
callback_times++;
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_FALSE(fast_advertisement);
found_latch.CountDown();
},
},
"");
api::ble_v2::BleAdvertisementData advertisement_data;
if (!advertisement_header_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kCopresenceServiceUuid));
advertisement_data.service_data.insert(
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
}
FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch);
FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch);
FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch);
// We should only receive ONE client callback of a peripheral discovery. And
// we should also do only ONE GATT read.
fetch_latch.Await(kWaitDuration);
EXPECT_EQ(callback_times, 1);
// The count down won't be finished since the callback only been called once.
// Let it time out.
EXPECT_FALSE(found_latch.Await(3 * kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1);
}
TEST_F(DiscoveredPeripheralTrackerTest,
FoundBleAdvertisementUntrackedServiceId) {
std::vector<std::string> service_ids = {std::string(kServiceIdA),
std::string(kServiceIdB)};
ByteArray advertisement_header_bytes = CreateBleAdvertisementHeader(
GenerateRandomAdvertisementHash(), service_ids);
ByteArray advertisement_bytes = CreateBleAdvertisement(
std::string(kServiceIdB), ByteArray(std::string(kData)),
ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(1);
CountDownLatch fetch_latch(1);
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&found_latch](
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) { found_latch.CountDown(); },
},
"");
api::ble_v2::BleAdvertisementData advertisement_data;
if (!advertisement_header_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kCopresenceServiceUuid));
advertisement_data.service_data.insert(
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
}
FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch);
// We should get no new discoveries.
fetch_latch.Await(kWaitDuration);
EXPECT_FALSE(found_latch.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1);
}
TEST_F(DiscoveredPeripheralTrackerTest,
LostPeripheralForFastAdvertisementLost) {
std::vector<std::string> service_ids = {};
ByteArray advertisement_header_bytes = CreateBleAdvertisementHeader(
GenerateRandomAdvertisementHash(), service_ids);
ByteArray fast_advertisement_bytes = CreateFastBleAdvertisement(
ByteArray(std::string(kData)), ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(1);
CountDownLatch lost_latch(2);
CountDownLatch fetch_latch(1);
int lost_callback_times = 0;
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&found_latch](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_TRUE(fast_advertisement);
found_latch.CountDown();
},
.peripheral_lost_cb =
[&lost_latch, &lost_callback_times](
BlePeripheral& peripheral, const std::string& service_id) {
lost_callback_times++;
lost_latch.CountDown();
},
},
std::string(kFastAdvertisementServiceUuid));
api::ble_v2::BleAdvertisementData advertisement_data;
if (!advertisement_header_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kCopresenceServiceUuid));
advertisement_data.service_data.insert(
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
}
if (!fast_advertisement_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kFastAdvertisementServiceUuid));
advertisement_data.service_data.insert(
{std::string(kFastAdvertisementServiceUuid), fast_advertisement_bytes});
}
FindFastAdvertisement(advertisement_data, {fast_advertisement_bytes},
fetch_latch);
// We should receive a client callback of a peripheral discovery.
fetch_latch.Await(kWaitDuration);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 0);
// Then, go through two cycles of onLost. The first cycle should include the
// recently discovered peripheral in its 'found' pool. The second one should
// trigger the onLost callback.
discovered_peripheral_tracker_.ProcessLostGattAdvertisements();
discovered_peripheral_tracker_.ProcessLostGattAdvertisements();
// We should receive a client callback of a lost peripheral.
EXPECT_FALSE(lost_latch.Await(kWaitDuration).result());
EXPECT_EQ(lost_callback_times, 1);
}
TEST_F(DiscoveredPeripheralTrackerTest,
FoundFastAdvertisementAlmostLostPeripheral) {
std::vector<std::string> service_ids = {};
ByteArray advertisement_header_bytes = CreateBleAdvertisementHeader(
GenerateRandomAdvertisementHash(), service_ids);
ByteArray fast_advertisement_bytes = CreateFastBleAdvertisement(
ByteArray(std::string(kData)), ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(1);
CountDownLatch lost_latch(1);
CountDownLatch fetch_latch(1);
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&found_latch](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_TRUE(fast_advertisement);
found_latch.CountDown();
},
.peripheral_lost_cb =
[&lost_latch](BlePeripheral& peripheral,
const std::string& service_id) {
lost_latch.CountDown();
},
},
std::string(kFastAdvertisementServiceUuid));
api::ble_v2::BleAdvertisementData advertisement_data;
if (!advertisement_header_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kCopresenceServiceUuid));
advertisement_data.service_data.insert(
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
}
if (!fast_advertisement_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kFastAdvertisementServiceUuid));
advertisement_data.service_data.insert(
{std::string(kFastAdvertisementServiceUuid), fast_advertisement_bytes});
}
// Start tracking a service ID and then process a loop of discoveries and
// onLost alarms.
for (int i = 0; i < 20; i++) {
FindFastAdvertisement(advertisement_data, {fast_advertisement_bytes},
fetch_latch);
discovered_peripheral_tracker_.ProcessLostGattAdvertisements();
}
// We should only receive ONE client callback of a peripheral discovery, ZERO
// GATT reads, and no onLost calls.
fetch_latch.Await(kWaitDuration);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 0);
EXPECT_FALSE(lost_latch.Await(kWaitDuration).result());
}
TEST_F(DiscoveredPeripheralTrackerTest, LostPeripheralForAdvertisementLost) {
std::vector<std::string> service_ids = {std::string(kServiceIdA)};
ByteArray advertisement_header_bytes = CreateBleAdvertisementHeader(
GenerateRandomAdvertisementHash(), service_ids);
ByteArray advertisement_bytes = CreateBleAdvertisement(
std::string(kServiceIdA), ByteArray(std::string(kData)),
ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(1);
CountDownLatch lost_latch(1);
CountDownLatch fetch_latch(1);
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&found_latch](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_FALSE(fast_advertisement);
found_latch.CountDown();
},
.peripheral_lost_cb =
[&lost_latch](BlePeripheral& peripheral,
const std::string& service_id) {
lost_latch.CountDown();
},
},
"");
api::ble_v2::BleAdvertisementData advertisement_data;
if (!advertisement_header_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kCopresenceServiceUuid));
advertisement_data.service_data.insert(
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
}
FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch);
// We should receive a client callback of a peripheral discovery.
fetch_latch.Await(kWaitDuration);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1);
// Then, go through two cycles of onLost. The first cycle should include the
// recently discovered eripheral in its 'found' pool. The second one should
// trigger the onLost callback.
discovered_peripheral_tracker_.ProcessLostGattAdvertisements();
discovered_peripheral_tracker_.ProcessLostGattAdvertisements();
// We should receive a client callback of a lost peripheral
EXPECT_TRUE(lost_latch.Await(kWaitDuration).result());
}
TEST_F(DiscoveredPeripheralTrackerTest,
LostPeripheralForFastAndGattAdvertisementLost) {
std::vector<std::string> service_ids = {std::string(kServiceIdB)};
ByteArray advertisement_header_bytes = CreateBleAdvertisementHeader(
GenerateRandomAdvertisementHash(), service_ids);
ByteArray fast_advertisement_bytes = CreateFastBleAdvertisement(
ByteArray(std::string(kData)), ByteArray(std::string(kDeviceToken)));
ByteArray advertisement_bytes = CreateBleAdvertisement(
std::string(kServiceIdB), ByteArray(std::string(kData)),
ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch_a(1);
CountDownLatch found_latch_b(1);
CountDownLatch lost_latch_a(1);
CountDownLatch lost_latch_b(1);
CountDownLatch fetch_latch(1);
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&found_latch_a](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_TRUE(fast_advertisement);
found_latch_a.CountDown();
},
.peripheral_lost_cb =
[&lost_latch_a](BlePeripheral& peripheral,
const std::string& service_id) {
lost_latch_a.CountDown();
},
},
std::string(kFastAdvertisementServiceUuid));
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdB),
{
.peripheral_discovered_cb =
[&found_latch_b](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_FALSE(fast_advertisement);
found_latch_b.CountDown();
},
.peripheral_lost_cb =
[&lost_latch_b](BlePeripheral& peripheral,
const std::string& service_id) {
lost_latch_b.CountDown();
},
},
"");
api::ble_v2::BleAdvertisementData advertisement_data;
if (!advertisement_header_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kCopresenceServiceUuid));
advertisement_data.service_data.insert(
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
}
if (!fast_advertisement_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kFastAdvertisementServiceUuid));
advertisement_data.service_data.insert(
{std::string(kFastAdvertisementServiceUuid), fast_advertisement_bytes});
}
FindFastAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch);
// We should receive two separate peripheral discoveries.
fetch_latch.Await(kWaitDuration);
EXPECT_TRUE(found_latch_a.Await(kWaitDuration).result());
EXPECT_TRUE(found_latch_b.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1);
// Then, go through two cycles of onLost. The first cycle should include the
// recently discovered peripheral in its 'found' pool. The second one should
// trigger the onLost callback.
discovered_peripheral_tracker_.ProcessLostGattAdvertisements();
discovered_peripheral_tracker_.ProcessLostGattAdvertisements();
// We should receive two client callbacks of a lost peripheral from each
// service ID.
EXPECT_TRUE(lost_latch_a.Await(kWaitDuration).result());
EXPECT_TRUE(lost_latch_b.Await(kWaitDuration).result());
}
TEST_F(DiscoveredPeripheralTrackerTest,
LostPeripheralNotCallbackForUntrackedServiceId) {
std::vector<std::string> service_ids = {std::string(kServiceIdA)};
ByteArray advertisement_header_bytes = CreateBleAdvertisementHeader(
GenerateRandomAdvertisementHash(), service_ids);
ByteArray advertisement_bytes = CreateBleAdvertisement(
std::string(kServiceIdA), ByteArray(std::string(kData)),
ByteArray(std::string(kDeviceToken)));
CountDownLatch found_latch(1);
CountDownLatch lost_latch(1);
CountDownLatch fetch_latch(1);
discovered_peripheral_tracker_.StartTracking(
std::string(kServiceIdA),
{
.peripheral_discovered_cb =
[&found_latch](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData)));
EXPECT_FALSE(fast_advertisement);
found_latch.CountDown();
},
.peripheral_lost_cb =
[&lost_latch](BlePeripheral& peripheral,
const std::string& service_id) {
lost_latch.CountDown();
},
},
"");
api::ble_v2::BleAdvertisementData advertisement_data;
if (!advertisement_header_bytes.Empty()) {
advertisement_data.service_uuids.insert(
std::string(kCopresenceServiceUuid));
advertisement_data.service_data.insert(
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
}
FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch);
// We should receive a client callback of a peripheral discovery.
fetch_latch.Await(kWaitDuration);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1);
// Then, stop tracking the service ID and go through two cycles of onLost.
discovered_peripheral_tracker_.StopTracking(std::string(kServiceIdA));
discovered_peripheral_tracker_.ProcessLostGattAdvertisements();
discovered_peripheral_tracker_.ProcessLostGattAdvertisements();
// We should NOT receive a client callback of a lost peripheral
EXPECT_FALSE(lost_latch.Await(kWaitDuration).result());
}
} // namespace
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location