Improve BLE Peripheral Management and Socket Closure on Apple.

PiperOrigin-RevId: 805438865
This commit is contained in:
Guogang Li
2025-09-10 11:14:14 -07:00
committed by Copybara-Service
parent 1093ee2794
commit 3c9f6ecdd3
7 changed files with 106 additions and 21 deletions
@@ -96,6 +96,18 @@
- (void)addPeripheralServiceManager:(GNSPeripheralServiceManager *)peripheralServiceManager
bleServiceAddedCompletion:(GNSErrorHandler)completion;
/**
* Removes a peripheral service manager from the managed services. It is safe to call this method
* even if the service is not added to the BLE service database. If the GNSPeripheralManager
* is started, the peripheral service manager will be removed right away from BLE service
* database. |completion| is called when the method is done.
*
* @param serviceUUID The service UUID to remove.
* @param completion Callback called when the service was removed.
*/
- (void)removePeripheralServiceManagerForServiceUUID:(CBUUID *)serviceUUID
bleServiceRemovedCompletion:(GNSErrorHandler)completion;
/**
* If the bluetooth is on, all CB services will be added, and the services will be advertised
* (according to -[GNSPeripheralServiceManager advertising]. Otherwise, it will be done as soon as
@@ -143,11 +143,13 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f;
GNCLoggerInfo(@"Peripheral manager already stopped.");
return;
}
GNCLoggerInfo(@"Peripheral manager stopped.");
_started = NO;
[self removeAllBleServicesAndStopAdvertising];
[self removeAllBleServices];
_cbPeripheralManager.delegate = nil;
_cbPeripheralManager = nil;
GNCLoggerInfo(@"Peripheral manager stopped.");
}
- (NSString *)description {
@@ -199,8 +201,23 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f;
[self updateAdvertisedServices];
}
- (void)removeAllBleServicesAndStopAdvertising {
[_cbPeripheralManager stopAdvertising];
- (void)removePeripheralServiceManagerForServiceUUID:(CBUUID *)serviceUUID
bleServiceRemovedCompletion:(GNSErrorHandler)completion {
GNSPeripheralServiceManager *peripheralServiceManager =
[_peripheralServiceManagers objectForKey:serviceUUID];
if (peripheralServiceManager == nil) {
completion(nil);
return;
}
[_cbPeripheralManager removeService:peripheralServiceManager.cbService];
[_peripheralServiceManagers removeObjectForKey:serviceUUID];
[peripheralServiceManager didRemoveCBService];
[self updateAdvertisedServices];
completion(nil);
}
- (void)removeAllBleServices {
_advertisementInProgressData = nil;
_advertisementData = nil;
@@ -395,15 +412,15 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f;
// As instructed by Apple enginners, clean-up all internal state when CoreBluetooth is
// resetting.
[self updateBTCrashLoopHeuristic];
[self removeAllBleServicesAndStopAdvertising];
[self removeAllBleServices];
break;
case CBManagerStateUnknown:
// Clean-up all internal state when the CoreBluetooth state is unknown.
[self removeAllBleServicesAndStopAdvertising];
[self removeAllBleServices];
break;
case CBManagerStateUnauthorized:
// Clean-up all internal state if the application is not authorized to use Bluetooth.
[self removeAllBleServicesAndStopAdvertising];
[self removeAllBleServices];
break;
case CBManagerStateUnsupported:
// The application should have never attempted to start advertising if Bluetooth Low Energy
@@ -381,17 +381,25 @@ static CBMutableCharacteristic *CreatePairingCharacteristic() {
__typeof__(weakSelf) strongSelf = weakSelf;
if (!strongSelf || (checkConnected && !socket.isConnected)) {
// Socket is gone or disconnected; don't reschedule.
if (completion) {
completion();
}
return YES;
}
if (self.cbServiceState == GNSBluetoothServiceStateNotAdded) {
if (completion) {
dispatch_async(_queue, completion);
}
return YES;
}
if (![strongSelf.peripheralManager updateOutgoingCharacteristic:data
onSocket:socket]) {
GNCLoggerInfo(@"Failed to update characteristic value; reschedule");
return NO;
}
if (completion) {
dispatch_async(_queue, ^{
completion();
});
dispatch_async(_queue, completion);
}
return YES;
}];
@@ -445,6 +453,15 @@ static CBMutableCharacteristic *CreatePairingCharacteristic() {
[_peripheralManager
updateOutgoingCharOnSocket:socket
withHandler:^{
if (self.cbServiceState == GNSBluetoothServiceStateNotAdded) {
if (completion) {
dispatch_async(_queue, ^{
completion(nil);
});
}
return YES;
}
BOOL wasSent = [_peripheralManager updateOutgoingCharacteristic:data
onSocket:socket];
if (wasSent) {
@@ -235,6 +235,8 @@ class BleMedium : public api::ble_v2::BleMedium {
absl::Mutex l2cap_server_socket_mutex_;
BleL2capServerSocket *l2cap_server_socket_ptr_ = nullptr;
dispatch_queue_t connection_callback_queue_ = nullptr;
};
} // namespace apple
@@ -54,6 +54,8 @@
#import "internal/platform/implementation/apple/utils.h"
static NSString *const kWeaveServiceUUID = @"FEF3";
static const char *const kConnectionCallbackQueueLabel =
"com.google.nearby.ble_medium.connection_callback_queue";
// Timeout for BLE operations that are initiated by a call to a public API.
static const UInt8 kApiTimeoutInSeconds = 12;
@@ -82,7 +84,10 @@ NSString *ConvertDataToHexString(NSData *data) {
} // namespace
BleMedium::BleMedium() : medium_([[GNCBLEMedium alloc] init]) {}
BleMedium::BleMedium() : medium_([[GNCBLEMedium alloc] init]) {
connection_callback_queue_ =
dispatch_queue_create(kConnectionCallbackQueueLabel, DISPATCH_QUEUE_SERIAL);
}
// ble_medium.mm
BleMedium::~BleMedium() { [medium_ stop]; }
@@ -115,8 +120,6 @@ bool BleMedium::StartAdvertising(const api::ble_v2::BleAdvertisementData &advert
NSMutableDictionary<CBUUID *, NSData *> *serviceData =
ObjCServiceDataFromCPP(advertising_data.service_data);
[socketPeripheralManager_ start];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
[medium_ startAdvertisingData:serviceData
@@ -136,8 +139,6 @@ bool BleMedium::StartAdvertising(const api::ble_v2::BleAdvertisementData &advert
}
bool BleMedium::StopAdvertising() {
[socketPeripheralManager_ stop];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
[medium_ stopAdvertisingWithCompletionHandler:^(NSError *error) {
@@ -443,6 +444,19 @@ std::unique_ptr<api::ble_v2::BleServerSocket> BleMedium::OpenServerSocket(
const std::string &service_id) {
auto server_socket = std::make_unique<BleServerSocket>();
__block auto server_socket_ptr = server_socket.get();
if (socketPeripheralManager_) {
[socketPeripheralManager_ stop];
}
socketPeripheralManager_ = [[GNSPeripheralManager alloc] initWithAdvertisedName:nil
restoreIdentifier:nil];
if (socketPeripheralManager_ == nil) {
GNCLoggerError(@"Failed to create peripheral manager.");
return nullptr;
}
socketPeripheralServiceManager_ = [[GNSPeripheralServiceManager alloc]
initWithBleServiceUUID:[CBUUID UUIDWithString:kWeaveServiceUUID]
addPairingCharacteristic:NO
@@ -454,18 +468,27 @@ std::unique_ptr<api::ble_v2::BleServerSocket> BleMedium::OpenServerSocket(
// have a service ID available to us.
serviceID:nil
expectedIntroPacket:YES
callbackQueue:dispatch_get_main_queue()];
callbackQueue:connection_callback_queue_];
auto socket = std::make_unique<BleSocket>(connection);
socket->SetCloseNotifier([socketPeripheralManager = socketPeripheralManager_,
serviceUUID = socketPeripheralServiceManager_.serviceUUID]() {
[socketPeripheralManager
removePeripheralServiceManagerForServiceUUID:serviceUUID
bleServiceRemovedCompletion:^(NSError *_Nullable error) {
GNCLoggerInfo(@"BleSocket is removed peripheral manager.");
}];
});
connection.connectionHandlers = socket->GetInputStream().GetConnectionHandlers();
if (server_socket_ptr) {
server_socket_ptr->Connect(std::move(socket));
}
GNCLoggerInfo(@"BleServerSocket is created with connection");
});
return YES;
}];
socketPeripheralManager_ = [[GNSPeripheralManager alloc] initWithAdvertisedName:nil
restoreIdentifier:nil];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
@@ -528,7 +551,7 @@ std::unique_ptr<api::ble_v2::BleL2capServerSocket> BleMedium::OpenL2capServerSoc
[GNCBLEL2CAPConnection connectionWithStream:stream
serviceID:@(service_id_str.c_str())
incomingConnection:YES
callbackQueue:dispatch_get_main_queue()];
callbackQueue:connection_callback_queue_];
auto socket = std::make_unique<BleL2capSocket>(connection);
{
absl::MutexLock lock(&l2cap_server_socket_mutex_);
@@ -586,7 +609,7 @@ std::unique_ptr<api::ble_v2::BleSocket> BleMedium::Connect(
connectionWithSocket:nssocket
serviceID:@(service_id.c_str())
expectedIntroPacket:NO
callbackQueue:dispatch_get_main_queue()];
callbackQueue:connection_callback_queue_];
socket = std::make_unique<BleSocket>(connection, peripheral_id);
connection.connectionHandlers =
socket->GetInputStream().GetConnectionHandlers();
@@ -631,7 +654,7 @@ std::unique_ptr<api::ble_v2::BleL2capSocket> BleMedium::ConnectOverL2cap(
[GNCBLEL2CAPConnection connectionWithStream:stream
serviceID:@(service_id_str.c_str())
incomingConnection:NO
callbackQueue:dispatch_get_main_queue()];
callbackQueue:connection_callback_queue_];
// Blocked call to wait for the packet validation result.
// TODO: b/419654808 - Remove this once the packet validation is moved to the
// Connections layer.
@@ -120,6 +120,9 @@ class BleSocket : public api::ble_v2::BleSocket {
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
// Sets a notifier that will be called when the socket is closed.
void SetCloseNotifier(absl::AnyInvocable<void()> notifier) ABSL_LOCKS_EXCLUDED(mutex_);
private:
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
@@ -128,6 +131,7 @@ class BleSocket : public api::ble_v2::BleSocket {
std::unique_ptr<BleInputStream> input_stream_;
std::unique_ptr<BleOutputStream> output_stream_;
api::ble_v2::BlePeripheral::UniqueId peripheral_id_;
absl::AnyInvocable<void()> close_notifier_;
};
} // namespace apple
@@ -201,11 +201,21 @@ Exception BleSocket::Close() {
return {Exception::kSuccess};
}
void BleSocket::SetCloseNotifier(absl::AnyInvocable<void()> notifier) {
absl::MutexLock lock(&mutex_);
close_notifier_ = std::move(notifier);
}
void BleSocket::DoClose() {
if (!closed_) {
input_stream_->Close();
output_stream_->Close();
closed_ = true;
if (close_notifier_) {
close_notifier_();
close_notifier_ = nullptr;
}
}
}