diff --git a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.h b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.h index b267fb29..3298169c 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.h +++ b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.h @@ -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 diff --git a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m index 12ec6244..b7f6f21e 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m +++ b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m @@ -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 diff --git a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralServiceManager.m b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralServiceManager.m index 1be7128f..9fe1bae0 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralServiceManager.m +++ b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralServiceManager.m @@ -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) { diff --git a/internal/platform/implementation/apple/ble_medium.h b/internal/platform/implementation/apple/ble_medium.h index bc86ebcd..a5ba76ab 100644 --- a/internal/platform/implementation/apple/ble_medium.h +++ b/internal/platform/implementation/apple/ble_medium.h @@ -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 diff --git a/internal/platform/implementation/apple/ble_medium.mm b/internal/platform/implementation/apple/ble_medium.mm index 94d7ac49..6750f3a9 100644 --- a/internal/platform/implementation/apple/ble_medium.mm +++ b/internal/platform/implementation/apple/ble_medium.mm @@ -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 *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 BleMedium::OpenServerSocket( const std::string &service_id) { auto server_socket = std::make_unique(); __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 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(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 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(connection); { absl::MutexLock lock(&l2cap_server_socket_mutex_); @@ -586,7 +609,7 @@ std::unique_ptr BleMedium::Connect( connectionWithSocket:nssocket serviceID:@(service_id.c_str()) expectedIntroPacket:NO - callbackQueue:dispatch_get_main_queue()]; + callbackQueue:connection_callback_queue_]; socket = std::make_unique(connection, peripheral_id); connection.connectionHandlers = socket->GetInputStream().GetConnectionHandlers(); @@ -631,7 +654,7 @@ std::unique_ptr 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. diff --git a/internal/platform/implementation/apple/ble_socket.h b/internal/platform/implementation/apple/ble_socket.h index ec801093..cd877066 100644 --- a/internal/platform/implementation/apple/ble_socket.h +++ b/internal/platform/implementation/apple/ble_socket.h @@ -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 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 input_stream_; std::unique_ptr output_stream_; api::ble_v2::BlePeripheral::UniqueId peripheral_id_; + absl::AnyInvocable close_notifier_; }; } // namespace apple diff --git a/internal/platform/implementation/apple/ble_socket.mm b/internal/platform/implementation/apple/ble_socket.mm index 997e5254..230b1f9c 100644 --- a/internal/platform/implementation/apple/ble_socket.mm +++ b/internal/platform/implementation/apple/ble_socket.mm @@ -201,11 +201,21 @@ Exception BleSocket::Close() { return {Exception::kSuccess}; } +void BleSocket::SetCloseNotifier(absl::AnyInvocable 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; + } } }