Files
nearby/internal/platform/implementation/apple/ble.mm
T
Janusz Sobczak b5938c0d17 Add G3 gatt server
PiperOrigin-RevId: 531287485
2023-05-11 13:15:40 -07:00

601 lines
22 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.h"
#include <CoreBluetooth/CoreBluetooth.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "internal/platform/implementation/apple/bluetooth_adapter.h"
#include "internal/platform/implementation/apple/utils.h"
#include "internal/platform/implementation/ble_v2.h"
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleCentral.h"
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBlePeripheral.h"
namespace nearby {
namespace apple {
using Permission = api::ble_v2::GattCharacteristic::Permission;
using Property = api::ble_v2::GattCharacteristic::Property;
namespace {
CBAttributePermissions PermissionToCBPermissions(Permission permission) {
CBAttributePermissions characteristPermissions = 0;
if ((permission & Permission::kRead) != Permission::kNone) {
characteristPermissions |= CBAttributePermissionsReadable;
}
if ((permission & Permission::kWrite) != Permission::kNone) {
characteristPermissions |= CBAttributePermissionsWriteable;
}
return characteristPermissions;
}
CBCharacteristicProperties PropertiesToCBProperties(Property property) {
CBCharacteristicProperties characteristicProperties = 0;
if ((property & Property::kRead) != Property::kNone) {
characteristicProperties |= CBCharacteristicPropertyRead;
}
if ((property & Property::kWrite) != Property::kNone) {
characteristicProperties |= CBCharacteristicPropertyWrite;
}
if ((property & Property::kIndicate) != Property::kNone) {
characteristicProperties |= CBCharacteristicPropertyIndicate;
}
if ((property & Property::kNotify) != Property::kNone) {
characteristicProperties |= CBCharacteristicPropertyNotify;
}
return characteristicProperties;
}
} // namespace
using ::nearby::api::ble_v2::BleAdvertisementData;
using ::nearby::api::ble_v2::TxPowerLevel;
using ScanCallback = ::nearby::api::ble_v2::BleMedium::ScanCallback;
/** InputStream that reads from GNCMConnection. */
BleInputStream::BleInputStream()
: newDataPackets_([NSMutableArray array]),
accumulatedData_([NSMutableData data]),
condition_([[NSCondition alloc] init]) {
// Create the handlers of incoming data from the remote endpoint.
connectionHandlers_ = [GNCMConnectionHandlers
payloadHandler:^(NSData* data) {
[condition_ lock];
// Add the incoming data to the data packet array to be processed in read() below.
[newDataPackets_ addObject:data];
[condition_ signal];
[condition_ unlock];
}
disconnectedHandler:^{
[condition_ lock];
// Release the data packet array, meaning the stream has been closed or severed.
newDataPackets_ = nil;
[condition_ signal];
[condition_ unlock];
}];
}
BleInputStream::~BleInputStream() {
NSCAssert(!newDataPackets_, @"BleInputStream not closed before destruction");
}
ExceptionOr<ByteArray> BleInputStream::Read(std::int64_t size) {
// Block until either (a) the connection has been closed, (b) we have enough data to return.
NSData* dataToReturn;
[condition_ lock];
while (true) {
// Check if the stream has been closed or severed.
if (!newDataPackets_) break;
if (newDataPackets_.count > 0) {
// Add the packet data to the accumulated data.
for (NSData* data in newDataPackets_) {
if (data.length > 0) {
[accumulatedData_ appendData:data];
}
}
[newDataPackets_ removeAllObjects];
}
if ((size == -1) && (accumulatedData_.length > 0)) {
// Return all of the data.
dataToReturn = accumulatedData_;
accumulatedData_ = [NSMutableData data];
break;
} else if (accumulatedData_.length > 0) {
// Return up to |size| bytes of the data.
std::int64_t sizeToReturn = (accumulatedData_.length < size) ? accumulatedData_.length : size;
NSRange range = NSMakeRange(0, (NSUInteger)sizeToReturn);
dataToReturn = [accumulatedData_ subdataWithRange:range];
[accumulatedData_ replaceBytesInRange:range withBytes:nil length:0];
break;
}
[condition_ wait];
}
[condition_ unlock];
if (dataToReturn) {
NSLog(@"[NEARBY] Input stream: Received data of size: %lu", (unsigned long)dataToReturn.length);
return ExceptionOr<ByteArray>(ByteArrayFromNSData(dataToReturn));
} else {
return ExceptionOr<ByteArray>{Exception::kIo};
}
}
Exception BleInputStream::Close() {
// Unblock pending read operation.
[condition_ lock];
newDataPackets_ = nil;
[condition_ signal];
[condition_ unlock];
return {Exception::kSuccess};
}
/** OutputStream that writes to GNCMConnection. */
BleOutputStream::~BleOutputStream() {
NSCAssert(!connection_, @"BleOutputStream not closed before destruction");
}
Exception BleOutputStream::Write(const ByteArray& data) {
[condition_ lock];
NSLog(@"[NEARBY] Sending data of size: %lu", (unsigned long)NSDataFromByteArray(data).length);
NSMutableData* packet = [NSMutableData dataWithData:NSDataFromByteArray(data)];
// Send the data, blocking until the completion handler is called.
__block GNCMPayloadResult sendResult = GNCMPayloadResultFailure;
__block bool isComplete = NO;
NSCondition* condition = condition_; // don't capture |this| in completion
// Check if connection_ is nil, then just don't wait and return as failure.
if (connection_ != nil) {
[connection_ sendData:packet
progressHandler:^(size_t count) {
}
completion:^(GNCMPayloadResult result) {
// Make sure we haven't already reported completion before. This prevents a crash
// where we try leaving a dispatch group more times than we entered it.
// b/79095653.
if (isComplete) {
return;
}
isComplete = YES;
sendResult = result;
[condition lock];
[condition signal];
[condition unlock];
}];
[condition_ wait];
[condition_ unlock];
} else {
sendResult = GNCMPayloadResultFailure;
[condition_ unlock];
}
if (sendResult == GNCMPayloadResultSuccess) {
return {Exception::kSuccess};
} else {
return {Exception::kIo};
}
}
Exception BleOutputStream::Flush() {
// The write() function blocks until the data is received by the remote endpoint, so there's
// nothing to do here.
return {Exception::kSuccess};
}
Exception BleOutputStream::Close() {
// Unblock pending write operation.
[condition_ lock];
connection_ = nil;
[condition_ signal];
[condition_ unlock];
return {Exception::kSuccess};
}
/** BleSocket implementation.*/
BleSocket::BleSocket(id<GNCMConnection> connection, BlePeripheral* peripheral)
: input_stream_(new BleInputStream()),
output_stream_(new BleOutputStream(connection)),
peripheral_(peripheral) {}
BleSocket::~BleSocket() {
absl::MutexLock lock(&mutex_);
DoClose();
}
bool BleSocket::IsClosed() const {
absl::MutexLock lock(&mutex_);
return closed_;
}
Exception BleSocket::Close() {
absl::MutexLock lock(&mutex_);
DoClose();
return {Exception::kSuccess};
}
void BleSocket::DoClose() {
if (!closed_) {
input_stream_->Close();
output_stream_->Close();
closed_ = true;
}
}
/** WifiLanServerSocket implementation. */
BleServerSocket::~BleServerSocket() {
absl::MutexLock lock(&mutex_);
DoClose();
}
std::unique_ptr<api::ble_v2::BleSocket> BleServerSocket::Accept() {
absl::MutexLock lock(&mutex_);
while (!closed_ && pending_sockets_.empty()) {
cond_.Wait(&mutex_);
}
// Return early if closed.
if (closed_) return {};
auto remote_socket = std::move(pending_sockets_.extract(pending_sockets_.begin()).value());
return std::move(remote_socket);
}
bool BleServerSocket::Connect(std::unique_ptr<BleSocket> socket) {
absl::MutexLock lock(&mutex_);
if (closed_) {
return false;
}
// add client socket to the pending list
pending_sockets_.insert(std::move(socket));
cond_.SignalAll();
if (closed_) {
return false;
}
return true;
}
void BleServerSocket::SetCloseNotifier(absl::AnyInvocable<void()> notifier) {
absl::MutexLock lock(&mutex_);
close_notifier_ = std::move(notifier);
}
Exception BleServerSocket::Close() {
absl::MutexLock lock(&mutex_);
return DoClose();
}
Exception BleServerSocket::DoClose() {
bool should_notify = !closed_;
closed_ = true;
if (should_notify) {
cond_.SignalAll();
if (close_notifier_) {
auto notifier = std::move(close_notifier_);
mutex_.Unlock();
// Notifier may contain calls to public API, and may cause deadlock, if
// mutex_ is held during the call.
notifier();
mutex_.Lock();
}
}
return {Exception::kSuccess};
}
/** BleMedium implementation. */
BleMedium::BleMedium(::nearby::api::BluetoothAdapter& adapter)
: adapter_(static_cast<BluetoothAdapter*>(&adapter)) {}
bool BleMedium::StartAdvertising(
const BleAdvertisementData& advertising_data,
::nearby::api::ble_v2::AdvertiseParameters advertise_set_parameters) {
if (advertising_data.service_data.empty()) {
return false;
}
const auto& service_uuid = advertising_data.service_data.begin()->first.Get16BitAsString();
const ByteArray& service_data_bytes = advertising_data.service_data.begin()->second;
if (!peripheral_) {
peripheral_ = [[GNCMBlePeripheral alloc] init];
}
auto& peripheral = adapter_->GetPeripheral();
[peripheral_
startAdvertisingWithServiceUUID:ObjCStringFromCppString(service_uuid)
advertisementData:NSDataFromByteArray(service_data_bytes)
endpointConnectedHandler:^GNCMConnectionHandlers*(id<GNCMConnection> connection) {
// TODO(edwinwu): This server_socket is supposed to be gotten from the map by key of
// servcie_id. We now always get the first iteration since we don't know the key now.
// Try the way to move the Ble socket frame verification up to one layer.
std::string service_id;
BleServerSocket* server_socket;
if (!server_sockets_.empty()) {
service_id = server_sockets_.begin()->first;
server_socket = server_sockets_.begin()->second;
} else {
return nil;
}
auto socket = std::make_unique<BleSocket>(connection, &peripheral);
GNCMConnectionHandlers* connectionHandlers =
static_cast<BleInputStream&>(socket->GetInputStream()).GetConnectionHandlers();
server_socket->Connect(std::move(socket));
return connectionHandlers;
}
callbackQueue:callback_queue_];
return true;
}
bool BleMedium::StopAdvertising() {
peripheral_ = nil;
return true;
}
std::unique_ptr<BleMedium::AdvertisingSession> BleMedium::StartAdvertising(
const api::ble_v2::BleAdvertisementData& advertising_data,
api::ble_v2::AdvertiseParameters advertise_parameters,
BleMedium::AdvertisingCallback callback) {
// TODO(hais): add real impl for iOs StartAdvertising
return std::make_unique<AdvertisingSession>(AdvertisingSession{});
}
bool BleMedium::StartScanning(const Uuid& service_uuid, TxPowerLevel tx_power_level,
ScanCallback scan_callback) {
if (!central_) {
central_ = [[GNCMBleCentral alloc] init];
}
__block ScanCallback callback = std::move(scan_callback);
[central_ startScanningWithServiceUUID:ObjCStringFromCppString(service_uuid.Get16BitAsString())
scanResultHandler:^(NSString* peripheralID, NSData* serviceData) {
BleAdvertisementData advertisement_data;
advertisement_data.service_data = {{service_uuid, ByteArrayFromNSData(serviceData)}};
BlePeripheral& peripheral = adapter_->GetPeripheral();
peripheral.SetPeripheralId(CppStringFromObjCString(peripheralID));
callback.advertisement_found_cb(peripheral, advertisement_data);
}
requestConnectionHandler:^(GNCMBleConnectionRequester connectionRequester) {
BlePeripheral& peripheral = adapter_->GetPeripheral();
peripheral.SetConnectionRequester(connectionRequester);
}
callbackQueue:callback_queue_];
return true;
}
bool BleMedium::StopScanning() {
central_ = nil;
return true;
}
std::unique_ptr<BleMedium::ScanningSession> BleMedium::StartScanning(
const Uuid& service_uuid, TxPowerLevel tx_power_level, BleMedium::ScanningCallback callback) {
// TODO(hais): add real impl for windows StartScanning.
return std::make_unique<ScanningSession>(ScanningSession{});
}
std::unique_ptr<api::ble_v2::GattServer> BleMedium::StartGattServer(
api::ble_v2::ServerGattConnectionCallback callback) {
if (!peripheral_) {
peripheral_ = [[GNCMBlePeripheral alloc] init];
}
return std::make_unique<GattServer>(adapter_, peripheral_);
}
std::unique_ptr<api::ble_v2::GattClient> BleMedium::ConnectToGattServer(
api::ble_v2::BlePeripheral& peripheral, TxPowerLevel tx_power_level,
api::ble_v2::ClientGattConnectionCallback callback) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError* connectedError;
BlePeripheral iosPeripheral = static_cast<BlePeripheral&>(peripheral);
std::string peripheral_id = iosPeripheral.GetPeripheralId();
[central_ connectGattServerWithPeripheralID:ObjCStringFromCppString(peripheral_id)
gattConnectionResultHandler:^(NSError* _Nullable error) {
connectedError = error;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
if (connectedError) {
return nullptr;
}
return std::make_unique<GattClient>(central_, peripheral_id);
}
std::unique_ptr<api::ble_v2::BleServerSocket> BleMedium::OpenServerSocket(
const std::string& service_id) {
auto server_socket = std::make_unique<BleServerSocket>();
server_socket->SetCloseNotifier([this, service_id]() {
absl::MutexLock lock(&mutex_);
server_sockets_.erase(service_id);
});
absl::MutexLock lock(&mutex_);
server_sockets_.insert({service_id, server_socket.get()});
return server_socket;
}
std::unique_ptr<api::ble_v2::BleSocket> BleMedium::Connect(const std::string& service_id,
TxPowerLevel tx_power_level,
api::ble_v2::BlePeripheral& peripheral,
CancellationFlag* cancellation_flag) {
NSString* serviceID = ObjCStringFromCppString(service_id);
__block std::unique_ptr<BleSocket> socket;
__block BlePeripheral ios_peripheral = static_cast<BlePeripheral&>(peripheral);
GNCMBleConnectionRequester connection_requester = ios_peripheral.GetConnectionRequester();
if (!connection_requester) return {};
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
if (cancellation_flag->Cancelled()) {
NSLog(@"[NEARBY] BLE Connect: Has been cancelled: service_id=%@", serviceID);
dispatch_group_leave(group); // unblock
return {};
}
connection_requester(serviceID, ^(id<GNCMConnection> connection) {
// If the connection wasn't successfully established, return a NULL socket.
if (connection) {
socket = std::make_unique<BleSocket>(connection, &ios_peripheral);
}
dispatch_group_leave(group); // unblock
return socket != nullptr
? static_cast<BleInputStream&>(socket->GetInputStream()).GetConnectionHandlers()
: nullptr;
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Send the (empty) intro packet, which the BLE advertiser is expecting.
if (socket != nullptr) {
socket->GetOutputStream().Write(ByteArray());
}
return std::move(socket);
}
bool BleMedium::IsExtendedAdvertisementsAvailable() { return false; }
// NOLINTNEXTLINE
absl::optional<api::ble_v2::GattCharacteristic> BleMedium::GattServer::CreateCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid,
api::ble_v2::GattCharacteristic::Permission permission,
api::ble_v2::GattCharacteristic::Property property) {
api::ble_v2::GattCharacteristic characteristic = {.uuid = characteristic_uuid,
.service_uuid = service_uuid,
.permission = permission,
.property = property};
[peripheral_
addCBServiceWithUUID:[CBUUID
UUIDWithString:ObjCStringFromCppString(
characteristic.service_uuid.Get16BitAsString())]];
[peripheral_
addCharacteristic:[[CBMutableCharacteristic alloc]
initWithType:[CBUUID UUIDWithString:ObjCStringFromCppString(std::string(
characteristic.uuid))]
properties:PropertiesToCBProperties(characteristic.property)
value:nil
permissions:PermissionToCBPermissions(characteristic.permission)]];
return characteristic;
}
bool BleMedium::GattServer::UpdateCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic, const nearby::ByteArray& value) {
[peripheral_ updateValue:NSDataFromByteArray(value)
forCharacteristic:[CBUUID UUIDWithString:ObjCStringFromCppString(
std::string(characteristic.uuid))]];
return true;
}
absl::Status BleMedium::GattServer::NotifyCharacteristicChanged(
const api::ble_v2::GattCharacteristic& characteristic, bool confirm,
const ByteArray& new_value) {
// no-op because client cannot request notifications.
return absl::UnimplementedError("Unimplemented!");
}
void BleMedium::GattServer::Stop() { [peripheral_ stopGATTService]; }
bool BleMedium::GattClient::DiscoverServiceAndCharacteristics(
const Uuid& service_uuid, const std::vector<Uuid>& characteristic_uuids) {
// Discover all characteristics that may contain the advertisement.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
gatt_characteristic_values_.clear();
CBUUID* serviceUUID = [CBUUID UUIDWithString:ObjCStringFromCppString(std::string(service_uuid))];
absl::flat_hash_map<std::string, Uuid> gatt_characteristics;
NSMutableArray<CBUUID*>* characteristicUUIDs =
[NSMutableArray arrayWithCapacity:characteristic_uuids.size()];
for (const auto& characteristic_uuid : characteristic_uuids) {
[characteristicUUIDs addObject:[CBUUID UUIDWithString:ObjCStringFromCppString(
std::string(characteristic_uuid))]];
gatt_characteristics.insert({std::string(characteristic_uuid), characteristic_uuid});
}
[central_ discoverGattService:serviceUUID
gattCharacteristics:characteristicUUIDs
peripheralID:ObjCStringFromCppString(peripheral_id_)
gattDiscoverResultHandler:^(NSOrderedSet<CBCharacteristic*>* _Nullable cb_characteristics) {
if (cb_characteristics != nil) {
for (CBCharacteristic* cb_characteristic in cb_characteristics) {
auto const& it = gatt_characteristics.find(
CppStringFromObjCString(cb_characteristic.UUID.UUIDString));
if (it == gatt_characteristics.end()) continue;
api::ble_v2::GattCharacteristic characteristic = {.uuid = it->second,
.service_uuid = service_uuid};
gatt_characteristic_values_.insert(
{characteristic, ByteArrayFromNSData(cb_characteristic.value).string_data()});
}
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
if (gatt_characteristic_values_.empty()) {
return false;
}
return true;
}
// NOLINTNEXTLINE
absl::optional<api::ble_v2::GattCharacteristic> BleMedium::GattClient::GetCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid) {
api::ble_v2::GattCharacteristic characteristic = {.uuid = characteristic_uuid,
.service_uuid = service_uuid};
auto const it = gatt_characteristic_values_.find(characteristic);
if (it == gatt_characteristic_values_.end()) {
return absl::nullopt; // NOLINT
}
return it->first;
}
// NOLINTNEXTLINE
absl::optional<std::string> BleMedium::GattClient::ReadCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic) {
auto const it = gatt_characteristic_values_.find(characteristic);
if (it == gatt_characteristic_values_.end()) {
return absl::nullopt; // NOLINT
}
return it->second;
}
bool BleMedium::GattClient::WriteCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic, absl::string_view value,
api::ble_v2::GattClient::WriteType write_type) {
// No op.
return false;
}
bool BleMedium::GattClient::SetCharacteristicSubscription(
const api::ble_v2::GattCharacteristic& characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)> on_characteristic_changed_cb) {
// No op since we can't write characteristics.
return false;
}
void BleMedium::GattClient::Disconnect() {
[central_ disconnectGattServiceWithPeripheralID:ObjCStringFromCppString(peripheral_id_)];
}
} // namespace apple
} // namespace nearby