Files
nearby/internal/platform/implementation/apple/ble_medium.mm
T
2025-05-13 08:45:06 -07:00

544 lines
23 KiB
Plaintext

// 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.
#import "internal/platform/implementation/apple/ble_medium.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "internal/platform/implementation/apple/ble_utils.h"
#include "internal/platform/implementation/apple/utils.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/bluetooth_adapter.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTCharacteristic.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTClient.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPConnection.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h"
// TODO(b/293336684): Old Weave imports that need to be deleted once shared Weave is complete.
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleConnection.h"
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleUtils.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Central/GNSCentralManager.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Central/GNSCentralPeerManager.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Peripheral/GNSPeripheralManager.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Peripheral/GNSPeripheralServiceManager.h"
#import "internal/platform/implementation/apple/ble_gatt_client.h"
#import "internal/platform/implementation/apple/ble_gatt_server.h"
#import "internal/platform/implementation/apple/ble_l2cap_server_socket.h"
#import "internal/platform/implementation/apple/ble_l2cap_socket.h"
#import "internal/platform/implementation/apple/ble_server_socket.h"
#import "internal/platform/implementation/apple/ble_socket.h"
#import "internal/platform/implementation/apple/bluetooth_adapter_v2.h"
#import "internal/platform/implementation/apple/utils.h"
#import "GoogleToolboxForMac/GTMLogger.h"
static NSString *const kWeaveServiceUUID = @"FEF3";
static const UInt8 kRequestConnectionTimeoutInSeconds = 10;
namespace nearby {
namespace apple {
BleMedium::BleMedium() : medium_([[GNCBLEMedium alloc] init]) {}
std::unique_ptr<api::ble_v2::BleMedium::AdvertisingSession> BleMedium::StartAdvertising(
const api::ble_v2::BleAdvertisementData &advertising_data,
api::ble_v2::AdvertiseParameters advertise_set_parameters,
api::ble_v2::BleMedium::AdvertisingCallback callback) {
NSMutableDictionary<CBUUID *, NSData *> *serviceData =
ObjCServiceDataFromCPP(advertising_data.service_data);
__block api::ble_v2::BleMedium::AdvertisingCallback blockCallback = std::move(callback);
[socketPeripheralManager_ start];
[medium_ startAdvertisingData:serviceData
completionHandler:^(NSError *error) {
blockCallback.start_advertising_result(
error == nil ? absl::OkStatus()
: absl::InternalError(error.localizedDescription.UTF8String));
}];
return std::make_unique<AdvertisingSession>(AdvertisingSession{.stop_advertising = [this] {
return StopAdvertising() ? absl::OkStatus() : absl::InternalError("Failed to stop advertising");
}});
}
bool BleMedium::StartAdvertising(const api::ble_v2::BleAdvertisementData &advertising_data,
api::ble_v2::AdvertiseParameters advertise_set_parameters) {
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
completionHandler:^(NSError *error) {
if (error != nil) {
GTMLoggerError(@"Failed to start advertising: %@", error);
}
blockError = error;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return blockError == nil;
}
bool BleMedium::StopAdvertising() {
[socketPeripheralManager_ stop];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
[medium_ stopAdvertisingWithCompletionHandler:^(NSError *error) {
if (error != nil) {
GTMLoggerError(@"Failed to stop advertising: %@", error);
}
blockError = error;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return blockError == nil;
}
void BleMedium::HandleAdvertisementFound(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *serviceData) {
api::ble_v2::BleAdvertisementData data;
for (CBUUID *key in serviceData.allKeys) {
data.service_data[CPPUUIDFromObjC(key)] = ByteArrayFromNSData(serviceData[key]);
}
// Add the peripheral to the map if we haven't discovered it yet.
api::ble_v2::BlePeripheral::UniqueId unique_id = peripherals_.Add(peripheral);
if (scanning_cb_.advertisement_found_cb) {
scanning_cb_.advertisement_found_cb(unique_id, data);
}
if (scan_cb_.advertisement_found_cb) {
scan_cb_.advertisement_found_cb(unique_id, data);
}
}
std::unique_ptr<api::ble_v2::BleMedium::ScanningSession> BleMedium::StartScanning(
const Uuid &service_uuid, api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::BleMedium::ScanningCallback callback) {
CBUUID *serviceUUID = CBUUID128FromCPP(service_uuid);
scanning_cb_ = std::move(callback);
// Clear the map of discovered peripherals only when we are starting a new scan. If we cleared the
// map every time we stopped a scan, we would not be able to connect to peripherals that we
// discovered in that scan session.
peripherals_.Clear();
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
[medium_ startScanningForService:serviceUUID
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *serviceData) {
HandleAdvertisementFound(peripheral, serviceData);
}
completionHandler:^(NSError *error) {
if (scanning_cb_.start_scanning_result) {
scanning_cb_.start_scanning_result(
error == nil ? absl::OkStatus()
: absl::InternalError(error.localizedDescription.UTF8String));
}
}];
return std::make_unique<ScanningSession>(ScanningSession{.stop_scanning = [this] {
return StopScanning() ? absl::OkStatus() : absl::InternalError("Failed to stop scanning");
}});
}
bool BleMedium::StartScanning(const Uuid &service_uuid, api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::BleMedium::ScanCallback callback) {
CBUUID *serviceUUID = CBUUID128FromCPP(service_uuid);
scan_cb_ = std::move(callback);
// Clear the map of discovered peripherals only when we are starting a new scan. If we cleared the
// map every time we stopped a scan, we would not be able to connect to peripherals that we
// discovered in that scan session.
peripherals_.Clear();
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
[medium_ startScanningForService:serviceUUID
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *serviceData) {
HandleAdvertisementFound(peripheral, serviceData);
}
completionHandler:^(NSError *error) {
if (error != nil) {
GTMLoggerError(@"Failed to start scanning: %@", error);
}
blockError = error;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return blockError == nil;
}
bool BleMedium::StartMultipleServicesScanning(const std::vector<Uuid> &service_uuids,
api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::BleMedium::ScanCallback callback) {
if (service_uuids.empty()) {
GTMLoggerError(@"No service UUIDs provided");
return false;
}
NSMutableArray<CBUUID *> *serviceUUIDs = [NSMutableArray arrayWithCapacity:service_uuids.size()];
for (const Uuid &service_uuid : service_uuids) {
[serviceUUIDs addObject:CBUUID128FromCPP(service_uuid)];
}
scan_cb_ = std::move(callback);
// Clear the map of discovered peripherals only when we are starting a new scan. If we cleared the
// map every time we stopped a scan, we would not be able to connect to peripherals that we
// discovered in that scan session.
peripherals_.Clear();
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUIDs[0]];
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUIDs[0] ]];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
[medium_ startScanningForMultipleServices:serviceUUIDs
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *serviceData) {
HandleAdvertisementFound(peripheral, serviceData);
}
completionHandler:^(NSError *error) {
if (error != nil) {
GTMLoggerError(@"Failed to start scanning for multiple services: %@", error);
blockError = error;
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return blockError == nil;
}
bool BleMedium::StopScanning() {
[socketCentralManager_ stopNoScanMode];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
[medium_ stopScanningWithCompletionHandler:^(NSError *error) {
if (error != nil) {
GTMLoggerError(@"Failed to stop scanning: %@", error);
}
blockError = error;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return blockError == nil;
}
bool BleMedium::PauseMediumScanning() { return StopScanning(); }
bool BleMedium::ResumeMediumScanning() {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
[medium_ resumeMediumScanning:^(NSError *error) {
if (error != nil) {
GTMLoggerError(@"Failed to start scanning for multiple services: %@", error);
blockError = error;
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return blockError == nil;
}
// TODO(b/290385712): Add implementation that calls ServerGattConnectionCallback methods.
std::unique_ptr<api::ble_v2::GattServer> BleMedium::StartGattServer(
api::ble_v2::ServerGattConnectionCallback callback) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block GNCBLEGATTServer *blockServer = nil;
[medium_ startGATTServerWithCompletionHandler:^(GNCBLEGATTServer *server, NSError *error) {
if (error != nil) {
GTMLoggerError(@"Error starting GATT server: %@", error);
}
blockServer = server;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (!blockServer) {
return nullptr;
}
return std::make_unique<GattServer>(blockServer);
}
std::unique_ptr<api::ble_v2::GattClient> BleMedium::ConnectToGattServer(
api::ble_v2::BlePeripheral::UniqueId peripheral_id, api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::ClientGattConnectionCallback callback) {
id<GNCPeripheral> peripheral = peripherals_.Get(peripheral_id);
if (!peripheral) {
GTMLoggerError(@"[NEARBY] Failed to connect to Gatt server: peripheral is not found.");
return nullptr;
}
__block api::ble_v2::ClientGattConnectionCallback blockCallback = std::move(callback);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block GNCBLEGATTClient *blockClient = nil;
[medium_ connectToGATTServerForPeripheral:peripheral
disconnectionHandler:^(void) {
blockCallback.disconnected_cb();
}
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
if (error != nil) {
GTMLoggerError(@"Error connecting to GATT server: %@", error);
}
blockClient = client;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (!blockClient) {
return nullptr;
}
return std::make_unique<GattClient>(blockClient);
}
// TODO(b/293336684): Old Weave code that need to be deleted once shared Weave is complete.
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();
socketPeripheralServiceManager_ = [[GNSPeripheralServiceManager alloc]
initWithBleServiceUUID:[CBUUID UUIDWithString:kWeaveServiceUUID]
addPairingCharacteristic:NO
shouldAcceptSocketHandler:^BOOL(GNSSocket *socket) {
GNCMWaitForConnection(socket, ^(BOOL didConnect) {
GNCMBleConnection *connection =
[GNCMBleConnection connectionWithSocket:socket
// This must be nil as the advertiser even though we
// have a service ID available to us.
serviceID:nil
expectedIntroPacket:YES
callbackQueue:dispatch_get_main_queue()];
auto socket = std::make_unique<BleSocket>(connection);
connection.connectionHandlers = socket->GetInputStream().GetConnectionHandlers();
if (server_socket_ptr) {
server_socket_ptr->Connect(std::move(socket));
}
});
return YES;
}];
socketPeripheralManager_ = [[GNSPeripheralManager alloc] initWithAdvertisedName:nil
restoreIdentifier:nil];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
[socketPeripheralManager_ addPeripheralServiceManager:socketPeripheralServiceManager_
bleServiceAddedCompletion:^(NSError *error) {
if (error != nil) {
GTMLoggerError(@"Failed to add Weave service: %@", error);
}
blockError = error;
dispatch_semaphore_signal(semaphore);
}];
[socketPeripheralManager_ start];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (blockError != nil) {
return nullptr;
}
return std::move(server_socket);
}
std::unique_ptr<api::ble_v2::BleL2capServerSocket> BleMedium::OpenL2capServerSocket(
const std::string &service_id) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockPSMPublishedError = nil;
auto l2cap_server_socket = std::make_unique<BleL2capServerSocket>();
__block auto l2cap_server_socket_ptr = l2cap_server_socket.get();
std::string service_id_str = service_id;
[medium_
openL2CAPServerWithPSMPublishedCompletionHandler:^(uint16_t PSM, NSError *_Nullable error) {
if (error) {
blockPSMPublishedError = error;
dispatch_semaphore_signal(semaphore);
return;
}
l2cap_server_socket_ptr->SetPSM(PSM);
dispatch_semaphore_signal(semaphore);
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error) {
if (error != nil) {
GTMLoggerError(@"Error opening L2CAP channel in L2CAP server: %@", error);
return;
}
GNCBLEL2CAPConnection *connection =
[GNCBLEL2CAPConnection connectionWithStream:stream
serviceID:@(service_id_str.c_str())
incomingConnection:YES
callbackQueue:dispatch_get_main_queue()];
auto socket = std::make_unique<BleL2capSocket>(connection);
if (l2cap_server_socket_ptr) {
l2cap_server_socket_ptr->AddPendingSocket(std::move(socket));
}
}
peripheralManager:nil];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (blockPSMPublishedError != nil) {
return nullptr;
}
return std::move(l2cap_server_socket);
}
// TODO(b/290385712): Add support for @c cancellation_flag.
// TODO(b/293336684): Old Weave code that need to be deleted once shared Weave is complete.
std::unique_ptr<api::ble_v2::BleSocket> BleMedium::Connect(
const std::string &service_id, api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::BlePeripheral::UniqueId peripheral_id, CancellationFlag *cancellation_flag) {
id<GNCPeripheral> peripheral = peripherals_.Get(peripheral_id);
if (!peripheral) {
GTMLoggerError(@"[NEARBY] Failed to connect to Gatt server: peripheral is not found.");
return nullptr;
}
GNSCentralPeerManager *updatedCentralPeerManager = [socketCentralManager_
retrieveCentralPeerWithIdentifier:peripheral.identifier];
if (!updatedCentralPeerManager) {
return nullptr;
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block std::unique_ptr<BleSocket> socket;
[updatedCentralPeerManager
socketWithPairingCharacteristic:NO
completion:^(GNSSocket *nssocket, NSError *error) {
if (error) {
dispatch_semaphore_signal(semaphore);
return;
}
GNCMWaitForConnection(nssocket, ^(BOOL didConnect) {
if (!didConnect) {
dispatch_semaphore_signal(semaphore);
return;
}
GNCMBleConnection *connection = [GNCMBleConnection
connectionWithSocket:nssocket
serviceID:@(service_id.c_str())
expectedIntroPacket:NO
callbackQueue:dispatch_get_main_queue()];
socket =
std::make_unique<BleSocket>(connection, peripheral_id);
connection.connectionHandlers =
socket->GetInputStream().GetConnectionHandlers();
dispatch_semaphore_signal(semaphore);
});
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (socket == nullptr) {
return nullptr;
}
// Send the (empty) intro packet, which the BLE advertiser is expecting.
socket->GetOutputStream().Write(ByteArray());
return std::move(socket);
}
std::unique_ptr<api::ble_v2::BleL2capSocket> BleMedium::ConnectOverL2cap(
int psm, const std::string &service_id, api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::BlePeripheral::UniqueId peripheral_id, CancellationFlag *cancellation_flag) {
id<GNCPeripheral> peripheral = peripherals_.Get(peripheral_id);
if (!peripheral) {
GTMLoggerError(@"[NEARBY] Failed to connect over L2CAP: peripheral is not found.");
return nullptr;
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_time_t timeout =
dispatch_time(DISPATCH_TIME_NOW, kRequestConnectionTimeoutInSeconds * NSEC_PER_SEC);
__block std::unique_ptr<BleL2capSocket> socket;
const std::string &service_id_str = service_id;
[medium_ openL2CAPChannelWithPSM:psm
peripheral:peripheral
completionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
if (error) {
dispatch_semaphore_signal(semaphore);
return;
}
GNCBLEL2CAPConnection *connection =
[GNCBLEL2CAPConnection connectionWithStream:stream
serviceID:@(service_id_str.c_str())
incomingConnection:NO
callbackQueue:dispatch_get_main_queue()];
// Blocked call to wait for the packet validation result.
// TODO: b/399815436 - Remove this once the packet validation is moved to the
// Connections layer.
[connection requestDataConnectionWithCompletion:^(BOOL result) {
if (result) {
socket = std::make_unique<BleL2capSocket>(connection, peripheral_id);
}
GTMLoggerInfo(result ? @"[NEARBY] Request data connection is ok"
: @"[NEARBY] Request data connection is not ok");
dispatch_semaphore_signal(semaphore);
}];
}];
if (dispatch_semaphore_wait(semaphore, timeout) != 0) {
GTMLoggerError(@"[NEARBY] Failed to connect over L2CAP: timeout.");
return nullptr;
}
if (socket == nullptr) {
return nullptr;
}
return std::move(socket);
}
bool BleMedium::IsExtendedAdvertisementsAvailable() {
return [medium_ supportsExtendedAdvertisements];
}
api::ble_v2::BlePeripheral::UniqueId BleMedium::PeripheralsMap::Add(id<GNCPeripheral> peripheral) {
absl::MutexLock lock(&mutex_);
api::ble_v2::BlePeripheral::UniqueId peripheral_id = peripheral.identifier.hash;
peripherals_.insert({peripheral_id, peripheral});
return peripheral_id;
}
id<GNCPeripheral> BleMedium::PeripheralsMap::Get(api::ble_v2::BlePeripheral::UniqueId peripheral_id) {
absl::MutexLock lock(&mutex_);
auto peripheral_it = peripherals_.find(peripheral_id);
if (peripheral_it == peripherals_.end()) {
return nil;
}
return peripheral_it->second;
}
void BleMedium::PeripheralsMap::Clear() {
absl::MutexLock lock(&mutex_);
peripherals_.clear();
}
} // namespace apple
} // namespace nearby