mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-15 07:06:11 -04:00
910 lines
33 KiB
C++
910 lines
33 KiB
C++
// 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.h"
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/strings/escaping.h"
|
|
#include "absl/time/time.h"
|
|
#include "absl/types/optional.h"
|
|
#include "connections/implementation/flags/nearby_connections_feature_flags.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 "connections/implementation/mediums/bluetooth_radio.h"
|
|
#include "connections/implementation/mediums/utils.h"
|
|
#include "connections/power_level.h"
|
|
#include "internal/flags/nearby_flags.h"
|
|
#include "internal/platform/byte_array.h"
|
|
#include "internal/platform/cancelable_alarm.h"
|
|
#include "internal/platform/logging.h"
|
|
#include "internal/platform/mutex_lock.h"
|
|
|
|
namespace nearby {
|
|
namespace connections {
|
|
|
|
namespace {
|
|
|
|
using ::nearby::api::ble_v2::BleAdvertisementData;
|
|
using ::nearby::api::ble_v2::GattCharacteristic;
|
|
using ::nearby::api::ble_v2::TxPowerLevel;
|
|
|
|
constexpr int kMaxAdvertisementLength = 512;
|
|
constexpr int kDummyServiceIdLength = 128;
|
|
|
|
// Tell the thread annotation static analysis that `m` is already exclusively
|
|
// locked. Used because the analysis is not interprocedural.
|
|
void AssumeHeld(Mutex& m) ABSL_ASSERT_EXCLUSIVE_LOCK(m) {}
|
|
|
|
} // namespace
|
|
|
|
BleV2::BleV2(BluetoothRadio& radio)
|
|
: radio_(radio), adapter_(radio_.GetBluetoothAdapter()) {}
|
|
|
|
BleV2::~BleV2() {
|
|
// Destructor is not taking locks, but methods it is calling are.
|
|
while (!scanned_service_ids_.empty()) {
|
|
StopScanning(*scanned_service_ids_.begin());
|
|
}
|
|
while (!advertising_infos_.empty()) {
|
|
StopAdvertising(advertising_infos_.begin()->first);
|
|
}
|
|
while (!server_sockets_.empty()) {
|
|
StopAcceptingConnections(server_sockets_.begin()->first);
|
|
}
|
|
|
|
serial_executor_.Shutdown();
|
|
alarm_executor_.Shutdown();
|
|
accept_loops_runner_.Shutdown();
|
|
}
|
|
|
|
bool BleV2::IsAvailable() const {
|
|
MutexLock lock(&mutex_);
|
|
|
|
return IsAvailableLocked();
|
|
}
|
|
|
|
bool BleV2::StartAdvertising(const std::string& service_id,
|
|
const ByteArray& advertisement_bytes,
|
|
PowerLevel power_level,
|
|
bool is_fast_advertisement) {
|
|
MutexLock lock(&mutex_);
|
|
|
|
if (advertisement_bytes.Empty()) {
|
|
NEARBY_LOGS(INFO)
|
|
<< "Refusing to turn on BLE advertising. Empty advertisement data.";
|
|
return false;
|
|
}
|
|
|
|
if (advertisement_bytes.size() > kMaxAdvertisementLength) {
|
|
NEARBY_LOG(INFO,
|
|
"Refusing to start BLE advertising because the advertisement "
|
|
"was too long. Expected at most %d bytes but received %d.",
|
|
kMaxAdvertisementLength, advertisement_bytes.size());
|
|
return false;
|
|
}
|
|
|
|
if (IsAdvertisingLocked(service_id)) {
|
|
NEARBY_LOGS(INFO)
|
|
<< "Failed to BLE advertise because we're already advertising.";
|
|
return false;
|
|
}
|
|
|
|
if (!radio_.IsEnabled()) {
|
|
NEARBY_LOGS(INFO)
|
|
<< "Can't start BLE scanning because Bluetooth was never turned on";
|
|
return false;
|
|
}
|
|
|
|
if (!IsAvailableLocked()) {
|
|
NEARBY_LOGS(INFO) << "Can't turn on BLE advertising. BLE is not available.";
|
|
return false;
|
|
}
|
|
|
|
// Wrap the connections advertisement to the medium advertisement.
|
|
ByteArray service_id_hash = mediums::bleutils::GenerateHash(
|
|
service_id, mediums::BleAdvertisement::kServiceIdHashLength);
|
|
// Get psm value from L2CAP server if L2CAP is supported. Now just use the
|
|
// default value.
|
|
int psm = mediums::BleAdvertisementHeader::kDefaultPsmValue;
|
|
mediums::BleAdvertisement medium_advertisement = {
|
|
mediums::BleAdvertisement::Version::kV2,
|
|
mediums::BleAdvertisement::SocketVersion::kV2,
|
|
/*service_id_hash=*/is_fast_advertisement ? ByteArray{} : service_id_hash,
|
|
advertisement_bytes,
|
|
mediums::bleutils::GenerateDeviceToken(),
|
|
psm};
|
|
if (!medium_advertisement.IsValid()) {
|
|
NEARBY_LOGS(INFO) << "Failed to BLE advertise because we could not wrap a "
|
|
"connection advertisement to medium advertisement.";
|
|
return false;
|
|
}
|
|
|
|
advertising_infos_.insert(
|
|
{service_id,
|
|
AdvertisingInfo{.medium_advertisement = medium_advertisement,
|
|
.power_level = power_level,
|
|
.is_fast_advertisement = is_fast_advertisement}});
|
|
|
|
// Stop the pre-existing BLE advertisement if there is one.
|
|
medium_.StopAdvertising();
|
|
|
|
if (!StartAdvertisingLocked(service_id)) {
|
|
advertising_infos_.erase(service_id);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BleV2::StopAdvertising(const std::string& service_id) {
|
|
MutexLock lock(&mutex_);
|
|
if (!IsAdvertisingLocked(service_id)) {
|
|
NEARBY_LOGS(INFO) << "Cannot stop BLE advertising for service_id="
|
|
<< service_id << " because it never started.";
|
|
return false;
|
|
}
|
|
|
|
// Stop the BLE advertisement. We will restart it later if necessary.
|
|
NEARBY_LOGS(INFO) << "Turned off BLE advertising with service_id="
|
|
<< service_id;
|
|
advertising_infos_.erase(service_id);
|
|
medium_.StopAdvertising();
|
|
|
|
// Remove the GATT advertisements.
|
|
gatt_advertisements_.clear();
|
|
|
|
// Restart the BLE advertisement if there is still an advertiser.
|
|
if (!advertising_infos_.empty()) {
|
|
if (!hosted_gatt_characteristics_.empty()) {
|
|
// Set the value of characteristic to empty if there is still an
|
|
// advertiser.
|
|
ByteArray empty_value = {};
|
|
for (const auto& characteristic : hosted_gatt_characteristics_) {
|
|
if (!gatt_server_->UpdateCharacteristic(characteristic, empty_value)) {
|
|
NEARBY_LOGS(ERROR)
|
|
<< "Failed to clear characteristic uuid="
|
|
<< std::string(characteristic.uuid)
|
|
<< " after stopping BLE advertisement for service_id="
|
|
<< service_id;
|
|
}
|
|
}
|
|
hosted_gatt_characteristics_.clear();
|
|
}
|
|
const std::string& new_service_id = advertising_infos_.begin()->first;
|
|
if (!StartAdvertisingLocked(new_service_id)) {
|
|
NEARBY_LOGS(ERROR)
|
|
<< "Failed to restart BLE advertisement after stopping "
|
|
"BLE advertisement for new service_id="
|
|
<< new_service_id;
|
|
advertising_infos_.erase(new_service_id);
|
|
return false;
|
|
}
|
|
NEARBY_LOGS(INFO) << "Restart BLE advertising with new service_id="
|
|
<< new_service_id;
|
|
} else if (incoming_sockets_.empty()) {
|
|
// Otherwise, if we aren't restarting the BLE advertisement, then shutdown
|
|
// the gatt server if it's not in use.
|
|
NEARBY_LOGS(VERBOSE) << "Aggressively stopping any pre-existing "
|
|
"advertisement GATT servers "
|
|
"because no incoming BLE sockets are connected.";
|
|
StopAdvertisementGattServerLocked();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BleV2::IsAdvertising(const std::string& service_id) const {
|
|
MutexLock lock(&mutex_);
|
|
|
|
return IsAdvertisingLocked(service_id);
|
|
}
|
|
|
|
bool BleV2::StartScanning(const std::string& service_id, PowerLevel power_level,
|
|
DiscoveredPeripheralCallback callback) {
|
|
MutexLock lock(&mutex_);
|
|
|
|
if (service_id.empty()) {
|
|
NEARBY_LOGS(INFO) << "Can not start BLE scanning with empty service id.";
|
|
return false;
|
|
}
|
|
|
|
if (IsScanningLocked(service_id)) {
|
|
NEARBY_LOGS(INFO) << "Cannot start scan of BLE peripherals because "
|
|
"scanning is already in-progress.";
|
|
return false;
|
|
}
|
|
|
|
if (!radio_.IsEnabled()) {
|
|
NEARBY_LOGS(INFO)
|
|
<< "Can't start BLE scanning because Bluetooth is disabled";
|
|
return false;
|
|
}
|
|
|
|
if (!IsAvailableLocked()) {
|
|
NEARBY_LOGS(INFO)
|
|
<< "Can't scan BLE peripherals because BLE isn't available.";
|
|
return false;
|
|
}
|
|
|
|
// Start to track the advertisement found for specific `service_id`.
|
|
discovered_peripheral_tracker_.StartTracking(
|
|
service_id, std::move(callback),
|
|
mediums::bleutils::kCopresenceServiceUuid);
|
|
|
|
// Check if scan has been activated, if yes, no need to notify client
|
|
// to scan again.
|
|
if (!scanned_service_ids_.empty()) {
|
|
scanned_service_ids_.insert(service_id);
|
|
NEARBY_LOGS(INFO) << "Turned on BLE scanning with service id=" << service_id
|
|
<< " without start client scanning";
|
|
return true;
|
|
}
|
|
|
|
scanned_service_ids_.insert(service_id);
|
|
// TODO(b/213835576): We should re-start scanning once the power level is
|
|
// changed.
|
|
if (!medium_.StartScanning(
|
|
mediums::bleutils::kCopresenceServiceUuid,
|
|
PowerLevelToTxPowerLevel(power_level),
|
|
{
|
|
.advertisement_found_cb =
|
|
[this](BleV2Peripheral peripheral,
|
|
BleAdvertisementData advertisement_data) {
|
|
RunOnBleThread([this, peripheral = std::move(peripheral),
|
|
advertisement_data]() {
|
|
MutexLock lock(&mutex_);
|
|
discovered_peripheral_tracker_
|
|
.ProcessFoundBleAdvertisement(
|
|
std::move(peripheral), advertisement_data,
|
|
{
|
|
.fetch_advertisements =
|
|
[&](BleV2Peripheral peripheral,
|
|
int num_slots, int psm,
|
|
const std::vector<std::string>&
|
|
interesting_service_ids,
|
|
mediums::AdvertisementReadResult&
|
|
advertisement_read_result) {
|
|
// Th`mutex_` is already held here. Use
|
|
// `AssumeHeld` tell the thread
|
|
// annotation static analysis that
|
|
// `mutex_` is already exclusively
|
|
// locked.
|
|
AssumeHeld(mutex_);
|
|
ProcessFetchGattAdvertisementsRequest(
|
|
std::move(peripheral), num_slots,
|
|
psm, interesting_service_ids,
|
|
advertisement_read_result);
|
|
},
|
|
});
|
|
});
|
|
},
|
|
})) {
|
|
NEARBY_LOGS(INFO) << "Failed to start scan of BLE services.";
|
|
discovered_peripheral_tracker_.StopTracking(service_id);
|
|
// Erase the service id that is just added.
|
|
scanned_service_ids_.erase(service_id);
|
|
return false;
|
|
}
|
|
|
|
absl::Duration peripheral_lost_timeout =
|
|
absl::Milliseconds(NearbyFlags::GetInstance().GetInt64Flag(
|
|
config_package_nearby::nearby_connections_feature::
|
|
kBlePeripheralLostTimeoutMillis));
|
|
// Set up lost alarm.
|
|
lost_alarm_ = std::make_unique<CancelableAlarm>(
|
|
"BLE.StartScanning() onLost",
|
|
[this]() {
|
|
MutexLock lock(&mutex_);
|
|
discovered_peripheral_tracker_.ProcessLostGattAdvertisements();
|
|
},
|
|
peripheral_lost_timeout, &alarm_executor_, /*is_recurring=*/true);
|
|
|
|
NEARBY_LOGS(INFO) << "Turned on BLE scanning with service id=" << service_id;
|
|
return true;
|
|
}
|
|
|
|
bool BleV2::StopScanning(const std::string& service_id) {
|
|
MutexLock lock(&mutex_);
|
|
|
|
if (!IsScanningLocked(service_id)) {
|
|
NEARBY_LOGS(INFO) << "Can't turn off BLE scanning because we never "
|
|
"started scanning.";
|
|
return false;
|
|
}
|
|
|
|
discovered_peripheral_tracker_.StopTracking(service_id);
|
|
NEARBY_LOGS(INFO) << "Turned off BLE scanning with service id=" << service_id;
|
|
scanned_service_ids_.erase(service_id);
|
|
|
|
// If still has scanner, don't stop the client scanning.
|
|
if (!scanned_service_ids_.empty()) {
|
|
return true;
|
|
}
|
|
|
|
// If no more scanning activities, then stop client scanning.
|
|
NEARBY_LOGS(INFO) << "Turned off BLE client scanning";
|
|
if (lost_alarm_->IsValid()) {
|
|
lost_alarm_->Cancel();
|
|
}
|
|
return medium_.StopScanning();
|
|
}
|
|
|
|
bool BleV2::IsScanning(const std::string& service_id) const {
|
|
MutexLock lock(&mutex_);
|
|
|
|
return IsScanningLocked(service_id);
|
|
}
|
|
|
|
bool BleV2::StartAcceptingConnections(const std::string& service_id,
|
|
AcceptedConnectionCallback callback) {
|
|
MutexLock lock(&mutex_);
|
|
|
|
if (service_id.empty()) {
|
|
NEARBY_LOGS(INFO)
|
|
<< "Refusing to start accepting BLE connections with empty service id.";
|
|
return false;
|
|
}
|
|
|
|
if (IsAcceptingConnectionsLocked(service_id)) {
|
|
NEARBY_LOGS(INFO)
|
|
<< "Refusing to start accepting BLE connections for " << service_id
|
|
<< " because another BLE peripheral socket is already in-progress.";
|
|
return false;
|
|
}
|
|
|
|
if (!radio_.IsEnabled()) {
|
|
NEARBY_LOGS(INFO) << "Can't start accepting BLE connections for "
|
|
<< service_id << " because Bluetooth isn't enabled.";
|
|
return false;
|
|
}
|
|
|
|
if (!IsAvailableLocked()) {
|
|
NEARBY_LOGS(INFO) << "Can't start accepting BLE connections for "
|
|
<< service_id << " because BLE isn't available.";
|
|
return false;
|
|
}
|
|
|
|
BleV2ServerSocket server_socket = medium_.OpenServerSocket(service_id);
|
|
if (!server_socket.IsValid()) {
|
|
NEARBY_LOGS(INFO)
|
|
<< "Failed to start accepting Ble connections for service_id="
|
|
<< service_id;
|
|
return false;
|
|
}
|
|
|
|
// Mark the fact that there's an in-progress Ble server accepting
|
|
// connections.
|
|
auto owned_server_socket =
|
|
server_sockets_.insert({service_id, std::move(server_socket)})
|
|
.first->second;
|
|
|
|
// Start the accept loop on a dedicated thread - this stays alive and
|
|
// listening for new incoming connections until StopAcceptingConnections() is
|
|
// invoked.
|
|
accept_loops_runner_.Execute(
|
|
"ble-accept",
|
|
[this, service_id = service_id, callback = std::move(callback),
|
|
server_socket = std::move(owned_server_socket)]() mutable {
|
|
while (true) {
|
|
BleV2Socket client_socket = server_socket.Accept();
|
|
if (!client_socket.IsValid()) {
|
|
NEARBY_LOGS(WARNING) << "The client socket to accept is invalid.";
|
|
server_socket.Close();
|
|
break;
|
|
}
|
|
{
|
|
MutexLock lock(&mutex_);
|
|
client_socket.SetCloseNotifier([this, service_id]() {
|
|
MutexLock lock(&mutex_);
|
|
incoming_sockets_.erase(service_id);
|
|
});
|
|
incoming_sockets_.insert({service_id, client_socket});
|
|
}
|
|
callback.accepted_cb(std::move(client_socket), service_id);
|
|
}
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BleV2::StopAcceptingConnections(const std::string& service_id) {
|
|
MutexLock lock(&mutex_);
|
|
|
|
const auto it = server_sockets_.find(service_id);
|
|
if (it == server_sockets_.end()) {
|
|
NEARBY_LOGS(INFO)
|
|
<< "Can't stop accepting BLE connections because it was never started.";
|
|
return false;
|
|
}
|
|
|
|
// Closing the BleServerSocket will kick off the suicide of the thread
|
|
// in accept_loops_thread_pool_ that blocks on BleServerSocket.accept().
|
|
// That may take some time to complete, but there's no particular reason to
|
|
// wait around for it.
|
|
auto item = server_sockets_.extract(it);
|
|
|
|
// Store a handle to the BleServerSocket, so we can use it after
|
|
// removing the entry from server_sockets_; making it scoped
|
|
// is a bonus that takes care of deallocation before we leave this method.
|
|
BleV2ServerSocket& listening_socket = item.mapped();
|
|
|
|
// Regardless of whether or not we fail to close the existing
|
|
// BleServerSocket, remove it from server_sockets_ so that it
|
|
// frees up this service for another round.
|
|
|
|
// Finally, close the BleServerSocket.
|
|
if (!listening_socket.Close().Ok()) {
|
|
NEARBY_LOGS(INFO) << "Failed to close Ble server socket for service_id="
|
|
<< service_id;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BleV2::IsAcceptingConnections(const std::string& service_id) {
|
|
MutexLock lock(&mutex_);
|
|
return IsAcceptingConnectionsLocked(service_id);
|
|
}
|
|
|
|
BleV2Socket BleV2::Connect(const std::string& service_id,
|
|
const BleV2Peripheral& peripheral,
|
|
CancellationFlag* cancellation_flag) {
|
|
MutexLock lock(&mutex_);
|
|
// Socket to return. To allow for NRVO to work, it has to be a single object.
|
|
BleV2Socket socket;
|
|
|
|
if (service_id.empty()) {
|
|
NEARBY_LOGS(INFO) << "Refusing to create client Ble socket because "
|
|
"service_id is empty.";
|
|
return socket;
|
|
}
|
|
|
|
if (!IsAvailableLocked()) {
|
|
NEARBY_LOGS(INFO) << "Can't create client Ble socket [service_id="
|
|
<< service_id << "]; Ble isn't available.";
|
|
return socket;
|
|
}
|
|
|
|
if (cancellation_flag->Cancelled()) {
|
|
NEARBY_LOGS(INFO) << "Can't create client Ble socket due to cancel.";
|
|
return socket;
|
|
}
|
|
|
|
socket = medium_.Connect(service_id,
|
|
PowerLevelToTxPowerLevel(PowerLevel::kHighPower),
|
|
peripheral, cancellation_flag);
|
|
if (!socket.IsValid()) {
|
|
NEARBY_LOGS(INFO) << "Failed to Connect via Ble [service_id=" << service_id
|
|
<< "]";
|
|
}
|
|
|
|
return socket;
|
|
}
|
|
|
|
bool BleV2::IsAvailableLocked() const { return medium_.IsValid(); }
|
|
|
|
bool BleV2::IsAdvertisingLocked(const std::string& service_id) const {
|
|
return advertising_infos_.contains(service_id);
|
|
}
|
|
|
|
bool BleV2::IsScanningLocked(const std::string& service_id) const {
|
|
return scanned_service_ids_.contains(service_id);
|
|
}
|
|
|
|
bool BleV2::IsAcceptingConnectionsLocked(const std::string& service_id) {
|
|
return server_sockets_.contains(service_id);
|
|
}
|
|
|
|
bool BleV2::IsAdvertisementGattServerRunningLocked() {
|
|
return gatt_server_ && gatt_server_->IsValid();
|
|
}
|
|
|
|
bool BleV2::StartAdvertisementGattServerLocked(
|
|
const std::string& service_id, const ByteArray& gatt_advertisement) {
|
|
if (IsAdvertisementGattServerRunningLocked()) {
|
|
NEARBY_LOGS(INFO) << "Advertisement GATT server is not started because one "
|
|
"is already running.";
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<GattServer> gatt_server =
|
|
medium_.StartGattServer(/*ServerGattConnectionCallback=*/{});
|
|
if (!gatt_server || !gatt_server->IsValid()) {
|
|
NEARBY_LOGS(INFO) << "Unable to start an advertisement GATT server.";
|
|
return false;
|
|
}
|
|
|
|
if (!GenerateAdvertisementCharacteristic(
|
|
/*slot=*/0, gatt_advertisement, *gatt_server)) {
|
|
gatt_server->Stop();
|
|
return false;
|
|
}
|
|
|
|
// Insert the advertisements into their open advertisement slots.
|
|
gatt_advertisements_.insert(
|
|
{/*slot=*/0, std::make_pair(service_id, gatt_advertisement)});
|
|
|
|
gatt_server_ = std::move(gatt_server);
|
|
return true;
|
|
}
|
|
|
|
bool BleV2::GenerateAdvertisementCharacteristic(
|
|
int slot, const ByteArray& gatt_advertisement, GattServer& gatt_server) {
|
|
GattCharacteristic::Permission permission =
|
|
GattCharacteristic::Permission::kRead;
|
|
GattCharacteristic::Property property = GattCharacteristic::Property::kRead;
|
|
|
|
// NOLINTNEXTLINE(google3-legacy-absl-backports)
|
|
absl::optional<Uuid> advertiement_uuid =
|
|
mediums::bleutils::GenerateAdvertisementUuid(slot);
|
|
if (!advertiement_uuid.has_value()) {
|
|
NEARBY_LOGS(INFO) << "Unable to generate advertisement uuid.";
|
|
return false;
|
|
}
|
|
// NOLINTNEXTLINE(google3-legacy-absl-backports)
|
|
absl::optional<GattCharacteristic> gatt_characteristic =
|
|
gatt_server.CreateCharacteristic(
|
|
mediums::bleutils::kCopresenceServiceUuid, *advertiement_uuid,
|
|
permission, property);
|
|
if (!gatt_characteristic.has_value()) {
|
|
NEARBY_LOGS(INFO) << "Unable to create and add a characterstic to the gatt "
|
|
"server for the advertisement.";
|
|
return false;
|
|
}
|
|
if (!gatt_server.UpdateCharacteristic(gatt_characteristic.value(),
|
|
gatt_advertisement)) {
|
|
NEARBY_LOGS(INFO) << "Unable to write a value to the GATT characteristic.";
|
|
return false;
|
|
}
|
|
hosted_gatt_characteristics_.insert(gatt_characteristic.value());
|
|
|
|
return true;
|
|
}
|
|
|
|
void BleV2::ProcessFetchGattAdvertisementsRequest(
|
|
BleV2Peripheral peripheral, int num_slots, int psm,
|
|
const std::vector<std::string>& interesting_service_ids,
|
|
mediums::AdvertisementReadResult& advertisement_read_result) {
|
|
if (!peripheral.IsValid()) {
|
|
NEARBY_LOGS(INFO) << "Can't read from an advertisement GATT server because "
|
|
"ble peripheral is null.";
|
|
return;
|
|
}
|
|
|
|
if (!radio_.IsEnabled()) {
|
|
NEARBY_LOGS(INFO) << "Can't read from an advertisement GATT server because "
|
|
"Bluetooth was never turned on.";
|
|
return;
|
|
}
|
|
|
|
if (!IsAvailableLocked()) {
|
|
NEARBY_LOGS(INFO) << "Can't read from an advertisement GATT server because "
|
|
"BLE is not available.";
|
|
return;
|
|
}
|
|
|
|
// Connect to a GATT server, reads advertisement data, and then disconnect
|
|
// from the GATT server.
|
|
bool read_success = true;
|
|
std::unique_ptr<GattClient> gatt_client = medium_.ConnectToGattServer(
|
|
std::move(peripheral), PowerLevelToTxPowerLevel(PowerLevel::kHighPower),
|
|
/*ClientGattConnectionCallback=*/{});
|
|
if (!gatt_client || !gatt_client->IsValid()) {
|
|
advertisement_read_result.RecordLastReadStatus(false);
|
|
return;
|
|
}
|
|
|
|
// Collect service_uuid and its associated characteristic_uuids.
|
|
absl::flat_hash_map<int, Uuid> slot_characteristic_uuids = {};
|
|
for (int slot = 0; slot < num_slots; ++slot) {
|
|
// Make sure we haven't already read this advertisement before.
|
|
if (advertisement_read_result.HasAdvertisement(slot)) {
|
|
continue;
|
|
}
|
|
|
|
// Make sure the characteristic even exists for this slot number. If
|
|
// the characteristic doesn't exist, we shouldn't count the fetch as a
|
|
// failure because there's nothing we could've done about a
|
|
// non-existed characteristic.
|
|
// NOLINTNEXTLINE(google3-legacy-absl-backports)
|
|
absl::optional<Uuid> advertiement_uuid =
|
|
mediums::bleutils::GenerateAdvertisementUuid(slot);
|
|
if (!advertiement_uuid.has_value()) {
|
|
continue;
|
|
}
|
|
slot_characteristic_uuids.insert({slot, *advertiement_uuid});
|
|
}
|
|
if (slot_characteristic_uuids.empty()) {
|
|
// TODO(b/222392304): More test coverage.
|
|
NEARBY_LOGS(WARNING) << "GATT client doesn't have characteristics.";
|
|
advertisement_read_result.RecordLastReadStatus(false);
|
|
return;
|
|
}
|
|
|
|
// Discover service and characteristics.
|
|
std::vector<Uuid> characteristic_uuids;
|
|
std::transform(slot_characteristic_uuids.begin(),
|
|
slot_characteristic_uuids.end(),
|
|
std::back_inserter(characteristic_uuids),
|
|
[](auto& kv) { return kv.second; });
|
|
if (!gatt_client->DiscoverServiceAndCharacteristics(
|
|
mediums::bleutils::kCopresenceServiceUuid, characteristic_uuids)) {
|
|
// TODO(b/222392304): More test coverage.
|
|
NEARBY_LOGS(WARNING) << "GATT client doesn't have characteristics.";
|
|
advertisement_read_result.RecordLastReadStatus(false);
|
|
return;
|
|
}
|
|
|
|
// Read all advertisements from all characteristics that we haven't read from
|
|
// yet.
|
|
for (const auto& it : slot_characteristic_uuids) {
|
|
int slot = it.first;
|
|
Uuid characteristic_uuid = it.second;
|
|
auto gatt_characteristic = gatt_client->GetCharacteristic(
|
|
mediums::bleutils::kCopresenceServiceUuid, characteristic_uuid);
|
|
if (!gatt_characteristic.has_value()) {
|
|
continue;
|
|
}
|
|
|
|
// Read advertisement data from the characteristic associated with this
|
|
// slot.
|
|
auto characteristic_byte =
|
|
gatt_client->ReadCharacteristic(gatt_characteristic.value());
|
|
if (characteristic_byte.has_value()) {
|
|
advertisement_read_result.AddAdvertisement(
|
|
slot, ByteArray(characteristic_byte.value()));
|
|
NEARBY_LOGS(VERBOSE) << "Successfully read advertisement at slot="
|
|
<< slot;
|
|
} else {
|
|
NEARBY_LOGS(WARNING) << "Can't read advertisement for slot=" << slot;
|
|
read_success = false;
|
|
}
|
|
// Whether or not we succeeded with this slot, we should try reading the
|
|
// other slots to get as many advertisements as possible before
|
|
// returning a success or failure.
|
|
}
|
|
gatt_client->Disconnect();
|
|
|
|
advertisement_read_result.RecordLastReadStatus(read_success);
|
|
}
|
|
|
|
bool BleV2::StopAdvertisementGattServerLocked() {
|
|
if (!IsAdvertisementGattServerRunningLocked()) {
|
|
NEARBY_LOGS(INFO) << "Unable to stop the advertisement GATT server because "
|
|
"it's not running.";
|
|
return false;
|
|
}
|
|
|
|
gatt_server_.reset();
|
|
return true;
|
|
}
|
|
|
|
ByteArray BleV2::CreateAdvertisementHeader(
|
|
int psm, bool extended_advertisement_advertised) {
|
|
// Create a randomized dummy service id to anonymize the header with.
|
|
ByteArray dummy_service_id_bytes =
|
|
Utils::GenerateRandomBytes(kDummyServiceIdLength);
|
|
std::string dummy_service_id{dummy_service_id_bytes};
|
|
|
|
mediums::BloomFilter bloom_filter(
|
|
std::make_unique<mediums::BitSetImpl<
|
|
mediums::BleAdvertisementHeader::kServiceIdBloomFilterByteLength>>());
|
|
bloom_filter.Add(dummy_service_id);
|
|
|
|
ByteArray advertisement_hash =
|
|
mediums::bleutils::GenerateAdvertisementHash(dummy_service_id_bytes);
|
|
for (const auto& item : gatt_advertisements_) {
|
|
const std::string& service_id = item.second.first;
|
|
const ByteArray& gatt_advertisement = item.second.second;
|
|
bloom_filter.Add(service_id);
|
|
|
|
// Compute the next hash.
|
|
std::string advertisement_bodies = absl::StrCat(
|
|
advertisement_hash.AsStringView(), gatt_advertisement.AsStringView());
|
|
|
|
advertisement_hash = mediums::bleutils::GenerateAdvertisementHash(
|
|
ByteArray(std::move(advertisement_bodies)));
|
|
}
|
|
|
|
return ByteArray(mediums::BleAdvertisementHeader(
|
|
mediums::BleAdvertisementHeader::Version::kV2,
|
|
extended_advertisement_advertised,
|
|
/*num_slots=*/gatt_advertisements_.size(), ByteArray(bloom_filter),
|
|
advertisement_hash, psm));
|
|
}
|
|
|
|
bool BleV2::StartAdvertisingLocked(const std::string& service_id) {
|
|
const auto it = advertising_infos_.find(service_id);
|
|
if (it == advertising_infos_.end()) {
|
|
NEARBY_LOGS(WARNING) << "Failed to BLE advertise with service_id="
|
|
<< service_id;
|
|
return false;
|
|
}
|
|
|
|
const AdvertisingInfo& info = it->second;
|
|
if (info.is_fast_advertisement) {
|
|
return StartFastAdvertisingLocked(info.power_level,
|
|
info.medium_advertisement);
|
|
} else {
|
|
return StartRegularAdvertisingLocked(service_id, info.power_level,
|
|
info.medium_advertisement);
|
|
}
|
|
}
|
|
|
|
bool BleV2::StartFastAdvertisingLocked(
|
|
PowerLevel power_level,
|
|
const mediums::BleAdvertisement& medium_advertisement) {
|
|
// Begin building the fast BLE advertisement.
|
|
BleAdvertisementData advertising_data;
|
|
ByteArray medium_advertisement_bytes = ByteArray(medium_advertisement);
|
|
advertising_data.is_extended_advertisement = false;
|
|
advertising_data.service_data.insert(
|
|
{mediums::bleutils::kCopresenceServiceUuid, medium_advertisement_bytes});
|
|
|
|
// Finally, start the fast advertising operation.
|
|
if (!medium_.StartAdvertising(
|
|
advertising_data,
|
|
{.tx_power_level = PowerLevelToTxPowerLevel(power_level),
|
|
.is_connectable = true})) {
|
|
NEARBY_LOGS(ERROR) << "Failed to turn on BLE fast advertising with "
|
|
"advertisement bytes="
|
|
<< absl::BytesToHexString(
|
|
medium_advertisement_bytes.data());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BleV2::StartRegularAdvertisingLocked(
|
|
const std::string& service_id, PowerLevel power_level,
|
|
const mediums::BleAdvertisement& medium_advertisement) {
|
|
// Begin building the regular BLE advertisement.
|
|
BleAdvertisementData advertising_data;
|
|
ByteArray medium_advertisement_bytes =
|
|
medium_.IsExtendedAdvertisementsAvailable()
|
|
? medium_advertisement.ByteArrayWithExtraField()
|
|
: ByteArray(medium_advertisement);
|
|
|
|
// Start extended advertisement first if available.
|
|
bool extended_regular_advertisement_success = false;
|
|
if (medium_.IsExtendedAdvertisementsAvailable()) {
|
|
advertising_data.is_extended_advertisement = true;
|
|
advertising_data.service_data.insert(
|
|
{mediums::bleutils::kCopresenceServiceUuid,
|
|
medium_advertisement_bytes});
|
|
|
|
// Start the extended regular advertising operation.
|
|
extended_regular_advertisement_success = medium_.StartAdvertising(
|
|
advertising_data,
|
|
{.tx_power_level = PowerLevelToTxPowerLevel(power_level),
|
|
.is_connectable = true});
|
|
if (!extended_regular_advertisement_success) {
|
|
NEARBY_LOGS(ERROR)
|
|
<< "Failed to turn on BLE extended regular advertising with "
|
|
"advertisement bytes="
|
|
<< absl::BytesToHexString(medium_advertisement_bytes.data());
|
|
}
|
|
}
|
|
|
|
// Start GATT advertisement no matter extended advertisment succeeded or not.
|
|
// This is to ensure that legacy devices which don't support extended
|
|
// advertisement can get the advertisement via GATT connection.
|
|
bool gatt_advertisement_success = StartGattAdvertisingLocked(
|
|
service_id, power_level, medium_advertisement.GetPsm(),
|
|
medium_advertisement_bytes, extended_regular_advertisement_success);
|
|
|
|
return extended_regular_advertisement_success || gatt_advertisement_success;
|
|
}
|
|
|
|
bool BleV2::StartGattAdvertisingLocked(
|
|
const std::string& service_id, PowerLevel power_level, int psm,
|
|
const ByteArray& medium_advertisement_bytes,
|
|
bool extended_advertisement_advertised) {
|
|
// Begin building the GATT BLE advertisement header.
|
|
BleAdvertisementData advertising_data;
|
|
advertising_data.is_extended_advertisement = false;
|
|
|
|
// Stop the current advertisement GATT server if there are no incoming
|
|
// sockets connected to this device.
|
|
//
|
|
// The reason for aggressively restarting a GATT server is to make sure this
|
|
// class is not using a stale server object that may not be actually running
|
|
// anymore (possibly due to Bluetooth being turned off).
|
|
//
|
|
// Changing one's GATT server while a remote device is connected to it leads
|
|
// to a loss of GATT callbacks for that remote device. The only time a
|
|
// remote device is indefinitely connected to this device's GATT server is
|
|
// when it has a BLE socket connection.
|
|
if (incoming_sockets_.empty()) {
|
|
NEARBY_LOGS(VERBOSE)
|
|
<< "Aggressively stopping any pre-existing advertisement GATT "
|
|
"servers because no incoming BLE sockets are connected";
|
|
StopAdvertisementGattServerLocked();
|
|
}
|
|
|
|
// Start a GATT server to deliver the full advertisement data. If fail to
|
|
// advertise the header, we must shut this down before the method returns.
|
|
if (!IsAdvertisementGattServerRunningLocked()) {
|
|
if (!StartAdvertisementGattServerLocked(service_id,
|
|
medium_advertisement_bytes)) {
|
|
NEARBY_LOGS(ERROR)
|
|
<< "Failed to turn on BLE GATT advertising for service_id="
|
|
<< service_id
|
|
<< " because the advertisement GATT server failed to start.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Begin building the regular BLE advertisement (backed by a GATT server).
|
|
// Add the advertisement header.
|
|
ByteArray advertisement_header_bytes =
|
|
CreateAdvertisementHeader(psm, extended_advertisement_advertised);
|
|
if (advertisement_header_bytes.Empty()) {
|
|
NEARBY_LOGS(ERROR)
|
|
<< "Failed to turn on BLE GATT advertising because we could not "
|
|
"create an advertisement header.";
|
|
// Failed to create an advertisement header, so stop the advertisement
|
|
// GATT server.
|
|
StopAdvertisementGattServerLocked();
|
|
return false;
|
|
}
|
|
|
|
advertising_data.service_data.insert(
|
|
{mediums::bleutils::kCopresenceServiceUuid, advertisement_header_bytes});
|
|
|
|
// Finally, start the regular advertising operation.
|
|
if (!medium_.StartAdvertising(
|
|
advertising_data,
|
|
{.tx_power_level = PowerLevelToTxPowerLevel(power_level),
|
|
.is_connectable = true})) {
|
|
NEARBY_LOGS(ERROR) << "Failed to turn on BLE GATT advertising with "
|
|
"advertisement bytes="
|
|
<< absl::BytesToHexString(
|
|
medium_advertisement_bytes.data());
|
|
// If BLE advertising was not successful, stop the advertisement GATT
|
|
// server.
|
|
StopAdvertisementGattServerLocked();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TxPowerLevel BleV2::PowerLevelToTxPowerLevel(PowerLevel power_level) {
|
|
switch (power_level) {
|
|
case PowerLevel::kHighPower:
|
|
return TxPowerLevel::kHigh;
|
|
case PowerLevel::kLowPower:
|
|
// Medium power is about the size of a conference room.
|
|
// Any lower and we won't be visible at a distance.
|
|
return TxPowerLevel::kMedium;
|
|
default:
|
|
return TxPowerLevel::kUnknown;
|
|
}
|
|
}
|
|
|
|
void BleV2::RunOnBleThread(Runnable runnable) {
|
|
serial_executor_.Execute(std::move(runnable));
|
|
}
|
|
|
|
} // namespace connections
|
|
} // namespace nearby
|