summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/darwin
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/darwin')
-rw-r--r--src/bluetooth/darwin/btcentralmanager.mm1860
-rw-r--r--src/bluetooth/darwin/btcentralmanager_p.h175
-rw-r--r--src/bluetooth/darwin/btconnectionmonitor.mm133
-rw-r--r--src/bluetooth/darwin/btconnectionmonitor_p.h71
-rw-r--r--src/bluetooth/darwin/btdelegates.cpp76
-rw-r--r--src/bluetooth/darwin/btdelegates_p.h149
-rw-r--r--src/bluetooth/darwin/btdeviceinquiry.mm168
-rw-r--r--src/bluetooth/darwin/btdeviceinquiry_p.h83
-rw-r--r--src/bluetooth/darwin/btdevicepair.mm215
-rw-r--r--src/bluetooth/darwin/btdevicepair_p.h110
-rw-r--r--src/bluetooth/darwin/btgcdtimer.mm146
-rw-r--r--src/bluetooth/darwin/btgcdtimer_p.h108
-rw-r--r--src/bluetooth/darwin/btl2capchannel.mm258
-rw-r--r--src/bluetooth/darwin/btl2capchannel_p.h118
-rw-r--r--src/bluetooth/darwin/btledeviceinquiry.mm378
-rw-r--r--src/bluetooth/darwin/btledeviceinquiry_p.h103
-rw-r--r--src/bluetooth/darwin/btnotifier.cpp1
-rw-r--r--src/bluetooth/darwin/btnotifier_p.h105
-rw-r--r--src/bluetooth/darwin/btobexsession.mm841
-rw-r--r--src/bluetooth/darwin/btobexsession_p.h138
-rw-r--r--src/bluetooth/darwin/btperipheralmanager.mm897
-rw-r--r--src/bluetooth/darwin/btperipheralmanager_p.h163
-rw-r--r--src/bluetooth/darwin/btraii.mm110
-rw-r--r--src/bluetooth/darwin/btraii_p.h136
-rw-r--r--src/bluetooth/darwin/btrfcommchannel.mm261
-rw-r--r--src/bluetooth/darwin/btrfcommchannel_p.h108
-rw-r--r--src/bluetooth/darwin/btsdpinquiry.mm329
-rw-r--r--src/bluetooth/darwin/btsdpinquiry_p.h99
-rw-r--r--src/bluetooth/darwin/btservicerecord.mm458
-rw-r--r--src/bluetooth/darwin/btservicerecord_p.h72
-rw-r--r--src/bluetooth/darwin/btsocketlistener.mm128
-rw-r--r--src/bluetooth/darwin/btsocketlistener_p.h91
-rw-r--r--src/bluetooth/darwin/btutility.mm372
-rw-r--r--src/bluetooth/darwin/btutility_p.h339
-rw-r--r--src/bluetooth/darwin/darwinbt.pri48
-rw-r--r--src/bluetooth/darwin/uistrings.cpp88
-rw-r--r--src/bluetooth/darwin/uistrings_p.h108
37 files changed, 9043 insertions, 0 deletions
diff --git a/src/bluetooth/darwin/btcentralmanager.mm b/src/bluetooth/darwin/btcentralmanager.mm
new file mode 100644
index 00000000..dd3cbb73
--- /dev/null
+++ b/src/bluetooth/darwin/btcentralmanager.mm
@@ -0,0 +1,1860 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qlowenergyserviceprivate_p.h"
+#include "qlowenergycharacteristic.h"
+#include "qlowenergycontroller.h"
+#include "btcentralmanager_p.h"
+#include "btnotifier_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qdebug.h>
+
+#include <algorithm>
+#include <vector>
+#include <limits>
+
+Q_DECLARE_METATYPE(QLowEnergyHandle)
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+NSUInteger qt_countGATTEntries(CBService *service)
+{
+ // Identify, how many characteristics/descriptors we have on a given service,
+ // +1 for the service itself.
+ // No checks if NSUInteger is big enough :)
+ // Let's assume such number of entries is not possible :)
+
+ Q_ASSERT_X(service, Q_FUNC_INFO, "invalid service (nil)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ NSArray *const cs = service.characteristics;
+ if (!cs || !cs.count)
+ return 1;
+
+ NSUInteger n = 1 + cs.count;
+ for (CBCharacteristic *c in cs) {
+ NSArray *const ds = c.descriptors;
+ if (ds)
+ n += ds.count;
+ }
+
+ return n;
+}
+
+ObjCStrongReference<NSError> qt_timeoutNSError(OperationTimeout type)
+{
+ // For now we do not provide details, since nobody is using this NSError
+ // after all (except callbacks checking if an operation was successful
+ // or not).
+ Q_ASSERT(type != OperationTimeout::none);
+ Q_UNUSED(type)
+ NSError *nsError = [[NSError alloc] initWithDomain:CBErrorDomain
+ code:CBErrorOperationCancelled
+ userInfo:nil];
+ return ObjCStrongReference<NSError>(nsError, false /*do not retain, done already*/);
+}
+
+auto qt_find_watchdog(const std::vector<GCDTimer> &watchdogs, id object, OperationTimeout type)
+{
+ return std::find_if(watchdogs.begin(), watchdogs.end(), [object, type](const GCDTimer &other){
+ return [other objectUnderWatch] == object && [other timeoutType] == type;});
+}
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTCentralManager) (PrivateAPI)
+
+- (void)watchAfter:(id)object timeout:(DarwinBluetooth::OperationTimeout)type;
+- (bool)objectIsUnderWatch:(id)object operation:(DarwinBluetooth::OperationTimeout)type;
+- (void)stopWatchingAfter:(id)object operation:(DarwinBluetooth::OperationTimeout)type;
+- (void)stopWatchers;
+- (void)retrievePeripheralAndConnect;
+- (void)connectToPeripheral;
+- (void)discoverIncludedServices;
+- (void)readCharacteristics:(CBService *)service;
+- (void)serviceDetailsDiscoveryFinished:(CBService *)service;
+- (void)performNextRequest;
+- (void)performNextReadRequest;
+- (void)performNextWriteRequest;
+
+// Aux. functions.
+- (CBService *)serviceForUUID:(const QBluetoothUuid &)qtUuid;
+- (CBCharacteristic *)nextCharacteristicForService:(CBService*)service
+ startingFrom:(CBCharacteristic *)from;
+- (CBCharacteristic *)nextCharacteristicForService:(CBService*)service
+ startingFrom:(CBCharacteristic *)from
+ withProperties:(CBCharacteristicProperties)properties;
+- (CBDescriptor *)nextDescriptorForCharacteristic:(CBCharacteristic *)characteristic
+ startingFrom:(CBDescriptor *)descriptor;
+- (CBDescriptor *)descriptor:(const QBluetoothUuid &)dUuid
+ forCharacteristic:(CBCharacteristic *)ch;
+- (bool)cacheWriteValue:(const QByteArray &)value for:(NSObject *)obj;
+- (void)handleReadWriteError:(NSError *)error;
+- (void)reset;
+
+@end
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTCentralManager)
+{
+@private
+ CBCentralManager *manager;
+ DarwinBluetooth::CentralManagerState managerState;
+ bool disconnectPending;
+
+ QBluetoothUuid deviceUuid;
+
+ DarwinBluetooth::LECBManagerNotifier *notifier;
+
+ // Quite a verbose service discovery machinery
+ // (a "graph traversal").
+ DarwinBluetooth::ObjCStrongReference<NSMutableArray> servicesToVisit;
+ // The service we're discovering now (included services discovery):
+ NSUInteger currentService;
+ // Included services, we'll iterate through at the end of 'servicesToVisit':
+ DarwinBluetooth::ObjCStrongReference<NSMutableArray> servicesToVisitNext;
+ // We'd like to avoid loops in a services' topology:
+ DarwinBluetooth::ObjCStrongReference<NSMutableSet> visitedServices;
+
+ QList<QBluetoothUuid> servicesToDiscoverDetails;
+
+ DarwinBluetooth::ServiceHash serviceMap;
+ DarwinBluetooth::CharHash charMap;
+ DarwinBluetooth::DescHash descMap;
+
+ QLowEnergyHandle lastValidHandle;
+
+ bool requestPending;
+ DarwinBluetooth::RequestQueue requests;
+ QLowEnergyHandle currentReadHandle;
+
+ DarwinBluetooth::ValueHash valuesToWrite;
+
+ qint64 timeoutMS;
+ std::vector<DarwinBluetooth::GCDTimer> timeoutWatchdogs;
+
+ CBPeripheral *peripheral;
+}
+
+- (id)initWith:(DarwinBluetooth::LECBManagerNotifier *)aNotifier
+{
+ using namespace DarwinBluetooth;
+
+ if (self = [super init]) {
+ manager = nil;
+ managerState = CentralManagerIdle;
+ disconnectPending = false;
+ peripheral = nil;
+ notifier = aNotifier;
+ currentService = 0;
+ lastValidHandle = 0;
+ requestPending = false;
+ currentReadHandle = 0;
+
+ if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("BLUETOOTH_GATT_TIMEOUT"))) {
+ bool ok = false;
+ const int value = qEnvironmentVariableIntValue("BLUETOOTH_GATT_TIMEOUT", &ok);
+ if (ok && value >= 0)
+ timeoutMS = value;
+ }
+
+ if (!timeoutMS)
+ timeoutMS = 20000;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ // In the past I had a 'transient delegate': I've seen some crashes
+ // while deleting a manager _before_ its state updated.
+ // Strangely enough, I can not reproduce this anymore, so this
+ // part is simplified now. To be investigated though.
+
+ visitedServices.reset(nil);
+ servicesToVisit.reset(nil);
+ servicesToVisitNext.reset(nil);
+
+ [manager setDelegate:nil];
+ [manager release];
+
+ [peripheral setDelegate:nil];
+ [peripheral release];
+
+ if (notifier)
+ notifier->deleteLater();
+
+ [self stopWatchers];
+ [super dealloc];
+}
+
+- (CBPeripheral *)peripheral
+{
+ return peripheral;
+}
+
+- (void)watchAfter:(id)object timeout:(DarwinBluetooth::OperationTimeout)type
+{
+ using namespace DarwinBluetooth;
+
+ GCDTimer newWatcher([[GCDTimerObjC alloc] initWithDelegate:self], false /*do not retain*/);
+ [newWatcher watchAfter:object withTimeoutType:type];
+ timeoutWatchdogs.push_back(newWatcher);
+ [newWatcher startWithTimeout:timeoutMS step:200];
+}
+
+- (bool)objectIsUnderWatch:(id)object operation:(DarwinBluetooth::OperationTimeout)type
+{
+ return DarwinBluetooth::qt_find_watchdog(timeoutWatchdogs, object, type) != timeoutWatchdogs.end();
+}
+
+- (void)stopWatchingAfter:(id)object operation:(DarwinBluetooth::OperationTimeout)type
+{
+ auto pos = DarwinBluetooth::qt_find_watchdog(timeoutWatchdogs, object, type);
+ if (pos != timeoutWatchdogs.end()) {
+ [*pos cancelTimer];
+ timeoutWatchdogs.erase(pos);
+ }
+}
+
+- (void)stopWatchers
+{
+ for (auto &watchdog : timeoutWatchdogs)
+ [watchdog cancelTimer];
+ timeoutWatchdogs.clear();
+}
+
+- (void)timeout:(id)sender
+{
+ Q_UNUSED(sender)
+
+ using namespace DarwinBluetooth;
+
+ GCDTimerObjC *watcher = static_cast<GCDTimerObjC *>(sender);
+ id cbObject = [watcher objectUnderWatch];
+ const OperationTimeout type = [watcher timeoutType];
+
+ Q_ASSERT([self objectIsUnderWatch:cbObject operation:type]);
+
+ NSLog(@"Timeout caused by: %@", cbObject);
+
+ // Note that after this switch the 'watcher' is released (we don't
+ // own it anymore), though GCD is probably still holding a reference.
+ const ObjCStrongReference<NSError> nsError(qt_timeoutNSError(type));
+ switch (type) {
+ case OperationTimeout::serviceDiscovery:
+ qCWarning(QT_BT_DARWIN, "Timeout in services discovery");
+ [self peripheral:peripheral didDiscoverServices:nsError];
+ break;
+ case OperationTimeout::includedServicesDiscovery:
+ qCWarning(QT_BT_DARWIN, "Timeout in included services discovery");
+ [self peripheral:peripheral didDiscoverIncludedServicesForService:cbObject error:nsError];
+ break;
+ case OperationTimeout::characteristicsDiscovery:
+ qCWarning(QT_BT_DARWIN, "Timeout in characteristics discovery");
+ [self peripheral:peripheral didDiscoverCharacteristicsForService:cbObject error:nsError];
+ break;
+ case OperationTimeout::characteristicRead:
+ qCWarning(QT_BT_DARWIN, "Timeout while reading a characteristic");
+ [self peripheral:peripheral didUpdateValueForCharacteristic:cbObject error:nsError];
+ break;
+ case OperationTimeout::descriptorsDiscovery:
+ qCWarning(QT_BT_DARWIN, "Timeout in descriptors discovery");
+ [self peripheral:peripheral didDiscoverDescriptorsForCharacteristic:cbObject error:nsError];
+ break;
+ case OperationTimeout::descriptorRead:
+ qCWarning(QT_BT_DARWIN, "Timeout while reading a descriptor");
+ [self peripheral:peripheral didUpdateValueForDescriptor:cbObject error:nsError];
+ break;
+ case OperationTimeout::characteristicWrite:
+ qCWarning(QT_BT_DARWIN, "Timeout while writing a characteristic with response");
+ [self peripheral:peripheral didWriteValueForCharacteristic:cbObject error:nsError];
+ default:;
+ }
+}
+
+- (void)connectToDevice:(const QBluetoothUuid &)aDeviceUuid
+{
+ disconnectPending = false; // Cancel the previous disconnect if any.
+ deviceUuid = aDeviceUuid;
+
+ if (!manager) {
+ // The first time we try to connect, no manager created yet,
+ // no status update received.
+ if (const dispatch_queue_t leQueue = DarwinBluetooth::qt_LE_queue()) {
+ managerState = DarwinBluetooth::CentralManagerUpdating;
+ manager = [[CBCentralManager alloc] initWithDelegate:self queue:leQueue];
+ }
+
+ if (!manager) {
+ managerState = DarwinBluetooth::CentralManagerIdle;
+ qCWarning(QT_BT_DARWIN) << "failed to allocate a central manager";
+ if (notifier)
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
+ }
+ } else if (managerState != DarwinBluetooth::CentralManagerUpdating) {
+ [self retrievePeripheralAndConnect];
+ }
+}
+
+- (void)retrievePeripheralAndConnect
+{
+ Q_ASSERT_X(manager, Q_FUNC_INFO, "invalid central manager (nil)");
+ Q_ASSERT_X(managerState == DarwinBluetooth::CentralManagerIdle,
+ Q_FUNC_INFO, "invalid state");
+
+ if ([self isConnected]) {
+ qCDebug(QT_BT_DARWIN) << "already connected";
+ if (notifier)
+ emit notifier->connected();
+ return;
+ } else if (peripheral) {
+ // Was retrieved already, but not connected
+ // or disconnected.
+ [self connectToPeripheral];
+ return;
+ }
+
+ using namespace DarwinBluetooth;
+
+ // Retrieve a peripheral first ...
+ ObjCScopedPointer<NSMutableArray> uuids([[NSMutableArray alloc] init]);
+ if (!uuids) {
+ qCWarning(QT_BT_DARWIN) << "failed to allocate identifiers";
+ if (notifier)
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
+ return;
+ }
+
+
+ const quint128 qtUuidData(deviceUuid.toUInt128());
+ uuid_t uuidData = {};
+ std::copy(qtUuidData.data, qtUuidData.data + 16, uuidData);
+ const ObjCScopedPointer<NSUUID> nsUuid([[NSUUID alloc] initWithUUIDBytes:uuidData]);
+ if (!nsUuid) {
+ qCWarning(QT_BT_DARWIN) << "failed to allocate NSUUID identifier";
+ if (notifier)
+ emit notifier->CBManagerError(QLowEnergyController::ConnectionError);
+ return;
+ }
+
+ [uuids addObject:nsUuid];
+ // With the latest CoreBluetooth, we can synchronously retrive peripherals:
+ QT_BT_MAC_AUTORELEASEPOOL;
+ NSArray *const peripherals = [manager retrievePeripheralsWithIdentifiers:uuids];
+ if (!peripherals || peripherals.count != 1) {
+ qCWarning(QT_BT_DARWIN) << "failed to retrive a peripheral";
+ if (notifier)
+ emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ return;
+ }
+
+ peripheral = [static_cast<CBPeripheral *>([peripherals objectAtIndex:0]) retain];
+ [self connectToPeripheral];
+}
+
+- (void)connectToPeripheral
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(manager, Q_FUNC_INFO, "invalid central manager (nil)");
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+ Q_ASSERT_X(managerState == CentralManagerIdle, Q_FUNC_INFO, "invalid state");
+
+ // The state is still the same - connecting.
+ if ([self isConnected]) {
+ qCDebug(QT_BT_DARWIN) << "already connected";
+ if (notifier)
+ emit notifier->connected();
+ } else {
+ qCDebug(QT_BT_DARWIN) << "trying to connect";
+ managerState = CentralManagerConnecting;
+ [manager connectPeripheral:peripheral options:nil];
+ }
+}
+
+- (bool)isConnected
+{
+ if (!peripheral)
+ return false;
+
+ return peripheral.state == CBPeripheralStateConnected;
+}
+
+- (void)disconnectFromDevice
+{
+ [self reset];
+
+ if (managerState == DarwinBluetooth::CentralManagerUpdating) {
+ disconnectPending = true; // this is for 'didUpdate' method.
+ if (notifier) {
+ // We were waiting for the first update
+ // with 'PoweredOn' status, when suddenly got disconnected called.
+ // Since we have not attempted to connect yet, emit now.
+ // Note: we do not change the state, since we still maybe interested
+ // in the status update before the next connect attempt.
+ emit notifier->disconnected();
+ }
+ } else {
+ disconnectPending = false;
+ if ([self isConnected])
+ managerState = DarwinBluetooth::CentralManagerDisconnecting;
+ else
+ managerState = DarwinBluetooth::CentralManagerIdle;
+
+ // We have to call -cancelPeripheralConnection: even
+ // if not connected (to cancel a pending connect attempt).
+ // Unfortunately, didDisconnect callback is not always called
+ // (despite of Apple's docs saying it _must_ be).
+ if (peripheral)
+ [manager cancelPeripheralConnection:peripheral];
+ }
+}
+
+- (void)discoverServices
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+ Q_ASSERT_X(managerState == CentralManagerIdle, Q_FUNC_INFO, "invalid state");
+
+ // From Apple's docs:
+ //
+ //"If the servicesUUIDs parameter is nil, all the available
+ //services of the peripheral are returned; setting the
+ //parameter to nil is considerably slower and is not recommended."
+ //
+ // ... but we'd like to have them all:
+ [peripheral setDelegate:self];
+ managerState = CentralManagerDiscovering;
+ [self watchAfter:peripheral timeout:OperationTimeout::serviceDiscovery];
+ [peripheral discoverServices:nil];
+}
+
+- (void)discoverIncludedServices
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(managerState == CentralManagerIdle, Q_FUNC_INFO, "invalid state");
+ Q_ASSERT_X(manager, Q_FUNC_INFO, "invalid manager (nil)");
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ NSArray *const services = peripheral.services;
+ if (!services || !services.count) {
+ // A peripheral without any services at all.
+ if (notifier)
+ emit notifier->serviceDiscoveryFinished();
+ } else {
+ // 'reset' also calls retain on a parameter.
+ servicesToVisitNext.reset(nil);
+ servicesToVisit.reset([NSMutableArray arrayWithArray:services]);
+ currentService = 0;
+ visitedServices.reset([NSMutableSet setWithCapacity:peripheral.services.count]);
+
+ CBService *const s = [services objectAtIndex:currentService];
+ [visitedServices addObject:s];
+ managerState = CentralManagerDiscovering;
+ [self watchAfter:s timeout:OperationTimeout::includedServicesDiscovery];
+ [peripheral discoverIncludedServices:nil forService:s];
+ }
+}
+
+- (void)discoverServiceDetails:(const QBluetoothUuid &)serviceUuid
+{
+ // This function does not change 'managerState', since it
+ // can be called concurrently (not waiting for the previous
+ // discovery to finish).
+
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(managerState != CentralManagerUpdating, Q_FUNC_INFO, "invalid state");
+ Q_ASSERT_X(!serviceUuid.isNull(), Q_FUNC_INFO, "invalid service UUID");
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+
+ if (servicesToDiscoverDetails.contains(serviceUuid)) {
+ qCWarning(QT_BT_DARWIN) << "already discovering for"
+ << serviceUuid;
+ return;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if (CBService *const service = [self serviceForUUID:serviceUuid]) {
+ servicesToDiscoverDetails.append(serviceUuid);
+ [self watchAfter:service timeout:OperationTimeout::characteristicsDiscovery];
+ [peripheral discoverCharacteristics:nil forService:service];
+ return;
+ }
+
+ qCWarning(QT_BT_DARWIN) << "unknown service uuid"
+ << serviceUuid;
+
+ if (notifier)
+ emit notifier->CBManagerError(serviceUuid, QLowEnergyService::UnknownError);
+}
+
+- (void)readCharacteristics:(CBService *)service
+{
+ // This method does not change 'managerState', we can
+ // have several 'detail discoveries' active.
+ Q_ASSERT_X(service, Q_FUNC_INFO, "invalid service (nil)");
+
+ using namespace DarwinBluetooth;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ Q_ASSERT_X(managerState != CentralManagerUpdating, Q_FUNC_INFO, "invalid state");
+ Q_ASSERT_X(manager, Q_FUNC_INFO, "invalid manager (nil)");
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+
+ if (!service.characteristics || !service.characteristics.count)
+ return [self serviceDetailsDiscoveryFinished:service];
+
+ NSArray *const cs = service.characteristics;
+ for (CBCharacteristic *c in cs) {
+ if (c.properties & CBCharacteristicPropertyRead) {
+ [self watchAfter:c timeout:OperationTimeout::characteristicRead];
+ return [peripheral readValueForCharacteristic:c];
+ }
+ }
+
+ // No readable properties? Discover descriptors then:
+ [self discoverDescriptors:service];
+}
+
+- (void)discoverDescriptors:(CBService *)service
+{
+ // This method does not change 'managerState', we can have
+ // several discoveries active.
+ Q_ASSERT_X(service, Q_FUNC_INFO, "invalid service (nil)");
+
+ using namespace DarwinBluetooth;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ Q_ASSERT_X(managerState != CentralManagerUpdating,
+ Q_FUNC_INFO, "invalid state");
+ Q_ASSERT_X(manager, Q_FUNC_INFO, "invalid manager (nil)");
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+
+ if (!service.characteristics || !service.characteristics.count) {
+ [self serviceDetailsDiscoveryFinished:service];
+ } else {
+ // Start from 0 and continue in the callback.
+ CBCharacteristic *ch = [service.characteristics objectAtIndex:0];
+ [self watchAfter:ch timeout:OperationTimeout::descriptorsDiscovery];
+ [peripheral discoverDescriptorsForCharacteristic:ch];
+ }
+}
+
+- (void)readDescriptors:(CBService *)service
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(service, Q_FUNC_INFO, "invalid service (nil)");
+ Q_ASSERT_X(managerState != CentralManagerUpdating, Q_FUNC_INFO, "invalid state");
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ NSArray *const cs = service.characteristics;
+ // We can never be here if we have no characteristics.
+ Q_ASSERT_X(cs && cs.count, Q_FUNC_INFO, "invalid service");
+ for (CBCharacteristic *c in cs) {
+ if (c.descriptors && c.descriptors.count) {
+ CBDescriptor *desc = [c.descriptors objectAtIndex:0];
+ [self watchAfter:desc timeout:OperationTimeout::descriptorRead];
+ return [peripheral readValueForDescriptor:desc];
+ }
+ }
+
+ // No descriptors to read, done.
+ [self serviceDetailsDiscoveryFinished:service];
+}
+
+- (void)serviceDetailsDiscoveryFinished:(CBService *)service
+{
+ Q_ASSERT_X(service, Q_FUNC_INFO, "invalid service (nil)");
+
+ using namespace DarwinBluetooth;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ const QBluetoothUuid serviceUuid(qt_uuid(service.UUID));
+ servicesToDiscoverDetails.removeAll(serviceUuid);
+
+ const NSUInteger nHandles = qt_countGATTEntries(service);
+ Q_ASSERT_X(nHandles, Q_FUNC_INFO, "unexpected number of GATT entires");
+
+ const QLowEnergyHandle maxHandle = std::numeric_limits<QLowEnergyHandle>::max();
+ if (nHandles >= maxHandle || lastValidHandle > maxHandle - nHandles) {
+ // Well, that's unlikely :) But we must be sure.
+ qCWarning(QT_BT_DARWIN) << "can not allocate more handles";
+ if (notifier)
+ notifier->CBManagerError(serviceUuid, QLowEnergyService::OperationError);
+ return;
+ }
+
+ // A temporary service object to pass the details.
+ // Set only uuid, characteristics and descriptors (and probably values),
+ // nothing else is needed.
+ QSharedPointer<QLowEnergyServicePrivate> qtService(new QLowEnergyServicePrivate);
+ qtService->uuid = serviceUuid;
+ // We 'register' handles/'CBentities' even if qlowenergycontroller (delegate)
+ // later fails to do this with some error. Otherwise, if we try to implement
+ // rollback/transaction logic interface is getting too ugly/complicated.
+ ++lastValidHandle;
+ serviceMap[lastValidHandle] = service;
+ qtService->startHandle = lastValidHandle;
+
+ NSArray *const cs = service.characteristics;
+ // Now map chars/descriptors and handles.
+ if (cs && cs.count) {
+ QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList;
+
+ for (CBCharacteristic *c in cs) {
+ ++lastValidHandle;
+ // Register this characteristic:
+ charMap[lastValidHandle] = c;
+ // Create a Qt's internal characteristic:
+ QLowEnergyServicePrivate::CharData newChar = {};
+ newChar.uuid = qt_uuid(c.UUID);
+ const int cbProps = c.properties & 0xff;
+ newChar.properties = static_cast<QLowEnergyCharacteristic::PropertyTypes>(cbProps);
+ newChar.value = qt_bytearray(c.value);
+ newChar.valueHandle = lastValidHandle;
+
+ NSArray *const ds = c.descriptors;
+ if (ds && ds.count) {
+ QHash<QLowEnergyHandle, QLowEnergyServicePrivate::DescData> descList;
+ for (CBDescriptor *d in ds) {
+ // Register this descriptor:
+ ++lastValidHandle;
+ descMap[lastValidHandle] = d;
+ // Create a Qt's internal descriptor:
+ QLowEnergyServicePrivate::DescData newDesc = {};
+ newDesc.uuid = qt_uuid(d.UUID);
+ newDesc.value = qt_bytearray(static_cast<NSObject *>(d.value));
+ descList[lastValidHandle] = newDesc;
+ // Check, if it's client characteristic configuration descriptor:
+ if (newDesc.uuid == QBluetoothUuid::ClientCharacteristicConfiguration) {
+ if (newDesc.value.size() && (newDesc.value[0] & 3))
+ [peripheral setNotifyValue:YES forCharacteristic:c];
+ }
+ }
+
+ newChar.descriptorList = descList;
+ }
+
+ charList[newChar.valueHandle] = newChar;
+ }
+
+ qtService->characteristicList = charList;
+ }
+
+ qtService->endHandle = lastValidHandle;
+
+ if (notifier)
+ emit notifier->serviceDetailsDiscoveryFinished(qtService);
+}
+
+- (void)performNextRequest
+{
+ using namespace DarwinBluetooth;
+
+ if (requestPending || !requests.size())
+ return;
+
+ switch (requests.head().type) {
+ case LERequest::CharRead:
+ case LERequest::DescRead:
+ return [self performNextReadRequest];
+ case LERequest::CharWrite:
+ case LERequest::DescWrite:
+ case LERequest::ClientConfiguration:
+ return [self performNextWriteRequest];
+ default:
+ // Should never happen.
+ Q_ASSERT(0);
+ }
+}
+
+- (void)performNextReadRequest
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+ Q_ASSERT_X(!requestPending, Q_FUNC_INFO, "processing another request");
+ Q_ASSERT_X(requests.size(), Q_FUNC_INFO, "no requests to handle");
+ Q_ASSERT_X(requests.head().type == LERequest::CharRead
+ || requests.head().type == LERequest::DescRead,
+ Q_FUNC_INFO, "not a read request");
+
+ const LERequest request(requests.dequeue());
+ if (request.type == LERequest::CharRead) {
+ if (!charMap.contains(request.handle)) {
+ qCWarning(QT_BT_DARWIN) << "characteristic with handle"
+ << request.handle << "not found";
+ return [self performNextRequest];
+ }
+
+ requestPending = true;
+ currentReadHandle = request.handle;
+ // Timeouts: for now, we do not alert timeoutWatchdog - never had such
+ // bug reports and after all a read timeout can be handled externally.
+ [peripheral readValueForCharacteristic:charMap[request.handle]];
+ } else {
+ if (!descMap.contains(request.handle)) {
+ qCWarning(QT_BT_DARWIN) << "descriptor with handle"
+ << request.handle << "not found";
+ return [self performNextRequest];
+ }
+
+ requestPending = true;
+ currentReadHandle = request.handle;
+ // Timeouts: see the comment above (CharRead).
+ [peripheral readValueForDescriptor:descMap[request.handle]];
+ }
+}
+
+- (void)performNextWriteRequest
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+ Q_ASSERT_X(!requestPending, Q_FUNC_INFO, "processing another request");
+ Q_ASSERT_X(requests.size(), Q_FUNC_INFO, "no requests to handle");
+ Q_ASSERT_X(requests.head().type == LERequest::CharWrite
+ || requests.head().type == LERequest::DescWrite
+ || requests.head().type == LERequest::ClientConfiguration,
+ Q_FUNC_INFO, "not a write request");
+
+ const LERequest request(requests.dequeue());
+
+ if (request.type == LERequest::DescWrite) {
+ if (!descMap.contains(request.handle)) {
+ qCWarning(QT_BT_DARWIN) << "handle:" << request.handle
+ << "not found";
+ return [self performNextRequest];
+ }
+
+ CBDescriptor *const descriptor = descMap[request.handle];
+ ObjCStrongReference<NSData> data(data_from_bytearray(request.value));
+ if (!data) {
+ // Even if qtData.size() == 0, we still need NSData object.
+ qCWarning(QT_BT_DARWIN) << "failed to allocate an NSData object";
+ return [self performNextRequest];
+ }
+
+ if (![self cacheWriteValue:request.value for:descriptor])
+ return [self performNextRequest];
+
+ requestPending = true;
+ return [peripheral writeValue:data.data() forDescriptor:descriptor];
+ } else {
+ if (!charMap.contains(request.handle)) {
+ qCWarning(QT_BT_DARWIN) << "characteristic with handle:"
+ << request.handle << "not found";
+ return [self performNextRequest];
+ }
+
+ CBCharacteristic *const characteristic = charMap[request.handle];
+
+ if (request.type == LERequest::ClientConfiguration) {
+ const QBluetoothUuid qtUuid(QBluetoothUuid::ClientCharacteristicConfiguration);
+ CBDescriptor *const descriptor = [self descriptor:qtUuid forCharacteristic:characteristic];
+ Q_ASSERT_X(descriptor, Q_FUNC_INFO, "no client characteristic "
+ "configuration descriptor found");
+
+ if (![self cacheWriteValue:request.value for:descriptor])
+ return [self performNextRequest];
+
+ bool enable = false;
+ if (request.value.size())
+ enable = request.value[0] & 3;
+
+ requestPending = true;
+ [peripheral setNotifyValue:enable forCharacteristic:characteristic];
+ } else {
+ ObjCStrongReference<NSData> data(data_from_bytearray(request.value));
+ if (!data) {
+ // Even if qtData.size() == 0, we still need NSData object.
+ qCWarning(QT_BT_DARWIN) << "failed to allocate NSData object";
+ return [self performNextRequest];
+ }
+
+ // TODO: check what happens if I'm using NSData with length 0.
+ if (request.withResponse) {
+ if (![self cacheWriteValue:request.value for:characteristic])
+ return [self performNextRequest];
+
+ requestPending = true;
+ [self watchAfter:characteristic timeout:OperationTimeout::characteristicWrite];
+ [peripheral writeValue:data.data() forCharacteristic:characteristic
+ type:CBCharacteristicWriteWithResponse];
+ } else {
+ [peripheral writeValue:data.data() forCharacteristic:characteristic
+ type:CBCharacteristicWriteWithoutResponse];
+ [self performNextRequest];
+ }
+ }
+ }
+}
+
+- (void)setNotifyValue:(const QByteArray &)value
+ forCharacteristic:(QLowEnergyHandle)charHandle
+ onService:(const QBluetoothUuid &)serviceUuid
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle (0)");
+
+ if (!charMap.contains(charHandle)) {
+ qCWarning(QT_BT_DARWIN) << "unknown characteristic handle"
+ << charHandle;
+ if (notifier) {
+ emit notifier->CBManagerError(serviceUuid,
+ QLowEnergyService::DescriptorWriteError);
+ }
+ return;
+ }
+
+ // At the moment we call setNotifyValue _only_ from 'writeDescriptor';
+ // from Qt's API POV it's a descriptor write operation and we must report
+ // it back, so check _now_ that we really have this descriptor.
+ const QBluetoothUuid qtUuid(QBluetoothUuid::ClientCharacteristicConfiguration);
+ if (![self descriptor:qtUuid forCharacteristic:charMap[charHandle]]) {
+ qCWarning(QT_BT_DARWIN) << "no client characteristic configuration found";
+ if (notifier) {
+ emit notifier->CBManagerError(serviceUuid,
+ QLowEnergyService::DescriptorWriteError);
+ }
+ return;
+ }
+
+ LERequest request;
+ request.type = LERequest::ClientConfiguration;
+ request.handle = charHandle;
+ request.value = value;
+
+ requests.enqueue(request);
+ [self performNextRequest];
+}
+
+- (void)readCharacteristic:(QLowEnergyHandle)charHandle
+ onService:(const QBluetoothUuid &)serviceUuid
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle (0)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if (!charMap.contains(charHandle)) {
+ qCWarning(QT_BT_DARWIN) << "characteristic:" << charHandle << "not found";
+ if (notifier) {
+ emit notifier->CBManagerError(serviceUuid,
+ QLowEnergyService::CharacteristicReadError);
+
+ }
+ return;
+ }
+
+ LERequest request;
+ request.type = LERequest::CharRead;
+ request.handle = charHandle;
+
+ requests.enqueue(request);
+ [self performNextRequest];
+}
+
+- (void)write:(const QByteArray &)value
+ charHandle:(QLowEnergyHandle)charHandle
+ onService:(const QBluetoothUuid &)serviceUuid
+ withResponse:(bool)withResponse
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(charHandle, Q_FUNC_INFO, "invalid characteristic handle (0)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if (!charMap.contains(charHandle)) {
+ qCWarning(QT_BT_DARWIN) << "characteristic:" << charHandle << "not found";
+ if (notifier) {
+ emit notifier->CBManagerError(serviceUuid,
+ QLowEnergyService::CharacteristicWriteError);
+ }
+ return;
+ }
+
+ LERequest request;
+ request.type = LERequest::CharWrite;
+ request.withResponse = withResponse;
+ request.handle = charHandle;
+ request.value = value;
+
+ requests.enqueue(request);
+ [self performNextRequest];
+}
+
+- (void)readDescriptor:(QLowEnergyHandle)descHandle
+ onService:(const QBluetoothUuid &)serviceUuid
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(descHandle, Q_FUNC_INFO, "invalid descriptor handle (0)");
+
+ if (!descMap.contains(descHandle)) {
+ qCWarning(QT_BT_DARWIN) << "handle:" << descHandle << "not found";
+ if (notifier) {
+ emit notifier->CBManagerError(serviceUuid,
+ QLowEnergyService::DescriptorReadError);
+ }
+ return;
+ }
+
+ LERequest request;
+ request.type = LERequest::DescRead;
+ request.handle = descHandle;
+
+ requests.enqueue(request);
+ [self performNextRequest];
+}
+
+- (void)write:(const QByteArray &)value
+ descHandle:(QLowEnergyHandle)descHandle
+ onService:(const QBluetoothUuid &)serviceUuid
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(descHandle, Q_FUNC_INFO, "invalid descriptor handle (0)");
+
+ if (!descMap.contains(descHandle)) {
+ qCWarning(QT_BT_DARWIN) << "handle:" << descHandle << "not found";
+ if (notifier) {
+ emit notifier->CBManagerError(serviceUuid,
+ QLowEnergyService::DescriptorWriteError);
+ }
+ return;
+ }
+
+ LERequest request;
+ request.type = LERequest::DescWrite;
+ request.handle = descHandle;
+ request.value = value;
+
+ requests.enqueue(request);
+ [self performNextRequest];
+}
+
+// Aux. methods:
+
+- (CBService *)serviceForUUID:(const QBluetoothUuid &)qtUuid
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(!qtUuid.isNull(), Q_FUNC_INFO, "invalid uuid");
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+
+ ObjCStrongReference<NSMutableArray> toVisit([NSMutableArray arrayWithArray:peripheral.services], true);
+ ObjCStrongReference<NSMutableArray> toVisitNext([[NSMutableArray alloc] init], false);
+ ObjCStrongReference<NSMutableSet> visitedNodes([[NSMutableSet alloc] init], false);
+
+ while (true) {
+ for (NSUInteger i = 0, e = [toVisit count]; i < e; ++i) {
+ CBService *const s = [toVisit objectAtIndex:i];
+ if (equal_uuids(s.UUID, qtUuid))
+ return s;
+ if (![visitedNodes containsObject:s] && s.includedServices && s.includedServices.count) {
+ [visitedNodes addObject:s];
+ [toVisitNext addObjectsFromArray:s.includedServices];
+ }
+ }
+
+ if (![toVisitNext count])
+ return nil;
+
+ toVisit.resetWithoutRetain(toVisitNext.take());
+ toVisitNext.resetWithoutRetain([[NSMutableArray alloc] init]);
+ }
+
+ return nil;
+}
+
+- (CBCharacteristic *)nextCharacteristicForService:(CBService*)service
+ startingFrom:(CBCharacteristic *)characteristic
+{
+ Q_ASSERT_X(service, Q_FUNC_INFO, "invalid service (nil)");
+ Q_ASSERT_X(characteristic, Q_FUNC_INFO, "invalid characteristic (nil)");
+ Q_ASSERT_X(service.characteristics, Q_FUNC_INFO, "invalid service");
+ Q_ASSERT_X(service.characteristics.count, Q_FUNC_INFO, "invalid service");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ // TODO: test that we NEVER have the same characteristic twice in array!
+ // At the moment I just protect against this by iterating in a reverse
+ // order (at least avoiding a potential inifite loop with '-indexOfObject:').
+ NSArray *const cs = service.characteristics;
+ if (cs.count == 1)
+ return nil;
+
+ for (NSUInteger index = cs.count - 1; index != 0; --index) {
+ if ([cs objectAtIndex:index] == characteristic) {
+ if (index + 1 == cs.count)
+ return nil;
+ else
+ return [cs objectAtIndex:index + 1];
+ }
+ }
+
+ Q_ASSERT_X([cs objectAtIndex:0] == characteristic, Q_FUNC_INFO,
+ "characteristic was not found in service.characteristics");
+
+ return [cs objectAtIndex:1];
+}
+
+- (CBCharacteristic *)nextCharacteristicForService:(CBService*)service
+ startingFrom:(CBCharacteristic *)characteristic
+ properties:(CBCharacteristicProperties)properties
+{
+ Q_ASSERT_X(service, Q_FUNC_INFO, "invalid service (nil)");
+ Q_ASSERT_X(characteristic, Q_FUNC_INFO, "invalid characteristic (nil)");
+ Q_ASSERT_X(service.characteristics, Q_FUNC_INFO, "invalid service");
+ Q_ASSERT_X(service.characteristics.count, Q_FUNC_INFO, "invalid service");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ // TODO: test that we NEVER have the same characteristic twice in array!
+ // At the moment I just protect against this by iterating in a reverse
+ // order (at least avoiding a potential inifite loop with '-indexOfObject:').
+ NSArray *const cs = service.characteristics;
+ if (cs.count == 1)
+ return nil;
+
+ NSUInteger index = cs.count - 1;
+ for (; index != 0; --index) {
+ if ([cs objectAtIndex:index] == characteristic) {
+ if (index + 1 == cs.count) {
+ return nil;
+ } else {
+ index += 1;
+ break;
+ }
+ }
+ }
+
+ if (!index) {
+ Q_ASSERT_X([cs objectAtIndex:0] == characteristic, Q_FUNC_INFO,
+ "characteristic not found in service.characteristics");
+ index = 1;
+ }
+
+ for (const NSUInteger e = cs.count; index < e; ++index) {
+ CBCharacteristic *const c = [cs objectAtIndex:index];
+ if (c.properties & properties)
+ return c;
+ }
+
+ return nil;
+}
+
+- (CBDescriptor *)nextDescriptorForCharacteristic:(CBCharacteristic *)characteristic
+ startingFrom:(CBDescriptor *)descriptor
+{
+ Q_ASSERT_X(characteristic, Q_FUNC_INFO, "invalid characteristic (nil)");
+ Q_ASSERT_X(descriptor, Q_FUNC_INFO, "invalid descriptor (nil)");
+ Q_ASSERT_X(characteristic.descriptors, Q_FUNC_INFO, "invalid characteristic");
+ Q_ASSERT_X(characteristic.descriptors.count, Q_FUNC_INFO, "invalid characteristic");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ NSArray *const ds = characteristic.descriptors;
+ if (ds.count == 1)
+ return nil;
+
+ for (NSUInteger index = ds.count - 1; index != 0; --index) {
+ if ([ds objectAtIndex:index] == descriptor) {
+ if (index + 1 == ds.count)
+ return nil;
+ else
+ return [ds objectAtIndex:index + 1];
+ }
+ }
+
+ Q_ASSERT_X([ds objectAtIndex:0] == descriptor, Q_FUNC_INFO,
+ "descriptor was not found in characteristic.descriptors");
+
+ return [ds objectAtIndex:1];
+}
+
+- (CBDescriptor *)descriptor:(const QBluetoothUuid &)qtUuid
+ forCharacteristic:(CBCharacteristic *)ch
+{
+ if (qtUuid.isNull() || !ch)
+ return nil;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ CBDescriptor *descriptor = nil;
+ NSArray *const ds = ch.descriptors;
+ if (ds && ds.count) {
+ for (CBDescriptor *d in ds) {
+ if (DarwinBluetooth::equal_uuids(d.UUID, qtUuid)) {
+ descriptor = d;
+ break;
+ }
+ }
+ }
+
+ return descriptor;
+}
+
+- (bool)cacheWriteValue:(const QByteArray &)value for:(NSObject *)obj
+{
+ Q_ASSERT_X(obj, Q_FUNC_INFO, "invalid object (nil)");
+
+ if ([obj isKindOfClass:[CBCharacteristic class]]) {
+ CBCharacteristic *const ch = static_cast<CBCharacteristic *>(obj);
+ if (!charMap.key(ch)) {
+ qCWarning(QT_BT_DARWIN) << "unexpected characteristic, no handle found";
+ return false;
+ }
+ } else if ([obj isKindOfClass:[CBDescriptor class]]) {
+ CBDescriptor *const d = static_cast<CBDescriptor *>(obj);
+ if (!descMap.key(d)) {
+ qCWarning(QT_BT_DARWIN) << "unexpected descriptor, no handle found";
+ return false;
+ }
+ } else {
+ qCWarning(QT_BT_DARWIN) << "invalid object, characteristic "
+ "or descriptor required";
+ return false;
+ }
+
+ if (valuesToWrite.contains(obj)) {
+ // It can be a result of some previous errors - for example,
+ // we never got a callback from a previous write.
+ qCWarning(QT_BT_DARWIN) << "already has a cached value for this "
+ "object, the value will be replaced";
+ }
+
+ valuesToWrite[obj] = value;
+ return true;
+}
+
+- (void)reset
+{
+ requestPending = false;
+ valuesToWrite.clear();
+ requests.clear();
+ servicesToDiscoverDetails.clear();
+ lastValidHandle = 0;
+ serviceMap.clear();
+ charMap.clear();
+ descMap.clear();
+ currentReadHandle = 0;
+ [self stopWatchers];
+ // TODO: also serviceToVisit/VisitNext and visitedServices ?
+}
+
+- (void)handleReadWriteError:(NSError *)error
+{
+ Q_ASSERT(notifier);
+
+ switch (error.code) {
+ case 0x05: // GATT_INSUFFICIENT_AUTHORIZATION
+ case 0x0F: // GATT_INSUFFICIENT_ENCRYPTION
+ emit notifier->CBManagerError(QLowEnergyController::AuthorizationError);
+ [self detach];
+ break;
+ default:
+ break;
+ }
+}
+
+// CBCentralManagerDelegate (the real one).
+
+- (void)centralManagerDidUpdateState:(CBCentralManager *)central
+{
+ using namespace DarwinBluetooth;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability-new"
+
+ const auto state = central.state;
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
+ if (state == CBManagerStateUnknown
+ || state == CBManagerStateResetting) {
+#else
+ if (state == CBCentralManagerStateUnknown
+ || state == CBCentralManagerStateResetting) {
+#endif
+ // We still have to wait, docs say:
+ // "The current state of the central manager is unknown;
+ // an update is imminent." or
+ // "The connection with the system service was momentarily
+ // lost; an update is imminent."
+ return;
+ }
+
+ // Let's check some states we do not like first:
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
+ if (state == CBManagerStateUnsupported || state == CBManagerStateUnauthorized) {
+#else
+ if (state == CBCentralManagerStateUnsupported || state == CBCentralManagerStateUnauthorized) {
+#endif
+ if (managerState == CentralManagerUpdating) {
+ // We tried to connect just to realize, LE is not supported. Report this.
+ managerState = CentralManagerIdle;
+ if (notifier)
+ emit notifier->LEnotSupported();
+ } else {
+ // TODO: if we are here, LE _was_ supported and we first managed to update
+ // and reset managerState from CentralManagerUpdating.
+ managerState = CentralManagerIdle;
+ if (notifier)
+ emit notifier->CBManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
+ }
+ [self stopWatchers];
+ return;
+ }
+
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
+ if (state == CBManagerStatePoweredOff) {
+#else
+ if (state == CBCentralManagerStatePoweredOff) {
+#endif
+
+ if (managerState == CentralManagerUpdating) {
+ managerState = CentralManagerIdle;
+ // I've seen this instead of Unsupported on OS X.
+ if (notifier)
+ emit notifier->LEnotSupported();
+ } else {
+ managerState = CentralManagerIdle;
+ // TODO: we need a better error +
+ // what will happen if later the state changes to PoweredOn???
+ if (notifier)
+ emit notifier->CBManagerError(QLowEnergyController::InvalidBluetoothAdapterError);
+ }
+ [self stopWatchers];
+ return;
+ }
+
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
+ if (state == CBManagerStatePoweredOn) {
+#else
+ if (state == CBCentralManagerStatePoweredOn) {
+#endif
+ if (managerState == CentralManagerUpdating && !disconnectPending) {
+ managerState = CentralManagerIdle;
+ [self retrievePeripheralAndConnect];
+ }
+ } else {
+ // We actually handled all known states, but .. Core Bluetooth can change?
+ Q_ASSERT_X(0, Q_FUNC_INFO, "invalid central's state");
+ }
+
+#pragma clang diagnostic pop
+}
+
+- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)aPeripheral
+{
+ Q_UNUSED(central)
+ Q_UNUSED(aPeripheral)
+
+ if (managerState != DarwinBluetooth::CentralManagerConnecting) {
+ // We called cancel but before disconnected, managed to connect?
+ return;
+ }
+
+ managerState = DarwinBluetooth::CentralManagerIdle;
+ if (notifier)
+ emit notifier->connected();
+}
+
+- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)aPeripheral
+ error:(NSError *)error
+{
+ Q_UNUSED(central)
+ Q_UNUSED(aPeripheral)
+ Q_UNUSED(error)
+
+ if (managerState != DarwinBluetooth::CentralManagerConnecting) {
+ // Canceled already.
+ return;
+ }
+
+ managerState = DarwinBluetooth::CentralManagerIdle;
+ // TODO: better error mapping is required.
+ if (notifier)
+ notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+}
+
+- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)aPeripheral
+ error:(NSError *)error
+{
+ Q_UNUSED(central)
+ Q_UNUSED(aPeripheral)
+
+ // Clear internal caches/data.
+ [self reset];
+
+ if (error && managerState == DarwinBluetooth::CentralManagerDisconnecting) {
+ managerState = DarwinBluetooth::CentralManagerIdle;
+ qCWarning(QT_BT_DARWIN) << "failed to disconnect";
+ if (notifier)
+ emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
+ } else {
+ managerState = DarwinBluetooth::CentralManagerIdle;
+ if (notifier)
+ emit notifier->disconnected();
+ }
+}
+
+// CBPeripheralDelegate.
+
+- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverServices:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+
+ using namespace DarwinBluetooth;
+
+ if (managerState != CentralManagerDiscovering) {
+ // Canceled by -disconnectFromDevice, or as a result of a timeout.
+ return;
+ }
+
+ if (![self objectIsUnderWatch:aPeripheral operation:OperationTimeout::serviceDiscovery]) // Timed out already
+ return;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ [self stopWatchingAfter:aPeripheral operation:OperationTimeout::serviceDiscovery];
+
+ managerState = CentralManagerIdle;
+
+ if (error) {
+ NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
+ // TODO: better error mapping required.
+ if (notifier)
+ emit notifier->CBManagerError(QLowEnergyController::UnknownError);
+ }
+
+ [self discoverIncludedServices];
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral
+ didModifyServices:(NSArray<CBService *> *)invalidatedServices
+{
+ Q_UNUSED(aPeripheral)
+ Q_UNUSED(invalidatedServices)
+
+ qCWarning(QT_BT_DARWIN) << "The peripheral has modified its services.";
+ // "This method is invoked whenever one or more services of a peripheral have changed.
+ // A peripheral’s services have changed if:
+ // * A service is removed from the peripheral’s database
+ // * A new service is added to the peripheral’s database
+ // * A service that was previously removed from the peripheral’s
+ // database is readded to the database at a different location"
+
+ // In case new services were added - we have to discover them.
+ // In case some were removed - we can end up with dangling pointers
+ // (see our 'watchdogs', for example). To handle the situation
+ // we stop all current operations here, report to QLowEnergyController
+ // so that it can trigger re-discovery.
+ [self reset];
+ managerState = DarwinBluetooth::CentralManagerIdle;
+ if (notifier)
+ emit notifier->servicesWereModified();
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverIncludedServicesForService:(CBService *)service
+ error:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+
+ using namespace DarwinBluetooth;
+
+ if (managerState != CentralManagerDiscovering) {
+ // Canceled by disconnectFromDevice or -peripheralDidDisconnect...
+ return;
+ }
+
+ if (![self objectIsUnderWatch:service operation:OperationTimeout::includedServicesDiscovery])
+ return;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+
+ [self stopWatchingAfter:service operation:OperationTimeout::includedServicesDiscovery];
+ managerState = CentralManagerIdle;
+
+ if (error) {
+ NSLog(@"%s: finished with error %@ for service %@",
+ Q_FUNC_INFO, error, service.UUID);
+ } else if (service.includedServices && service.includedServices.count) {
+ // Now we have even more services to do included services discovery ...
+ if (!servicesToVisitNext)
+ servicesToVisitNext.reset([NSMutableArray arrayWithArray:service.includedServices]);
+ else
+ [servicesToVisitNext addObjectsFromArray:service.includedServices];
+ }
+
+ // Do we have something else to discover on this 'level'?
+ ++currentService;
+
+ for (const NSUInteger e = [servicesToVisit count]; currentService < e; ++currentService) {
+ CBService *const s = [servicesToVisit objectAtIndex:currentService];
+ if (![visitedServices containsObject:s]) {
+ // Continue with discovery ...
+ [visitedServices addObject:s];
+ managerState = CentralManagerDiscovering;
+ [self watchAfter:s timeout:OperationTimeout::includedServicesDiscovery];
+ return [peripheral discoverIncludedServices:nil forService:s];
+ }
+ }
+
+ // No services to visit more on this 'level'.
+
+ if (servicesToVisitNext && [servicesToVisitNext count]) {
+ servicesToVisit.resetWithoutRetain(servicesToVisitNext.take());
+
+ currentService = 0;
+ for (const NSUInteger e = [servicesToVisit count]; currentService < e; ++currentService) {
+ CBService *const s = [servicesToVisit objectAtIndex:currentService];
+ if (![visitedServices containsObject:s]) {
+ [visitedServices addObject:s];
+ managerState = CentralManagerDiscovering;
+ [self watchAfter:s timeout:OperationTimeout::includedServicesDiscovery];
+ return [peripheral discoverIncludedServices:nil forService:s];
+ }
+ }
+ }
+
+ // Finally, if we're here, the service discovery is done!
+
+ // Release all these things now, no need to prolong their lifetime.
+ visitedServices.reset(nil);
+ servicesToVisit.reset(nil);
+ servicesToVisitNext.reset(nil);
+
+ if (notifier)
+ emit notifier->serviceDiscoveryFinished();
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverCharacteristicsForService:(CBService *)service
+ error:(NSError *)error
+{
+ // This method does not change 'managerState', we can have several
+ // discoveries active.
+ Q_UNUSED(aPeripheral)
+
+ if (!notifier) {
+ // Detached.
+ return;
+ }
+
+ using namespace DarwinBluetooth;
+
+ if (![self objectIsUnderWatch:service operation:OperationTimeout::characteristicsDiscovery])
+ return;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ [self stopWatchingAfter:service operation:OperationTimeout::characteristicsDiscovery];
+
+ Q_ASSERT_X(managerState != CentralManagerUpdating, Q_FUNC_INFO, "invalid state");
+
+ if (error) {
+ NSLog(@"%s failed with error: %@", Q_FUNC_INFO, error);
+ // We did not discover any characteristics and can not discover descriptors,
+ // inform our delegate (it will set a service state also).
+ emit notifier->CBManagerError(qt_uuid(service.UUID), QLowEnergyController::UnknownError);
+ }
+
+ [self readCharacteristics:service];
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral
+ didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
+ error:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+
+ if (!notifier) // Detached.
+ return;
+
+ using namespace DarwinBluetooth;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ const bool readMatch = [self objectIsUnderWatch:characteristic operation:OperationTimeout::characteristicRead];
+ if (readMatch)
+ [self stopWatchingAfter:characteristic operation:OperationTimeout::characteristicRead];
+
+ Q_ASSERT_X(managerState != CentralManagerUpdating, Q_FUNC_INFO, "invalid state");
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+
+
+ // First, let's check if we're discovering a service details now.
+ CBService *const service = characteristic.service;
+ const QBluetoothUuid qtUuid(qt_uuid(service.UUID));
+ const bool isDetailsDiscovery = servicesToDiscoverDetails.contains(qtUuid);
+ const QLowEnergyHandle chHandle = charMap.key(characteristic);
+
+ if (error) {
+ NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
+ if (!isDetailsDiscovery) {
+ if (chHandle && chHandle == currentReadHandle) {
+ currentReadHandle = 0;
+ requestPending = false;
+ emit notifier->CBManagerError(qtUuid, QLowEnergyService::CharacteristicReadError);
+ [self handleReadWriteError:error];
+ [self performNextRequest];
+ }
+ return;
+ }
+ }
+
+ if (isDetailsDiscovery) {
+ if (readMatch) {
+ // Test if we have any other characteristic to read yet.
+ CBCharacteristic *const next = [self nextCharacteristicForService:characteristic.service
+ startingFrom:characteristic properties:CBCharacteristicPropertyRead];
+ if (next) {
+ [self watchAfter:next timeout:OperationTimeout::characteristicRead];
+ [peripheral readValueForCharacteristic:next];
+ } else {
+ [self discoverDescriptors:characteristic.service];
+ }
+ }
+ } else {
+ // This is (probably) the result of update notification.
+ // It's very possible we can have an invalid handle here (0) -
+ // if something esle is wrong (we subscribed for a notification),
+ // disconnected (but other application is connected) and still receiveing
+ // updated values ...
+ // TODO: this must be properly tested.
+ if (!chHandle) {
+ qCCritical(QT_BT_DARWIN) << "unexpected update notification, "
+ "no characteristic handle found";
+ return;
+ }
+
+ if (currentReadHandle == chHandle) {
+ // Even if it was not a reply to our read request (no way to test it)
+ // report it.
+ requestPending = false;
+ currentReadHandle = 0;
+ //
+ emit notifier->characteristicRead(chHandle, qt_bytearray(characteristic.value));
+ [self performNextRequest];
+ } else {
+ emit notifier->characteristicUpdated(chHandle, qt_bytearray(characteristic.value));
+ }
+ }
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral
+ didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic
+ error:(NSError *)error
+{
+ // This method does not change 'managerState', we can
+ // have several discoveries active at the same time.
+ Q_UNUSED(aPeripheral)
+
+ if (!notifier) {
+ // Detached, no need to continue ...
+ return;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ using namespace DarwinBluetooth;
+
+ if (![self objectIsUnderWatch:characteristic operation:OperationTimeout::descriptorsDiscovery])
+ return;
+
+ [self stopWatchingAfter:characteristic operation:OperationTimeout::descriptorsDiscovery];
+
+ if (error) {
+ NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
+ // We can continue though ...
+ }
+
+ // Do we have more characteristics on this service to discover descriptors?
+ CBCharacteristic *const next = [self nextCharacteristicForService:characteristic.service
+ startingFrom:characteristic];
+ if (next) {
+ [self watchAfter:next timeout:OperationTimeout::descriptorsDiscovery];
+ [peripheral discoverDescriptorsForCharacteristic:next];
+ } else {
+ [self readDescriptors:characteristic.service];
+ }
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral
+ didUpdateValueForDescriptor:(CBDescriptor *)descriptor
+ error:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+
+ Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
+
+ if (!notifier) {
+ // Detached ...
+ return;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ using namespace DarwinBluetooth;
+
+ if (![self objectIsUnderWatch:descriptor operation:OperationTimeout::descriptorRead])
+ return;
+
+ [self stopWatchingAfter:descriptor operation:OperationTimeout::descriptorRead];
+
+ CBService *const service = descriptor.characteristic.service;
+ const QBluetoothUuid qtUuid(qt_uuid(service.UUID));
+ const bool isDetailsDiscovery = servicesToDiscoverDetails.contains(qtUuid);
+ const QLowEnergyHandle dHandle = descMap.key(descriptor);
+
+ if (error) {
+ NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
+
+ if (!isDetailsDiscovery) {
+ if (dHandle && dHandle == currentReadHandle) {
+ currentReadHandle = 0;
+ requestPending = false;
+ emit notifier->CBManagerError(qtUuid, QLowEnergyService::DescriptorReadError);
+ [self handleReadWriteError:error];
+ [self performNextRequest];
+ }
+ return;
+ }
+ }
+
+ if (isDetailsDiscovery) {
+ // Test if we have any other characteristic to read yet.
+ CBDescriptor *const next = [self nextDescriptorForCharacteristic:descriptor.characteristic
+ startingFrom:descriptor];
+ if (next) {
+ [self watchAfter:next timeout:OperationTimeout::descriptorRead];
+ [peripheral readValueForDescriptor:next];
+ } else {
+ // We either have to read a value for a next descriptor
+ // on a given characteristic, or continue with the
+ // next characteristic in a given service (if any).
+ CBCharacteristic *const ch = descriptor.characteristic;
+ CBCharacteristic *nextCh = [self nextCharacteristicForService:ch.service
+ startingFrom:ch];
+ while (nextCh) {
+ if (nextCh.descriptors && nextCh.descriptors.count) {
+ CBDescriptor *desc = [nextCh.descriptors objectAtIndex:0];
+ [self watchAfter:desc timeout:OperationTimeout::descriptorRead];
+ return [peripheral readValueForDescriptor:desc];
+ }
+
+ nextCh = [self nextCharacteristicForService:ch.service
+ startingFrom:nextCh];
+ }
+
+ [self serviceDetailsDiscoveryFinished:service];
+ }
+ } else {
+ if (!dHandle) {
+ qCCritical(QT_BT_DARWIN) << "unexpected value update notification, "
+ "no descriptor handle found";
+ return;
+ }
+
+ if (dHandle == currentReadHandle) {
+ currentReadHandle = 0;
+ requestPending = false;
+ emit notifier->descriptorRead(dHandle, qt_bytearray(static_cast<NSObject *>(descriptor.value)));
+ [self performNextRequest];
+ }
+ }
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral
+ didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
+ error:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+ Q_UNUSED(characteristic)
+
+ if (!notifier) {
+ // Detached.
+ return;
+ }
+
+ // From docs:
+ //
+ // "This method is invoked only when your app calls the writeValue:forCharacteristic:type:
+ // method with the CBCharacteristicWriteWithResponse constant specified as the write type.
+ // If successful, the error parameter is nil. If unsuccessful,
+ // the error parameter returns the cause of the failure."
+
+ using namespace DarwinBluetooth;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if (![self objectIsUnderWatch:characteristic operation:OperationTimeout::characteristicWrite])
+ return;
+
+ [self stopWatchingAfter:characteristic operation:OperationTimeout::characteristicWrite];
+ requestPending = false;
+
+ // Error or not, but the cached value has to be deleted ...
+ const QByteArray valueToReport(valuesToWrite.value(characteristic, QByteArray()));
+ if (!valuesToWrite.remove(characteristic)) {
+ qCWarning(QT_BT_DARWIN) << "no updated value found "
+ "for characteristic";
+ }
+
+ if (error) {
+ NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
+ emit notifier->CBManagerError(qt_uuid(characteristic.service.UUID),
+ QLowEnergyService::CharacteristicWriteError);
+ [self handleReadWriteError:error];
+ } else {
+ const QLowEnergyHandle cHandle = charMap.key(characteristic);
+ emit notifier->characteristicWritten(cHandle, valueToReport);
+ }
+
+ [self performNextRequest];
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral
+ didWriteValueForDescriptor:(CBDescriptor *)descriptor
+ error:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+
+ if (!notifier) {
+ // Detached already.
+ return;
+ }
+
+ using namespace DarwinBluetooth;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ requestPending = false;
+
+ // Error or not, a value (if any) must be removed.
+ const QByteArray valueToReport(valuesToWrite.value(descriptor, QByteArray()));
+ if (!valuesToWrite.remove(descriptor))
+ qCWarning(QT_BT_DARWIN) << "no updated value found";
+
+ if (error) {
+ NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
+ emit notifier->CBManagerError(qt_uuid(descriptor.characteristic.service.UUID),
+ QLowEnergyService::DescriptorWriteError);
+ [self handleReadWriteError:error];
+ } else {
+ const QLowEnergyHandle dHandle = descMap.key(descriptor);
+ Q_ASSERT_X(dHandle, Q_FUNC_INFO, "descriptor not found in the descriptors map");
+ emit notifier->descriptorWritten(dHandle, valueToReport);
+ }
+
+ [self performNextRequest];
+}
+
+- (void)peripheral:(CBPeripheral *)aPeripheral
+ didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic
+ error:(NSError *)error
+{
+ Q_UNUSED(aPeripheral)
+
+ if (!notifier)
+ return;
+
+ using namespace DarwinBluetooth;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ requestPending = false;
+
+ const QBluetoothUuid qtUuid(QBluetoothUuid::ClientCharacteristicConfiguration);
+ CBDescriptor *const descriptor = [self descriptor:qtUuid forCharacteristic:characteristic];
+ const QByteArray valueToReport(valuesToWrite.value(descriptor, QByteArray()));
+ const int nRemoved = valuesToWrite.remove(descriptor);
+
+ if (error) {
+ NSLog(@"%s failed with error %@", Q_FUNC_INFO, error);
+ // In Qt's API it's a descriptor write actually.
+ emit notifier->CBManagerError(qt_uuid(characteristic.service.UUID),
+ QLowEnergyService::DescriptorWriteError);
+ } else if (nRemoved) {
+ const QLowEnergyHandle dHandle = descMap.key(descriptor);
+ emit notifier->descriptorWritten(dHandle, valueToReport);
+ }
+
+ [self performNextRequest];
+}
+
+- (void)detach
+{
+ if (notifier) {
+ notifier->disconnect();
+ notifier->deleteLater();
+ notifier = nullptr;
+ }
+
+ [self stopWatchers];
+ [self disconnectFromDevice];
+}
+
+@end
diff --git a/src/bluetooth/darwin/btcentralmanager_p.h b/src/bluetooth/darwin/btcentralmanager_p.h
new file mode 100644
index 00000000..0b5c8fbb
--- /dev/null
+++ b/src/bluetooth/darwin/btcentralmanager_p.h
@@ -0,0 +1,175 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTCENTRALMANAGER_P_H
+#define BTCENTRALMANAGER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qlowenergycontroller.h"
+#include "qlowenergyservice.h"
+#include "qbluetoothuuid.h"
+#include "btgcdtimer_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qqueue.h>
+#include <QtCore/qhash.h>
+
+#include <Foundation/Foundation.h>
+
+#include <CoreBluetooth/CoreBluetooth.h>
+
+QT_BEGIN_NAMESPACE
+
+class QLowEnergyServicePrivate;
+
+namespace DarwinBluetooth {
+
+class LECBManagerNotifier;
+
+enum CentralManagerState
+{
+ // QLowEnergyController already has some of these states,
+ // but it's not enough and we need more special states here.
+ CentralManagerIdle,
+ // Required by CBCentralManager to avoid problems with dangled pointers.
+ CentralManagerUpdating,
+ CentralManagerConnecting,
+ CentralManagerDiscovering,
+ CentralManagerDisconnecting
+};
+
+// In Qt we work with handles and UUIDs. Core Bluetooth
+// has NSArrays (and nested NSArrays inside servces/characteristics).
+// To simplify a navigation, I need a simple way to map from a handle
+// to a Core Bluetooth object. These are weak pointers,
+// will probably require '__weak' with ARC.
+typedef QHash<QLowEnergyHandle, CBService *> ServiceHash;
+typedef QHash<QLowEnergyHandle, CBCharacteristic *> CharHash;
+typedef QHash<QLowEnergyHandle, CBDescriptor *> DescHash;
+
+// Descriptor/charactesirstic read/write requests
+// - we have to serialize 'concurrent' requests.
+struct LERequest {
+ enum RequestType {
+ CharRead,
+ CharWrite,
+ DescRead,
+ DescWrite,
+ ClientConfiguration,
+ Invalid
+ };
+
+ LERequest() : type(Invalid),
+ withResponse(false),
+ handle(0)
+ {}
+
+ RequestType type;
+ bool withResponse;
+ QLowEnergyHandle handle;
+ QByteArray value;
+};
+
+typedef QQueue<LERequest> RequestQueue;
+
+// Core Bluetooth's write confirmation does not provide
+// the updated value (characteristic or descriptor).
+// But the Qt's Bluetooth API ('write with response')
+// expects this updated value as a response, so we have
+// to cache this write value and report it back.
+// 'NSObject *' will require '__weak' with ARC.
+typedef QHash<NSObject *, QByteArray> ValueHash;
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTCentralManager) : NSObject<CBCentralManagerDelegate,
+ CBPeripheralDelegate,
+ QT_MANGLE_NAMESPACE(GCDTimerDelegate)>
+- (id)initWith:(QT_PREPEND_NAMESPACE(DarwinBluetooth)::LECBManagerNotifier *)notifier;
+- (void)dealloc;
+
+- (CBPeripheral *)peripheral;
+
+// IMPORTANT: _all_ these methods are to be executed on qt_LE_queue,
+// when passing parameters - C++ objects _must_ be copied (see the controller's code).
+- (void)connectToDevice:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)aDeviceUuid;
+
+- (void)disconnectFromDevice;
+
+- (void)discoverServices;
+- (void)discoverServiceDetails:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
+
+- (void)setNotifyValue:(const QT_PREPEND_NAMESPACE(QByteArray) &)value
+ forCharacteristic:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
+ onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
+
+- (void)readCharacteristic:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
+ onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
+
+- (void)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value
+ charHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
+ onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid
+ withResponse:(bool)writeWithResponse;
+
+- (void)readDescriptor:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))descHandle
+ onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
+
+- (void)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value
+ descHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))descHandle
+ onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
+
+- (void)detach;
+
+@end
+
+#endif
diff --git a/src/bluetooth/darwin/btconnectionmonitor.mm b/src/bluetooth/darwin/btconnectionmonitor.mm
new file mode 100644
index 00000000..f9adfde8
--- /dev/null
+++ b/src/bluetooth/darwin/btconnectionmonitor.mm
@@ -0,0 +1,133 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "btconnectionmonitor_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+
+QT_END_NAMESPACE
+
+#ifdef QT_NAMESPACE
+using namespace QT_NAMESPACE;
+#endif
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTConnectionMonitor)
+{
+ QT_PREPEND_NAMESPACE(DarwinBluetooth::ConnectionMonitor) *monitor;
+ IOBluetoothUserNotification *discoveryNotification;
+ NSMutableArray *foundConnections;
+}
+
+- (id)initWithMonitor:(DarwinBluetooth::ConnectionMonitor *)aMonitor
+{
+ Q_ASSERT_X(aMonitor, "-initWithMonitor:", "invalid monitor (null)");
+
+ if (self = [super init]) {
+ monitor = aMonitor;
+ discoveryNotification = [[IOBluetoothDevice registerForConnectNotifications:self
+ selector:@selector(connectionNotification:withDevice:)] retain];
+ foundConnections = [[NSMutableArray alloc] init];
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [discoveryNotification unregister];
+ [discoveryNotification release];
+
+ for (IOBluetoothUserNotification *n in foundConnections)
+ [n unregister];
+
+ [foundConnections release];
+
+ [super dealloc];
+}
+
+- (void)connectionNotification:(IOBluetoothUserNotification *)aNotification
+ withDevice:(IOBluetoothDevice *)device
+{
+ Q_UNUSED(aNotification)
+
+ typedef IOBluetoothUserNotification Notification;
+
+ if (!device)
+ return;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ // All Obj-C objects are autoreleased.
+
+ const QBluetoothAddress deviceAddress(DarwinBluetooth::qt_address([device getAddress]));
+ if (deviceAddress.isNull())
+ return;
+
+ if (foundConnections) {
+ Notification *const notification = [device registerForDisconnectNotification:self
+ selector: @selector(connectionClosedNotification:withDevice:)];
+ if (notification)
+ [foundConnections addObject:notification];
+ }
+
+ Q_ASSERT_X(monitor, "-connectionNotification:withDevice:", "invalid monitor (null)");
+ monitor->deviceConnected(deviceAddress);
+}
+
+- (void)connectionClosedNotification:(IOBluetoothUserNotification *)notification
+ withDevice:(IOBluetoothDevice *)device
+{
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ [notification unregister];//?
+ [foundConnections removeObject:notification];
+
+ const QBluetoothAddress deviceAddress(DarwinBluetooth::qt_address([device getAddress]));
+ if (deviceAddress.isNull())
+ return;
+
+ Q_ASSERT_X(monitor, "-connectionClosedNotification:withDevice:", "invalid monitor (null)");
+ monitor->deviceDisconnected(deviceAddress);
+}
+
+@end
diff --git a/src/bluetooth/darwin/btconnectionmonitor_p.h b/src/bluetooth/darwin/btconnectionmonitor_p.h
new file mode 100644
index 00000000..e3b7b90d
--- /dev/null
+++ b/src/bluetooth/darwin/btconnectionmonitor_p.h
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTCONNECTIONMONITOR_P_H
+#define BTCONNECTIONMONITOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qbluetoothaddress.h"
+#include "btdelegates_p.h"
+
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+
+#include <IOBluetooth/IOBluetooth.h>
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTConnectionMonitor) : NSObject
+
+- (id)initWithMonitor:(QT_PREPEND_NAMESPACE(DarwinBluetooth::ConnectionMonitor) *)monitor;
+- (void)connectionNotification:(id)notification withDevice:(IOBluetoothDevice *)device;
+- (void)connectionClosedNotification:(id)notification withDevice:(IOBluetoothDevice *)device;
+
+@end
+
+#endif
diff --git a/src/bluetooth/darwin/btdelegates.cpp b/src/bluetooth/darwin/btdelegates.cpp
new file mode 100644
index 00000000..531ca1df
--- /dev/null
+++ b/src/bluetooth/darwin/btdelegates.cpp
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "btdelegates_p.h"
+
+#if defined(Q_OS_MACOS)
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+DeviceInquiryDelegate::~DeviceInquiryDelegate()
+{
+}
+
+PairingDelegate::~PairingDelegate()
+{
+}
+
+SDPInquiryDelegate::~SDPInquiryDelegate()
+{
+}
+
+ChannelDelegate::~ChannelDelegate()
+{
+}
+
+ConnectionMonitor::~ConnectionMonitor()
+{
+}
+
+SocketListener::~SocketListener()
+{
+}
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/bluetooth/darwin/btdelegates_p.h b/src/bluetooth/darwin/btdelegates_p.h
new file mode 100644
index 00000000..11fbcc28
--- /dev/null
+++ b/src/bluetooth/darwin/btdelegates_p.h
@@ -0,0 +1,149 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTDELEGATES_P_H
+#define BTDELEGATES_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qbluetoothdevicediscoveryagent.h"
+#include "qlowenergycontroller.h"
+#include "qbluetooth.h"
+
+#include <QtCore/qsharedpointer.h>
+#include <QtCore/qglobal.h>
+
+#if defined(Q_OS_MACOS)
+
+#include <IOKit/IOReturn.h>
+
+#include <cstdint>
+
+QT_BEGIN_NAMESPACE
+
+class QLowEnergyServicePrivate;
+class QBluetoothAddress;
+class QByteArray;
+
+namespace DarwinBluetooth {
+
+class DeviceInquiryDelegate
+{
+public:
+ virtual ~DeviceInquiryDelegate();
+
+ virtual void inquiryFinished() = 0;
+ virtual void error(IOReturn error) = 0;
+ virtual void classicDeviceFound(void *ioBluetoothDevice) = 0;
+};
+
+class PairingDelegate
+{
+public:
+ using BluetoothNumericValue = uint32_t;
+ using BluetoothPasskey = BluetoothNumericValue;
+
+ virtual ~PairingDelegate();
+
+ virtual void connecting(void *pair) = 0;
+ virtual void requestPIN(void *pair) = 0;
+ virtual void requestUserConfirmation(void *pair,
+ BluetoothNumericValue) = 0;
+ virtual void passkeyNotification(void *pair,
+ BluetoothPasskey passkey) = 0;
+ virtual void error(void *pair, IOReturn errorCode) = 0;
+ virtual void pairingFinished(void *pair) = 0;
+};
+
+class SDPInquiryDelegate {
+public:
+ virtual ~SDPInquiryDelegate();
+
+ virtual void SDPInquiryFinished(void *ioBluetoothDevice) = 0;
+ virtual void SDPInquiryError(void *ioBluetoothDevice, IOReturn errorCode) = 0;
+};
+
+// L2CAP and RFCOMM.
+class ChannelDelegate
+{
+public:
+ virtual ~ChannelDelegate();
+
+ virtual void setChannelError(IOReturn errorCode) = 0;
+ virtual void channelOpenComplete() = 0;
+ virtual void channelClosed() = 0;
+
+ virtual void readChannelData(void *data, std::size_t size) = 0;
+ virtual void writeComplete() = 0;
+};
+
+class ConnectionMonitor {
+public:
+ virtual ~ConnectionMonitor();
+
+ virtual void deviceConnected(const QBluetoothAddress &address) = 0;
+ virtual void deviceDisconnected(const QBluetoothAddress &address) = 0;
+};
+
+class SocketListener
+{
+public:
+ virtual ~SocketListener();
+
+ virtual void openNotifyRFCOMM(void *rfcommChannel) = 0;
+ virtual void openNotifyL2CAP(void *l2capChannel) = 0;
+};
+
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+#endif // Q_OS_MACOS
+
+#endif // DARWINBTDELEGATES_P_H
diff --git a/src/bluetooth/darwin/btdeviceinquiry.mm b/src/bluetooth/darwin/btdeviceinquiry.mm
new file mode 100644
index 00000000..ad59a4a4
--- /dev/null
+++ b/src/bluetooth/darwin/btdeviceinquiry.mm
@@ -0,0 +1,168 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "btdeviceinquiry_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qdebug.h>
+
+QT_USE_NAMESPACE
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTClassicDeviceInquiry)
+{
+ IOBluetoothDeviceInquiry *m_inquiry;
+ bool m_active;
+ DarwinBluetooth::DeviceInquiryDelegate *m_delegate;//C++ "delegate"
+}
+
+- (id)initWithDelegate:(DarwinBluetooth::DeviceInquiryDelegate *)delegate
+{
+ if (self = [super init]) {
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid device inquiry delegate (null)");
+
+ m_inquiry = [[IOBluetoothDeviceInquiry inquiryWithDelegate:self] retain];
+
+ if (m_inquiry) {
+ [m_inquiry setInquiryLength:15];
+ [m_inquiry setUpdateNewDeviceNames:NO];//Useless, disable!
+ m_delegate = delegate;
+ } else {
+ qCCritical(QT_BT_DARWIN) << "failed to create a device inquiry";
+ }
+
+ m_active = false;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ // Noop if m_inquiry is nil.
+ [m_inquiry setDelegate:nil];
+ if (m_active)
+ [m_inquiry stop];
+ [m_inquiry release];
+
+ [super dealloc];
+}
+
+- (bool)isActive
+{
+ return m_active;
+}
+
+- (IOReturn)start
+{
+ if (!m_inquiry)
+ return kIOReturnNoPower;
+
+ if (m_active)
+ return kIOReturnBusy;
+
+ m_active = true;
+ [m_inquiry clearFoundDevices];
+ const IOReturn result = [m_inquiry start];
+ if (result != kIOReturnSuccess) {
+ // QtBluetooth will probably convert an error into UnknownError,
+ // loosing the actual information.
+ qCWarning(QT_BT_DARWIN) << "failed with IOKit error code:" << result;
+ m_active = false;
+ }
+
+ return result;
+}
+
+- (IOReturn)stop
+{
+ if (m_active) {
+ Q_ASSERT_X(m_inquiry, Q_FUNC_INFO, "active but nil inquiry");
+
+ m_active = false;
+ const IOReturn res = [m_inquiry stop];
+ if (res != kIOReturnSuccess)
+ m_active = true;
+ else
+ qCDebug(QT_BT_DARWIN) << "-stop, success (waiting for 'inquiryComplete')";
+
+ return res;
+ }
+
+ return kIOReturnSuccess;
+}
+
+- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry *)sender
+ error:(IOReturn)error aborted:(BOOL)aborted
+{
+ Q_UNUSED(aborted)
+
+ if (sender != m_inquiry) // Can never happen in the current version.
+ return;
+
+ m_active = false;
+
+ Q_ASSERT_X(m_delegate, Q_FUNC_INFO, "invalid device inquiry delegate (null)");
+
+ if (error != kIOReturnSuccess) {
+ // QtBluetooth has not too many error codes, 'UnknownError' is not really
+ // useful, report the actual error code here:
+ qCWarning(QT_BT_DARWIN) << "IOKit error code: " << error;
+ m_delegate->error(error);
+ } else {
+ m_delegate->inquiryFinished();
+ }
+}
+
+- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry *)sender
+ device:(IOBluetoothDevice *)device
+{
+ if (sender != m_inquiry) // Can never happen in the current version.
+ return;
+
+ Q_ASSERT_X(m_delegate, Q_FUNC_INFO, "invalid device inquiry delegate (null)");
+ m_delegate->classicDeviceFound(device);
+}
+
+- (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry *)sender
+{
+ Q_UNUSED(sender)
+}
+
+@end
diff --git a/src/bluetooth/darwin/btdeviceinquiry_p.h b/src/bluetooth/darwin/btdeviceinquiry_p.h
new file mode 100644
index 00000000..fa4fd01c
--- /dev/null
+++ b/src/bluetooth/darwin/btdeviceinquiry_p.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTDEVICEINQUIRY_P_H
+#define BTDEVICEINQUIRY_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "btdelegates_p.h"
+
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+#include <IOKit/IOReturn.h>
+
+#include <IOBluetooth/IOBluetooth.h>
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTClassicDeviceInquiry) : NSObject<IOBluetoothDeviceInquiryDelegate>
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth::DeviceInquiryDelegate) *)delegate;
+- (void)dealloc;
+
+- (bool)isActive;
+- (IOReturn)start;
+- (IOReturn)stop;
+
+//Obj-C delegate:
+- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry *)sender
+ error:(IOReturn)error aborted:(BOOL)aborted;
+
+- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry *)sender
+ device:(IOBluetoothDevice *)device;
+
+- (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry *)sender;
+
+@end
+
+#endif
diff --git a/src/bluetooth/darwin/btdevicepair.mm b/src/bluetooth/darwin/btdevicepair.mm
new file mode 100644
index 00000000..2c212c2d
--- /dev/null
+++ b/src/bluetooth/darwin/btdevicepair.mm
@@ -0,0 +1,215 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "btdevicepair_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+ObjCStrongReference<IOBluetoothDevice> device_with_address(const QBluetoothAddress &address)
+{
+ if (address.isNull())
+ return ObjCStrongReference<IOBluetoothDevice>(nil, false);
+
+ const BluetoothDeviceAddress &iobtAddress = iobluetooth_address(address);
+ ObjCStrongReference<IOBluetoothDevice> res([[IOBluetoothDevice deviceWithAddress:&iobtAddress] retain], false);
+ return res;
+}
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTClassicPairing)
+{
+ QT_PREPEND_NAMESPACE(QBluetoothAddress) m_targetAddress;
+
+ bool m_active;
+ IOBluetoothDevicePair *m_pairing; // The real pairing request
+ QT_PREPEND_NAMESPACE(DarwinBluetooth)::PairingDelegate *m_object;
+}
+
+- (id)initWithTarget:(const QBluetoothAddress &)address
+ delegate:(DarwinBluetooth::PairingDelegate *)object
+{
+ if (self = [super init]) {
+ Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid target address");
+ Q_ASSERT_X(object, Q_FUNC_INFO, "invalid delegate (null)");
+
+ m_targetAddress = address;
+ m_object = object;
+ m_active = false;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [m_pairing stop];
+ [m_pairing release];
+ [super dealloc];
+}
+
+- (IOReturn) start
+{
+ if (m_active)
+ return kIOReturnBusy;
+
+ Q_ASSERT_X(!m_targetAddress.isNull(), Q_FUNC_INFO, "invalid target address");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ const BluetoothDeviceAddress &iobtAddress = DarwinBluetooth::iobluetooth_address(m_targetAddress);
+ // Device is autoreleased.
+ IOBluetoothDevice *const device = [IOBluetoothDevice deviceWithAddress:&iobtAddress];
+ if (!device) {
+ qCCritical(QT_BT_DARWIN) << "failed to create a device to pair with";
+ return kIOReturnError;
+ }
+
+ m_pairing = [[IOBluetoothDevicePair pairWithDevice:device] retain];
+ if (!m_pairing) {
+ qCCritical(QT_BT_DARWIN) << "failed to create a device pair";
+ return kIOReturnError;
+ }
+
+ [m_pairing setDelegate:self];
+ const IOReturn result = [m_pairing start];
+ if (result != kIOReturnSuccess) {
+ [m_pairing release];
+ m_pairing = nil;
+ } else
+ m_active = true;
+
+ return result;
+}
+
+- (bool)isActive
+{
+ return m_active;
+}
+
+- (void)stop
+{
+ // stop: stops pairing, removes the delegate
+ // and disconnects if device was connected.
+ if (m_pairing)
+ [m_pairing stop];
+}
+
+- (const QBluetoothAddress &)targetAddress
+{
+ return m_targetAddress;
+}
+
+- (IOBluetoothDevicePair *)pairingRequest
+{
+ return [[m_pairing retain] autorelease];
+}
+
+- (IOBluetoothDevice *)targetDevice
+{
+ return [m_pairing device];//It's retained/autoreleased by pair.
+}
+
+// IOBluetoothDevicePairDelegate:
+
+- (void)devicePairingStarted:(id)sender
+{
+ Q_UNUSED(sender)
+}
+
+- (void)devicePairingConnecting:(id)sender
+{
+ Q_UNUSED(sender)
+}
+
+- (void)deviceParingPINCodeRequest:(id)sender
+{
+ Q_UNUSED(sender)
+}
+
+- (void)devicePairingUserConfirmationRequest:(id)sender
+ numericValue:(BluetoothNumericValue)numericValue
+{
+ if (sender != m_pairing) // Can never happen.
+ return;
+
+ Q_ASSERT_X(m_object, Q_FUNC_INFO, "invalid delegate (null)");
+
+ m_object->requestUserConfirmation(self, numericValue);
+}
+
+- (void)devicePairingUserPasskeyNotification:(id)sender
+ passkey:(BluetoothPasskey)passkey
+{
+ Q_UNUSED(sender)
+ Q_UNUSED(passkey)
+}
+
+- (void)devicePairingFinished:(id)sender error:(IOReturn)error
+{
+ Q_ASSERT_X(m_object, Q_FUNC_INFO, "invalid delegate (null)");
+
+ if (sender != m_pairing) // Can never happen though.
+ return;
+
+ m_active = false;
+ if (error != kIOReturnSuccess)
+ m_object->error(self, error);
+ else
+ m_object->pairingFinished(self);
+}
+
+- (void)deviceSimplePairingComplete:(id)sender
+ status:(BluetoothHCIEventStatus)status
+{
+ Q_UNUSED(sender)
+ Q_UNUSED(status)
+}
+
+@end
diff --git a/src/bluetooth/darwin/btdevicepair_p.h b/src/bluetooth/darwin/btdevicepair_p.h
new file mode 100644
index 00000000..1b361258
--- /dev/null
+++ b/src/bluetooth/darwin/btdevicepair_p.h
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTDEVICEPAIR_P_H
+#define BTDEVICEPAIR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qbluetoothaddress.h"
+#include "btdelegates_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+
+#include <IOBluetooth/IOBluetooth.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+ObjCStrongReference<IOBluetoothDevice> device_with_address(const QBluetoothAddress &address);
+
+} // Namespace DarwinBluetooth.
+
+QT_END_NAMESPACE
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTClassicPairing) : NSObject<IOBluetoothDevicePairDelegate>
+
+- (id)initWithTarget:(const QBluetoothAddress &)address
+ delegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth::PairingDelegate) *)object;
+
+- (void)dealloc;
+
+- (IOReturn)start;
+- (bool)isActive;
+- (void)stop;
+
+- (const QBluetoothAddress &)targetAddress;
+- (IOBluetoothDevicePair *)pairingRequest;
+- (IOBluetoothDevice *)targetDevice;
+
+// IOBluetoothDevicePairDelegate:
+
+- (void)devicePairingStarted:(id)sender;
+- (void)devicePairingConnecting:(id)sender;
+- (void)deviceParingPINCodeRequest:(id)sender;
+
+- (void)devicePairingUserConfirmationRequest:(id)sender
+ numericValue:(BluetoothNumericValue)numericValue;
+
+- (void)devicePairingUserPasskeyNotification:(id)sender
+ passkey:(BluetoothPasskey)passkey;
+
+- (void)devicePairingFinished:(id)sender
+ error:(IOReturn)error;
+
+- (void)deviceSimplePairingComplete:(id)sender
+ status:(BluetoothHCIEventStatus)status;
+
+@end
+
+
+#endif
diff --git a/src/bluetooth/darwin/btgcdtimer.mm b/src/bluetooth/darwin/btgcdtimer.mm
new file mode 100644
index 00000000..9105a8fb
--- /dev/null
+++ b/src/bluetooth/darwin/btgcdtimer.mm
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "btgcdtimer_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qdebug.h>
+
+#include <algorithm>
+
+QT_USE_NAMESPACE
+using namespace DarwinBluetooth;
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTGCDTimer) {
+@private
+ qint64 timeoutMS;
+ qint64 timeoutStepMS;
+
+ // Optional:
+ id objectUnderWatch;
+ OperationTimeout timeoutType;
+
+ QElapsedTimer timer;
+ id<QT_MANGLE_NAMESPACE(GCDTimerDelegate)> timeoutHandler;
+
+ bool cancelled;
+}
+
+- (instancetype)initWithDelegate:(id<QT_MANGLE_NAMESPACE(GCDTimerDelegate)>)delegate
+{
+ if (self = [super init]) {
+ timeoutHandler = delegate;
+ timeoutMS = 0;
+ timeoutStepMS = 0;
+ objectUnderWatch = nil;
+ timeoutType = OperationTimeout::none;
+ cancelled = false;
+ }
+ return self;
+}
+
+- (void)watchAfter:(id)object withTimeoutType:(OperationTimeout)type
+{
+ objectUnderWatch = object;
+ timeoutType = type;
+}
+
+- (void)startWithTimeout:(qint64)ms step:(qint64)stepMS
+{
+ Q_ASSERT(!timeoutMS && !timeoutStepMS);
+ Q_ASSERT(!cancelled);
+
+ if (!timeoutHandler) {
+ // Nobody to report timeout to, no need to start any task then.
+ return;
+ }
+
+ if (ms <= 0 || stepMS <= 0) {
+ qCWarning(QT_BT_DARWIN, "Invalid timeout/step parameters");
+ return;
+ }
+
+ timeoutMS = ms;
+ timeoutStepMS = stepMS;
+ timer.start();
+
+ [self handleTimeout];
+}
+
+- (void)handleTimeout
+{
+ if (cancelled)
+ return;
+
+ const qint64 elapsed = timer.elapsed();
+ if (elapsed >= timeoutMS) {
+ [timeoutHandler timeout:self];
+ } else {
+ // Re-schedule:
+ dispatch_queue_t leQueue(qt_LE_queue());
+ Q_ASSERT(leQueue);
+ const qint64 timeChunkMS = std::min(timeoutMS - elapsed, timeoutStepMS);
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
+ int64_t(timeChunkMS / 1000. * NSEC_PER_SEC)),
+ leQueue,
+ ^{
+ [self handleTimeout];
+ });
+ }
+}
+
+- (void)cancelTimer
+{
+ cancelled = true;
+ timeoutHandler = nil;
+ objectUnderWatch = nil;
+ timeoutType = OperationTimeout::none;
+}
+
+- (id)objectUnderWatch
+{
+ return objectUnderWatch;
+}
+
+- (OperationTimeout)timeoutType
+{
+ return timeoutType;
+}
+
+@end
diff --git a/src/bluetooth/darwin/btgcdtimer_p.h b/src/bluetooth/darwin/btgcdtimer_p.h
new file mode 100644
index 00000000..dd67d6e8
--- /dev/null
+++ b/src/bluetooth/darwin/btgcdtimer_p.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTGCDTIMER_P_H
+#define BTGCDTIMER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "btutility_p.h"
+
+#include <QtCore/qelapsedtimer.h>
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+enum class OperationTimeout
+{
+ none,
+ serviceDiscovery,
+ includedServicesDiscovery,
+ characteristicsDiscovery,
+ characteristicRead,
+ descriptorsDiscovery,
+ descriptorRead,
+ characteristicWrite
+};
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+@protocol QT_MANGLE_NAMESPACE(GCDTimerDelegate)
+@required
+- (void)timeout:(id)sender;
+@end
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTGCDTimer) : NSObject
+- (instancetype)initWithDelegate:(id<QT_MANGLE_NAMESPACE(GCDTimerDelegate)>)delegate;
+- (void)watchAfter:(id)object withTimeoutType:(QT_PREPEND_NAMESPACE(DarwinBluetooth)::OperationTimeout)type;
+- (void)startWithTimeout:(qint64)ms step:(qint64)stepMS;
+- (void)handleTimeout;
+- (void)cancelTimer;
+- (id)objectUnderWatch;
+- (QT_PREPEND_NAMESPACE(DarwinBluetooth)::OperationTimeout)timeoutType;
+@end
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+using GCDTimerObjC = QT_MANGLE_NAMESPACE(DarwinBTGCDTimer);
+using GCDTimer = ObjCStrongReference<GCDTimerObjC>;
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+#endif // BTGCDTIMER_P_H
+
diff --git a/src/bluetooth/darwin/btl2capchannel.mm b/src/bluetooth/darwin/btl2capchannel.mm
new file mode 100644
index 00000000..e440a0ee
--- /dev/null
+++ b/src/bluetooth/darwin/btl2capchannel.mm
@@ -0,0 +1,258 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "btl2capchannel_p.h"
+#include "qbluetoothaddress.h"
+#include "btdelegates_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qdebug.h>
+
+QT_USE_NAMESPACE
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTL2CAPChannel)
+{
+ QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *delegate;
+ IOBluetoothDevice *device;
+ IOBluetoothL2CAPChannel *channel;
+ bool connected;
+}
+
+- (id)initWithDelegate:(DarwinBluetooth::ChannelDelegate *)aDelegate
+{
+ Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)");
+
+ if (self = [super init]) {
+ delegate = aDelegate;
+ device = nil;
+ channel = nil;
+ connected = false;
+ }
+
+ return self;
+}
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth::ChannelDelegate) *)aDelegate
+ channel:(IOBluetoothL2CAPChannel *)aChannel
+{
+ // This type of channel does not require connect, it's created with
+ // already open channel.
+ Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)");
+ Q_ASSERT_X(channel, Q_FUNC_INFO, "invalid channel (nil)");
+
+ if (self = [super init]) {
+ delegate = aDelegate;
+ channel = [aChannel retain];
+ [channel setDelegate:self];
+ device = [channel.device retain];
+ connected = true;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ // TODO: test if this implementation works at all!
+ if (channel) {
+ [channel setDelegate:nil];
+ // From Apple's docs:
+ // "This method may only be called by the client that opened the channel
+ // in the first place. In the future asynchronous and synchronous versions
+ // will be provided that let the client know when the close process has been finished."
+ [channel closeChannel];
+ [channel release];
+ }
+
+ [device release];
+
+ [super dealloc];
+}
+
+- (IOReturn)connectAsyncToDevice:(const QBluetoothAddress &)address
+ withPSM:(BluetoothL2CAPChannelID)psm
+{
+ if (address.isNull()) {
+ qCCritical(QT_BT_DARWIN) << "invalid peer address";
+ return kIOReturnNoDevice;
+ }
+
+ // Can never be called twice.
+ if (connected || device || channel) {
+ qCCritical(QT_BT_DARWIN) << "connection is already active";
+ return kIOReturnStillOpen;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ const BluetoothDeviceAddress iobtAddress = DarwinBluetooth::iobluetooth_address(address);
+ device = [IOBluetoothDevice deviceWithAddress:&iobtAddress];
+ if (!device) {
+ qCCritical(QT_BT_DARWIN) << "failed to create a device";
+ return kIOReturnNoDevice;
+ }
+
+ const IOReturn status = [device openL2CAPChannelAsync:&channel withPSM:psm delegate:self];
+ if (status != kIOReturnSuccess) {
+ qCCritical(QT_BT_DARWIN) << "failed to open L2CAP channel";
+ // device is still autoreleased.
+ device = nil;
+ return status;
+ }
+
+ [channel retain];// What if we're closed already?
+ [device retain];
+
+ return kIOReturnSuccess;
+}
+
+// IOBluetoothL2CAPChannelDelegate:
+
+- (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel
+ data:(void *)dataPointer length:(size_t)dataLength
+{
+ Q_UNUSED(l2capChannel)
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+
+ if (dataPointer && dataLength)
+ delegate->readChannelData(dataPointer, dataLength);
+}
+
+- (void)l2capChannelOpenComplete:(IOBluetoothL2CAPChannel*)
+ l2capChannel status:(IOReturn)error
+{
+ Q_UNUSED(l2capChannel)
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+
+ if (error != kIOReturnSuccess) {
+ delegate->setChannelError(error);
+ } else {
+ connected = true;
+ delegate->channelOpenComplete();
+ }
+}
+
+- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel
+{
+ Q_UNUSED(l2capChannel)
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+ delegate->channelClosed();
+ connected = false;
+}
+
+- (void)l2capChannelReconfigured:(IOBluetoothL2CAPChannel*)l2capChannel
+{
+ Q_UNUSED(l2capChannel)
+}
+
+- (void)l2capChannelWriteComplete:(IOBluetoothL2CAPChannel*)l2capChannel
+ refcon:(void*)refcon status:(IOReturn)error
+{
+ Q_UNUSED(l2capChannel)
+ Q_UNUSED(refcon)
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+
+ if (error != kIOReturnSuccess)
+ delegate->setChannelError(error);
+ else
+ delegate->writeComplete();
+}
+
+- (void)l2capChannelQueueSpaceAvailable:(IOBluetoothL2CAPChannel*)l2capChannel
+{
+ Q_UNUSED(l2capChannel)
+}
+
+// Aux. methods.
+- (BluetoothL2CAPPSM)getPSM
+{
+ if (channel)
+ return channel.PSM;
+
+ return 0;
+}
+
+- (BluetoothDeviceAddress)peerAddress
+{
+ const BluetoothDeviceAddress *const addr = device ? [device getAddress]
+ : nullptr;
+ if (addr)
+ return *addr;
+
+ return BluetoothDeviceAddress();
+}
+
+- (NSString *)peerName
+{
+ if (device)
+ return device.name;
+
+ return nil;
+}
+
+- (bool)isConnected
+{
+ return connected;
+}
+
+- (IOReturn) writeSync:(void*)data length:(UInt16)length
+{
+ Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)");
+ Q_ASSERT_X(length, Q_FUNC_INFO, "invalid data size");
+ Q_ASSERT_X(connected && channel, Q_FUNC_INFO, "invalid L2CAP channel");
+
+ return [channel writeSync:data length:length];
+}
+
+- (IOReturn) writeAsync:(void*)data length:(UInt16)length
+{
+ Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)");
+ Q_ASSERT_X(length, Q_FUNC_INFO, "invalid data size");
+ Q_ASSERT_X(connected && channel, Q_FUNC_INFO, "invalid L2CAP channel");
+
+ return [channel writeAsync:data length:length refcon:nullptr];
+}
+
+
+@end
diff --git a/src/bluetooth/darwin/btl2capchannel_p.h b/src/bluetooth/darwin/btl2capchannel_p.h
new file mode 100644
index 00000000..32122fe8
--- /dev/null
+++ b/src/bluetooth/darwin/btl2capchannel_p.h
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTL2CAPCHANNEL_P_H
+#define BTL2CAPCHANNEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+
+#include <IOBluetooth/IOBluetooth.h>
+
+#include <cstddef>
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothAddress;
+
+namespace DarwinBluetooth {
+
+class ChannelDelegate;
+
+}
+
+QT_END_NAMESPACE
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTL2CAPChannel) : NSObject<IOBluetoothL2CAPChannelDelegate>
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *)aDelegate;
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *)aDelegate
+ channel:(IOBluetoothL2CAPChannel *)aChannel;
+
+- (void)dealloc;
+
+// Async. connection (connect can be called only once).
+- (IOReturn)connectAsyncToDevice:(const QT_PREPEND_NAMESPACE(QBluetoothAddress) &)address
+ withPSM:(BluetoothL2CAPChannelID)psm;
+
+// IOBluetoothL2CAPChannelDelegate:
+- (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel
+ data:(void *)dataPointer length:(size_t)dataLength;
+- (void)l2capChannelOpenComplete:(IOBluetoothL2CAPChannel*)
+ l2capChannel status:(IOReturn)error;
+- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel;
+- (void)l2capChannelReconfigured:(IOBluetoothL2CAPChannel*)l2capChannel;
+- (void)l2capChannelWriteComplete:(IOBluetoothL2CAPChannel*)l2capChannel
+ refcon:(void*)refcon status:(IOReturn)error;
+- (void)l2capChannelQueueSpaceAvailable:(IOBluetoothL2CAPChannel*)l2capChannel;
+
+//
+- (BluetoothL2CAPPSM)getPSM;
+- (BluetoothDeviceAddress)peerAddress;
+- (NSString *)peerName;
+- (bool)isConnected;
+
+// Writes the given data synchronously over the target L2CAP channel to the remote
+// device.
+// The length of the data may not exceed the L2CAP channel's outgoing MTU.
+// This method will block until the data has been successfully sent to the
+// hardware for transmission (or an error occurs).
+- (IOReturn) writeSync:(void*)data length:(UInt16)length;
+
+// The length of the data may not exceed the L2CAP channel's outgoing MTU.
+// When the data has been successfully passed to the hardware to be transmitted,
+// the delegate method -l2capChannelWriteComplete:refcon:status: will be called.
+// Returns kIOReturnSuccess if the data was buffered successfully.
+- (IOReturn) writeAsync:(void*)data length:(UInt16)length;
+
+@end
+
+#endif
diff --git a/src/bluetooth/darwin/btledeviceinquiry.mm b/src/bluetooth/darwin/btledeviceinquiry.mm
new file mode 100644
index 00000000..b3f29035
--- /dev/null
+++ b/src/bluetooth/darwin/btledeviceinquiry.mm
@@ -0,0 +1,378 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbluetoothdeviceinfo.h"
+#include "btledeviceinquiry_p.h"
+#include "qbluetoothuuid.h"
+#include "btnotifier_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qendian.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+QBluetoothUuid qt_uuid(NSUUID *nsUuid)
+{
+ if (!nsUuid)
+ return QBluetoothUuid();
+
+ uuid_t uuidData = {};
+ [nsUuid getUUIDBytes:uuidData];
+ quint128 qtUuidData = {};
+ std::copy(uuidData, uuidData + 16, qtUuidData.data);
+ return QBluetoothUuid(qtUuidData);
+}
+
+const int timeStepMS = 100;
+const int powerOffTimeoutMS = 30000;
+
+struct AdvertisementData {
+ // That's what CoreBluetooth has:
+ // CBAdvertisementDataLocalNameKey
+ // CBAdvertisementDataTxPowerLevelKey
+ // CBAdvertisementDataServiceUUIDsKey
+ // CBAdvertisementDataServiceDataKey
+ // CBAdvertisementDataManufacturerDataKey
+ // CBAdvertisementDataOverflowServiceUUIDsKey
+ // CBAdvertisementDataIsConnectable
+ // CBAdvertisementDataSolicitedServiceUUIDsKey
+
+ // For now, we "parse":
+ QString localName;
+ QVector<QBluetoothUuid> serviceUuids;
+ QHash<quint16, QByteArray> manufacturerData;
+ // TODO: other keys probably?
+ AdvertisementData(NSDictionary *AdvertisementData);
+};
+
+AdvertisementData::AdvertisementData(NSDictionary *advertisementData)
+{
+ if (!advertisementData)
+ return;
+
+ // ... constant CBAdvertisementDataLocalNameKey ...
+ // NSString containing the local name of a peripheral.
+ NSObject *value = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
+ if (value && [value isKindOfClass:[NSString class]])
+ localName = QString::fromNSString(static_cast<NSString *>(value));
+
+ // ... constant CBAdvertisementDataServiceUUIDsKey ...
+ // A list of one or more CBUUID objects, representing CBService UUIDs.
+
+ value = [advertisementData objectForKey:CBAdvertisementDataServiceUUIDsKey];
+ if (value && [value isKindOfClass:[NSArray class]]) {
+ NSArray *uuids = static_cast<NSArray *>(value);
+ for (CBUUID *cbUuid in uuids)
+ serviceUuids << qt_uuid(cbUuid);
+ }
+
+ value = [advertisementData objectForKey:CBAdvertisementDataManufacturerDataKey];
+ if (value && [value isKindOfClass:[NSData class]]) {
+ QByteArray data = QByteArray::fromNSData(static_cast<NSData *>(value));
+ manufacturerData.insert(qFromLittleEndian<quint16>(data.constData()), data.mid(2));
+ }
+}
+
+}
+
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTLEDeviceInquiry)(PrivateAPI)
+- (void)stopScanSafe;
+- (void)stopNotifier;
+@end
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTLEDeviceInquiry)
+{
+ LECBManagerNotifier *notifier;
+ ObjCScopedPointer<CBCentralManager> manager;
+
+ QList<QBluetoothDeviceInfo> devices;
+ LEInquiryState internalState;
+ int inquiryTimeoutMS;
+
+ QT_PREPEND_NAMESPACE(DarwinBluetooth)::GCDTimer elapsedTimer;
+}
+
+-(id)initWithNotifier:(LECBManagerNotifier *)aNotifier
+{
+ if (self = [super init]) {
+ Q_ASSERT(aNotifier);
+ notifier = aNotifier;
+ internalState = InquiryStarting;
+ inquiryTimeoutMS = DarwinBluetooth::defaultLEScanTimeoutMS;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [self stopScanSafe];
+ [manager setDelegate:nil];
+ [elapsedTimer cancelTimer];
+ [self stopNotifier];
+ [super dealloc];
+}
+
+- (void)timeout:(id)sender
+{
+ Q_UNUSED(sender)
+
+ if (internalState == InquiryActive) {
+ [self stopScanSafe];
+ [manager setDelegate:nil];
+ internalState = InquiryFinished;
+ Q_ASSERT(notifier);
+ emit notifier->discoveryFinished();
+ } else if (internalState == InquiryStarting) {
+ // This is interesting on iOS only, where the system shows an alert
+ // asking to enable Bluetooth in the 'Settings' app. If not done yet
+ // (after 30 seconds) - we consider this as an error.
+ [manager setDelegate:nil];
+ internalState = ErrorPoweredOff;
+ Q_ASSERT(notifier);
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
+ }
+}
+
+- (void)startWithTimeout:(int)timeout
+{
+ dispatch_queue_t leQueue(DarwinBluetooth::qt_LE_queue());
+ Q_ASSERT(leQueue);
+ inquiryTimeoutMS = timeout;
+ manager.reset([[CBCentralManager alloc] initWithDelegate:self queue:leQueue]);
+}
+
+- (void)centralManagerDidUpdateState:(CBCentralManager *)central
+{
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability-new"
+
+ if (central != manager)
+ return;
+
+ if (internalState != InquiryActive && internalState != InquiryStarting)
+ return;
+
+ Q_ASSERT(notifier);
+
+ using namespace DarwinBluetooth;
+
+ const auto state = central.state;
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
+ if (state == CBManagerStatePoweredOn) {
+#else
+ if (state == CBCentralManagerStatePoweredOn) {
+#endif
+ if (internalState == InquiryStarting) {
+ internalState = InquiryActive;
+
+ if (inquiryTimeoutMS > 0) {
+ [elapsedTimer cancelTimer];
+ elapsedTimer.resetWithoutRetain([[GCDTimerObjC alloc] initWithDelegate:self]);
+ [elapsedTimer startWithTimeout:inquiryTimeoutMS step:timeStepMS];
+ }
+
+ [manager scanForPeripheralsWithServices:nil options:nil];
+ } // Else we ignore.
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
+ } else if (state == CBManagerStateUnsupported || state == CBManagerStateUnauthorized) {
+#else
+ } else if (state == CBCentralManagerStateUnsupported || state == CBCentralManagerStateUnauthorized) {
+#endif
+ if (internalState == InquiryActive) {
+ [self stopScanSafe];
+ // Not sure how this is possible at all,
+ // probably, can never happen.
+ internalState = ErrorPoweredOff;
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
+ } else {
+ internalState = ErrorLENotSupported;
+ emit notifier->LEnotSupported();
+ }
+
+ [manager setDelegate:nil];
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
+ } else if (state == CBManagerStatePoweredOff) {
+#else
+ } else if (state == CBCentralManagerStatePoweredOff) {
+#endif
+
+#ifndef Q_OS_MACOS
+ if (internalState == InquiryStarting) {
+ // On iOS a user can see at this point an alert asking to
+ // enable Bluetooth in the "Settings" app. If a user does so,
+ // we'll receive 'PoweredOn' state update later.
+ // No change in internalState. Wait for 30 seconds.
+ [elapsedTimer cancelTimer];
+ elapsedTimer.resetWithoutRetain([[GCDTimerObjC alloc] initWithDelegate:self]);
+ [elapsedTimer startWithTimeout:powerOffTimeoutMS step:300];
+ return;
+ }
+#else
+ Q_UNUSED(powerOffTimeoutMS)
+#endif // Q_OS_MACOS
+ [elapsedTimer cancelTimer];
+ [self stopScanSafe];
+ [manager setDelegate:nil];
+ internalState = ErrorPoweredOff;
+ // On macOS we report PoweredOffError and our C++ owner will delete us
+ // (here we're kwnon as 'self'). Connection is Qt::QueuedConnection so we
+ // are apparently safe to call -stopNotifier after the signal.
+ emit notifier->CBManagerError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
+ [self stopNotifier];
+ } else {
+ // The following two states we ignore (from Apple's docs):
+ //"
+ // -CBCentralManagerStateUnknown
+ // The current state of the central manager is unknown;
+ // an update is imminent.
+ //
+ // -CBCentralManagerStateResetting
+ // The connection with the system service was momentarily
+ // lost; an update is imminent. "
+ // Wait for this imminent update.
+ }
+
+#pragma clang diagnostic pop
+}
+
+- (void)stopScanSafe
+{
+ // CoreBluetooth warns about API misused if we call stopScan in a state
+ // other than powered on. Hence this 'Safe' ...
+ if (!manager)
+ return;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability-new"
+
+ if (internalState == InquiryActive) {
+ const auto state = manager.data().state;
+ #if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
+ if (state == CBManagerStatePoweredOn)
+ #else
+ if (state == CBCentralManagerStatePoweredOn)
+ #endif
+ [manager stopScan];
+ }
+
+#pragma clang diagnostic pop
+}
+
+- (void)stopNotifier
+{
+ if (notifier) {
+ notifier->disconnect();
+ notifier->deleteLater();
+ notifier = nullptr;
+ }
+}
+
+- (void)stop
+{
+ [self stopScanSafe];
+ [manager setDelegate:nil];
+ [elapsedTimer cancelTimer];
+ [self stopNotifier];
+ internalState = InquiryCancelled;
+}
+
+- (void)centralManager:(CBCentralManager *)central
+ didDiscoverPeripheral:(CBPeripheral *)peripheral
+ advertisementData:(NSDictionary *)advertisementData
+ RSSI:(NSNumber *)RSSI
+{
+ using namespace DarwinBluetooth;
+
+ if (central != manager)
+ return;
+
+ if (internalState != InquiryActive)
+ return;
+
+ if (!notifier)
+ return;
+
+ QBluetoothUuid deviceUuid;
+
+ if (!peripheral.identifier) {
+ qCWarning(QT_BT_DARWIN) << "peripheral without NSUUID";
+ return;
+ }
+
+ deviceUuid = DarwinBluetooth::qt_uuid(peripheral.identifier);
+
+ if (deviceUuid.isNull()) {
+ qCWarning(QT_BT_DARWIN) << "no way to address peripheral, QBluetoothUuid is null";
+ return;
+ }
+
+ const AdvertisementData qtAdvData(advertisementData);
+ QString name(qtAdvData.localName);
+ if (!name.size() && peripheral.name)
+ name = QString::fromNSString(peripheral.name);
+
+ // TODO: fix 'classOfDevice' (0 for now).
+ QBluetoothDeviceInfo newDeviceInfo(deviceUuid, name, 0);
+ if (RSSI)
+ newDeviceInfo.setRssi([RSSI shortValue]);
+
+ if (qtAdvData.serviceUuids.size())
+ newDeviceInfo.setServiceUuids(qtAdvData.serviceUuids);
+
+ const QList<quint16> keys = qtAdvData.manufacturerData.keys();
+ for (quint16 key : keys)
+ newDeviceInfo.setManufacturerData(key, qtAdvData.manufacturerData.value(key));
+
+ // CoreBluetooth scans only for LE devices.
+ newDeviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
+ emit notifier->deviceDiscovered(newDeviceInfo);
+}
+
+@end
diff --git a/src/bluetooth/darwin/btledeviceinquiry_p.h b/src/bluetooth/darwin/btledeviceinquiry_p.h
new file mode 100644
index 00000000..58c66e56
--- /dev/null
+++ b/src/bluetooth/darwin/btledeviceinquiry_p.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTLEDEVICEINQUIRY_P_H
+#define BTLEDEVICEINQUIRY_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qbluetoothdevicediscoveryagent.h"
+#include "qbluetoothdeviceinfo.h"
+#include "btgcdtimer_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qlist.h>
+
+#include <Foundation/Foundation.h>
+
+#include <CoreBluetooth/CoreBluetooth.h>
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothUuid;
+
+namespace DarwinBluetooth
+{
+
+class LECBManagerNotifier;
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+using QT_PREPEND_NAMESPACE(DarwinBluetooth)::LECBManagerNotifier;
+using QT_PREPEND_NAMESPACE(DarwinBluetooth)::ObjCScopedPointer;
+
+enum LEInquiryState
+{
+ InquiryStarting,
+ InquiryActive,
+ InquiryFinished,
+ InquiryCancelled,
+ ErrorPoweredOff,
+ ErrorLENotSupported
+};
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTLEDeviceInquiry) : NSObject<CBCentralManagerDelegate, QT_MANGLE_NAMESPACE(GCDTimerDelegate)>
+- (id)initWithNotifier:(LECBManagerNotifier *)aNotifier;
+- (void)dealloc;
+
+// IMPORTANT: both 'startWithTimeout' and 'stop' MUST be executed on the "Qt's
+// LE queue".
+- (void)startWithTimeout:(int)timeout;
+- (void)stop;
+
+@end
+
+#endif
diff --git a/src/bluetooth/darwin/btnotifier.cpp b/src/bluetooth/darwin/btnotifier.cpp
new file mode 100644
index 00000000..24f54a09
--- /dev/null
+++ b/src/bluetooth/darwin/btnotifier.cpp
@@ -0,0 +1 @@
+#include "btnotifier_p.h"
diff --git a/src/bluetooth/darwin/btnotifier_p.h b/src/bluetooth/darwin/btnotifier_p.h
new file mode 100644
index 00000000..e074a225
--- /dev/null
+++ b/src/bluetooth/darwin/btnotifier_p.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTNOTIFIER_P_H
+#define BTNOTIFIER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+
+#include "qbluetoothdevicediscoveryagent.h"
+#include "qlowenergycontroller.h"
+#include "qbluetoothdeviceinfo.h"
+#include "qbluetoothuuid.h"
+#include "qbluetooth.h"
+
+#include <QtCore/qsharedpointer.h>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qobject.h>
+
+QT_BEGIN_NAMESPACE
+
+class QLowEnergyServicePrivate;
+
+namespace DarwinBluetooth
+{
+
+class LECBManagerNotifier : public QObject
+{
+ Q_OBJECT
+
+Q_SIGNALS:
+ void deviceDiscovered(QBluetoothDeviceInfo deviceInfo);
+ void discoveryFinished();
+
+ void connected();
+ void disconnected();
+
+ void serviceDiscoveryFinished();
+ void serviceDetailsDiscoveryFinished(QSharedPointer<QLowEnergyServicePrivate> service);
+ void characteristicRead(QLowEnergyHandle charHandle, const QByteArray &value);
+ void characteristicWritten(QLowEnergyHandle charHandle, const QByteArray &value);
+ void characteristicUpdated(QLowEnergyHandle charHandle, const QByteArray &value);
+ void descriptorRead(QLowEnergyHandle descHandle, const QByteArray &value);
+ void descriptorWritten(QLowEnergyHandle descHandle, const QByteArray &value);
+ void notificationEnabled(QLowEnergyHandle charHandle, bool enabled);
+ void servicesWereModified();
+
+ void LEnotSupported();
+ void CBManagerError(QBluetoothDeviceDiscoveryAgent::Error error);
+ void CBManagerError(QLowEnergyController::Error error);
+ void CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyController::Error error);
+ void CBManagerError(const QBluetoothUuid &serviceUuid, QLowEnergyService::ServiceError error);
+};
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/bluetooth/darwin/btobexsession.mm b/src/bluetooth/darwin/btobexsession.mm
new file mode 100644
index 00000000..ba48aab0
--- /dev/null
+++ b/src/bluetooth/darwin/btobexsession.mm
@@ -0,0 +1,841 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbluetoothaddress.h"
+#include "btobexsession_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qvector.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qlist.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth
+{
+
+OBEXSessionDelegate::~OBEXSessionDelegate()
+{
+}
+
+namespace {
+
+struct OBEXHeader
+{
+ OBEXHeader() : headerID(0)
+ {
+ }
+
+ quint8 headerID;
+ QVariant value;
+};
+
+enum {
+ // Bits 7 and 8 == header's format.
+ OBEXHeaderFormatMask = 0xc0,
+ //
+ OBEXHeaderFormatUnicode = 0, // 87
+ OBEXHeaderFormatByteSequence = 0x40, // 0100 0000
+ OBEXHeaderFormat1Byte = 0x80, // 1000 0000
+ OBEXHeaderFormat4Byte = 0xc0, // 1100 0000
+
+};
+
+quint32 extract_uint32(const uint8_t *bytes)
+{
+ // Four byte value, high byte first.
+ Q_ASSERT_X(bytes, Q_FUNC_INFO, "invalid input data (null)");
+
+ uint32_t value = uint32_t();
+ std::copy(bytes, bytes + sizeof value, reinterpret_cast<uint8_t *>(&value));
+
+ return NSSwapBigIntToHost(value);
+}
+
+quint16 extract_uint16(const uint8_t *bytes)
+{
+ // Two byte value, high byte first.
+ Q_ASSERT_X(bytes, Q_FUNC_INFO, "invalid input data (null)");
+
+ uint16_t value = uint16_t();
+ std::copy(bytes, bytes + sizeof value, reinterpret_cast<uint8_t *>(&value));
+
+ return NSSwapBigShortToHost(value);
+}
+
+QString extract_qstring(const uint8_t *bytes, quint16 stringLength)
+{
+ if (bytes && stringLength) {
+ NSString * const nsString = [[NSString alloc] initWithBytes:bytes
+ length:stringLength
+ encoding:NSUnicodeStringEncoding];
+ if (nsString)
+ return QString::fromNSString(nsString);
+ }
+
+ // Empty string is an error, "valid" empty strings are
+ // handled separately.
+ return QString();
+}
+
+QList<OBEXHeader> qt_bluetooth_headers(const uint8_t *data, std::size_t length)
+{
+ // Convert a data from IOBluetooth into something, Qt understands.
+ // Possible formats (bits 7 and 8):
+ // 1. 00 Two bytes of length folowed by a null-terminated
+ // Unicode text string (length is unsigned integer;
+ // it covers the header ID and the whole of the header
+ // value, including the length bytes and the two bytes
+ // of null terminator.
+ // 2. 01 Two bytes of length followed by a byte sequence (length
+ // is an unsigned integer sent high byte first; it covers
+ // the header ID and the whole of the header value).
+ // 3. 10 A single byte value.
+ // 4. 11 A four byte value, sent high byte first.
+
+ Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)");
+ Q_ASSERT_X(length >= 2, Q_FUNC_INFO, "invalid data length");
+
+ Q_UNUSED(data)
+ Q_UNUSED(length)
+
+ QList<OBEXHeader> empty;
+ QList<OBEXHeader> qtHeaders;
+
+ for (std::size_t i = 0; i < length;) {
+ std::size_t headerLength = 0;
+ OBEXHeader header;
+ header.headerID = data[i];
+
+ switch (data[i] & OBEXHeaderFormatMask) {
+ case OBEXHeaderFormatUnicode:
+ {
+ if (i + 3 > length)
+ return empty;
+ headerLength = extract_uint16(data + i + 1);
+ // Invalid length or input data:
+ if (headerLength < 3 || i + headerLength > length)
+ return empty;
+ if (headerLength == 3 || headerLength == 5) { // Can 5 ever happen?
+ header.value.fromValue<QString>(QString());
+ } else if (headerLength > 5) {// TODO: We do not check now, that the string actually valid.
+ const QString value(extract_qstring(data + i + 3, headerLength - 5));
+ if (!value.length()) // Some error?
+ return empty;
+ header.value.setValue<QString>(value);
+ } else // Still something weird.
+ return empty;
+ break;
+ }
+ case OBEXHeaderFormatByteSequence:
+ {
+ if (i + 3 > length)
+ return empty;
+ headerLength = extract_uint16(data + i + 1);
+ // Something is wrong:
+ if (headerLength < 3 || i + headerLength > length)
+ return empty;
+ QVector<unsigned char> value;
+ if (headerLength > 3) {
+ value.resize(headerLength - 3);
+ std::copy(data, data + headerLength, value.begin());
+ }
+ header.value.setValue<QVector<unsigned char> >(value);
+ break;
+ }
+ case OBEXHeaderFormat1Byte:
+ {
+ // 1 byte integer + 1 byte headerID == 2
+ if (i + 2 > length)
+ return empty;
+ headerLength = 2;
+ header.value.setValue<quint8>(data[i + 1]);
+ break;
+ }
+ case OBEXHeaderFormat4Byte:
+ {
+ // 4 byte integer + 1 byte headerID == 5
+ if (i + 5 > length)
+ return empty;
+ headerLength = 5;
+ header.value.setValue<quint32>(extract_uint32(data + i + 1));
+ break;
+ }
+ default:
+ qCWarning(QT_BT_DARWIN) << "invalid header format";
+ return empty;
+ }
+
+ i += headerLength;
+ qtHeaders.push_back(header);
+ }
+
+ return qtHeaders;
+}
+
+bool append_uint16(ObjCStrongReference<NSMutableData> headers, uint16_t value)
+{
+ if (!headers)
+ return false;
+
+ const NSUInteger length = [headers length];
+ const uint16_t valueSwapped = NSSwapHostShortToBig(value);
+ [headers appendBytes:&valueSwapped length:sizeof valueSwapped];
+
+ return [headers length] - length == 2;
+}
+
+
+bool append_four_byte_header(ObjCStrongReference<NSMutableData> headers, uint8_t headerID,
+ uint32_t headerValue)
+{
+ if (!headers)
+ return false;
+
+ const NSUInteger length = [headers length];
+ // Header ID (1 byte)
+ [headers appendBytes:&headerID length:1];
+ // Header value (4 bytes)
+ const uint32_t valueSwapped(NSSwapHostIntToBig(headerValue));
+ [headers appendBytes:&valueSwapped length:sizeof valueSwapped];
+
+ return [headers length] - length == 5;
+}
+
+bool append_unicode_header(ObjCStrongReference<NSMutableData> headers, uint8_t headerID,
+ const QString &string)
+{
+ // Two bytes of length followed by a null-terminated
+ // Unicode text string. Length is unsigned integer,
+ // it covers the header ID and the whole of the header
+ // value, including the length bytes and the two bytes
+ // of null terminator.
+ // All Obj-C objects are autoreleased.
+
+ if (!headers)
+ return false;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ const NSUInteger initialLength = [headers length];
+ [headers appendBytes:&headerID length:1];
+
+ if (!string.length()) {
+ // Empty string. The length is 3
+ // (header ID + length value itself).
+ return append_uint16(headers, 3);
+ }
+
+ NSString *const nsString = string.toNSString();
+ if (!nsString)
+ return false;
+
+ // TODO: check if the encodings is right. It was NSUnicodeStringEncoding but
+ // byte order was wrong. Also, I do not need BOM check anymore?
+ NSData *const data = [nsString dataUsingEncoding:NSUTF16BigEndianStringEncoding];
+ if (!data)
+ return false;
+
+ // This data can include byte-order marker (BOM) and does not include
+ // a null terminator. Anyway, the length must be >= 2.
+ NSUInteger length = [data length];
+ if (length < 2)
+ return false;
+
+ const uint8_t *dataPtr = static_cast<const uint8_t *>([data bytes]);
+ if ((dataPtr[0] == 0xff && dataPtr[1] == 0xfe)
+ || (dataPtr[0] == 0xfe && dataPtr[1] == 0xff)) {
+ if (length == 2) //Something weird?
+ return false;
+ // Skip a BOM.
+ dataPtr += 2;
+ length -= 2;
+ }
+
+ // headerID + length == 3, string's length + 2
+ // bytes for a null terminator.
+ if (!append_uint16(headers, length + 3 + 2))
+ return false;
+
+ [headers appendBytes:dataPtr length:length];
+ const uint8_t nullTerminator[2] = {};
+ [headers appendBytes:nullTerminator length:2];
+
+ return [headers length] - initialLength == length + 3 + 2;
+}
+
+ObjCStrongReference<NSMutableData> next_data_chunk(QIODevice &inputStream, IOBluetoothOBEXSession *session,
+ NSUInteger headersLength, bool &isLast)
+{
+ // Work only for OBEX put (we request a specific payload length).
+ Q_ASSERT_X(session, Q_FUNC_INFO, "invalid OBEX session (nil)");
+
+ const OBEXMaxPacketLength packetSize = [session getAvailableCommandPayloadLength:kOBEXOpCodePut];
+ if (!packetSize || headersLength >= packetSize)
+ return ObjCStrongReference<NSMutableData>();
+
+ const OBEXMaxPacketLength maxBodySize = packetSize - headersLength;
+
+ QVector<char> block(maxBodySize);
+ const int realSize = inputStream.read(block.data(), block.size());
+ if (realSize <= 0) {
+ // Well, either the last or an error.
+ isLast = true;
+ return ObjCStrongReference<NSMutableData>();
+ }
+
+ ObjCStrongReference<NSMutableData> chunk([NSMutableData dataWithBytes:block.data()
+ length:realSize], true);
+ if (chunk && [chunk length]) {
+ // If it actually was the last chunk
+ // of a length == maxBodySize, we'll
+ // send one more packet (empty though)?
+ isLast = [chunk length] < maxBodySize;
+ }
+
+ return chunk;
+}
+
+bool check_connect_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &response)
+{
+ Q_ASSERT_X(e, Q_FUNC_INFO, "invalid event (null)");
+
+ // This function tries to extract either an error code or a
+ // server response code. "Good" event has type connect command respond
+ // and reponse code 0XA0. Everything else is a "bad" event and
+ // means connect failed.
+
+ // If it's an error event - return the error.
+ // If it's connect response - extract the response code.
+ // If it's something else (is it possible?) - set general error.
+
+ if (e->type == kOBEXSessionEventTypeError) {
+ error = e->u.errorData.error;
+ return false;
+ } if (e->type == kOBEXSessionEventTypeConnectCommandResponseReceived) {
+ // We can read response code only for such an event.
+ response = e->u.connectCommandResponseData.serverResponseOpCode;
+ return response == kOBEXResponseCodeSuccessWithFinalBit;
+ } else {
+ qCWarning(QT_BT_DARWIN) << "unexpected event type";
+ error = kOBEXGeneralError;
+ return false;
+ }
+}
+
+bool check_put_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &response)
+{
+ Q_ASSERT_X(e, Q_FUNC_INFO, "invalid event (null)");
+
+ // See the comments above.
+
+ if (e->type == kOBEXSessionEventTypeError) {
+ error = e->u.errorData.error;
+ return false;
+ } else if (e->type == kOBEXSessionEventTypePutCommandResponseReceived) {
+ response = e->u.putCommandResponseData.serverResponseOpCode;
+ return response == kOBEXResponseCodeContinueWithFinalBit ||
+ response == kOBEXResponseCodeSuccessWithFinalBit;
+ } else {
+ qCWarning(QT_BT_DARWIN) << "unexpected event type";
+ error = kOBEXGeneralError;
+ return false;
+ }
+}
+
+bool check_abort_event(const OBEXSessionEvent *e, OBEXError &error, OBEXOpCode &response)
+{
+ Q_ASSERT_X(e, Q_FUNC_INFO, "invalid event (null)");
+
+ if (e->type == kOBEXSessionEventTypeError) {
+ error = e->u.errorData.error;
+ return false;
+ } else if (e->type == kOBEXSessionEventTypeAbortCommandResponseReceived) {
+ response = e->u.abortCommandResponseData.serverResponseOpCode;
+ return response == kOBEXResponseCodeSuccessWithFinalBit;
+ } else {
+ qCWarning(QT_BT_DARWIN) << "unexpected event type";
+ return false;
+ }
+}
+
+} // Unnamed namespace.
+} // namespace DarwinBluetooth.
+
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTOBEXSession) (PrivateAPI)
+
+// OBEXDisconnect returns void - it's considered to be always
+// successful. These methods are "private API" - no need to expose them,
+// for internal use only.
+- (void)OBEXDisconnect;
+- (void)OBEXDisconnectHandler:(const OBEXSessionEvent*)event;
+
+@end
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTOBEXSession)
+{
+ QT_PREPEND_NAMESPACE(DarwinBluetooth)::OBEXSessionDelegate *delegate;
+ IOBluetoothDevice *device;
+ quint16 channelID;
+ IOBluetoothOBEXSession *session;
+
+ QT_PREPEND_NAMESPACE(DarwinBluetooth)::OBEXRequest currentRequest;
+
+ bool connected;
+ bool connectionIDFound;
+ quint32 connectionID;
+
+ QT_PREPEND_NAMESPACE(QIODevice) *inputStream;
+
+ // TODO: switch to scoped pointers or strong reference objects instead.
+ NSMutableData *headersData;
+ NSMutableData *bodyData;
+
+ quint32 bytesSent;
+ bool pendingAbort;
+}
+
++ (OBEXMaxPacketLength) maxPacketLength
+{
+ // Some arbitrary number, we'll adjust it as soon as
+ // we connected, asking a session about packet size for
+ // a particular command.
+ return 0x1000;
+}
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth::OBEXSessionDelegate) *)aDelegate
+ remoteDevice:(const QBluetoothAddress &)deviceAddress channelID:(quint16)port
+{
+ Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)");
+ Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO, "invalid remote device address");
+ Q_ASSERT_X(port, Q_FUNC_INFO, "invalid port (0)");
+
+ if (self = [super init]) {
+ connected = false;
+ currentRequest = DarwinBluetooth::OBEXNoop;
+ connectionID = 0;
+ connectionIDFound = false;
+
+ const BluetoothDeviceAddress addr(DarwinBluetooth::iobluetooth_address(deviceAddress));
+ device = [[IOBluetoothDevice deviceWithAddress:&addr] retain];
+ if (!device) {
+ qCWarning(QT_BT_DARWIN) << "failed to create an IOBluetoothDevice";
+ return self;
+ }
+
+ session = [[IOBluetoothOBEXSession alloc] initWithDevice:device channelID:port];
+ if (!session) {
+ qCWarning(QT_BT_DARWIN) << "failed to create an OBEX session";
+ return self;
+ }
+
+ delegate = aDelegate;
+ channelID = port;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [device release];
+ [session release];
+
+ [headersData release];
+ [bodyData release];
+
+ [super dealloc];
+}
+
+- (OBEXError)OBEXConnect
+{
+ if (!session) {
+ qCWarning(QT_BT_DARWIN) << "invalid session (nil)";
+ return kOBEXGeneralError;
+ }
+
+ // That's a "single-shot" operation:
+ Q_ASSERT_X(currentRequest == DarwinBluetooth::OBEXNoop, Q_FUNC_INFO,
+ "can not connect in this state (another request is active)");
+
+ connected = false;
+ connectionIDFound = false;
+ connectionID = 0;
+ currentRequest = DarwinBluetooth::OBEXConnect;
+
+ const OBEXError status = [session OBEXConnect:kOBEXConnectFlagNone
+ maxPacketLength:[QT_MANGLE_NAMESPACE(DarwinBTOBEXSession) maxPacketLength]
+ optionalHeaders:nullptr
+ optionalHeadersLength:0
+ eventSelector:@selector(OBEXConnectHandler:)
+ selectorTarget:self
+ refCon:nullptr];
+
+ if (status != kOBEXSuccess) {
+ currentRequest = DarwinBluetooth::OBEXNoop;
+ // Already connected is still ok for us?
+ connected = status == kOBEXSessionAlreadyConnectedError;
+ }
+
+ return status;
+}
+
+- (void)OBEXConnectHandler:(const OBEXSessionEvent*)event
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(session, Q_FUNC_INFO, "invalid session (nil)");
+
+ if (pendingAbort) {
+ currentRequest = OBEXNoop;
+ [self OBEXAbort];
+ return;
+ }
+
+ if (currentRequest != OBEXConnect) {
+ qCWarning(QT_BT_DARWIN) << "called while there is no "
+ "active connect request";
+ return;
+ }
+
+ currentRequest = OBEXNoop;
+
+ OBEXError errorCode = kOBEXSuccess;
+ OBEXOpCode responseCode = kOBEXResponseCodeSuccessWithFinalBit;
+
+ if (!check_connect_event(event, errorCode, responseCode)) {
+ // OBEX connect failed.
+ if (delegate)
+ delegate->OBEXConnectError(errorCode, responseCode);
+ return;
+ }
+
+ const OBEXConnectCommandResponseData *const response = &event->u.connectCommandResponseData;
+ if (response->headerDataPtr && response->headerDataLength >= 2) {
+ // 2 == 1 byte headerID + at least 1 byte headerValue ...
+ const QList<OBEXHeader> headers(qt_bluetooth_headers(static_cast<const uint8_t *>(response->headerDataPtr),
+ response->headerDataLength));
+ // ConnectionID is used when multiplexing OBEX connections
+ // to identify which particular connection this object is
+ // being sent on. When used, this _must_ be the first
+ // header sent.
+
+ for (const OBEXHeader &header : headers) {
+ if (header.headerID == kOBEXHeaderIDConnectionID) {
+ connectionID = header.value.value<quint32>();
+ connectionIDFound = true;
+ break;
+ }
+ }
+ }
+
+ connected = true;
+
+ if (delegate)
+ delegate->OBEXConnectSuccess();
+}
+
+- (OBEXError)OBEXAbort
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(session, Q_FUNC_INFO, "invalid OBEX session (nil)");
+
+ if (currentRequest == OBEXNoop) {
+ pendingAbort = false;
+
+ if (![self isConnected])
+ return kOBEXSessionNotConnectedError;
+
+ currentRequest = OBEXAbort;
+ const OBEXError status = [session OBEXAbort:nullptr
+ optionalHeadersLength:0
+ eventSelector:@selector(OBEXAbortHandler:)
+ selectorTarget:self
+ refCon:nullptr];
+ if (status != kOBEXSuccess)
+ currentRequest = OBEXNoop;
+
+ return status;
+ } else {
+ // We're in the middle of some request, wait
+ // for any handler to be called first.
+ pendingAbort = true;
+ return kOBEXSuccess;
+ }
+}
+
+- (void)OBEXAbortHandler:(const OBEXSessionEvent*)event
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(session, Q_FUNC_INFO, "invalid OBEX session (nil)");
+
+ if (currentRequest != OBEXAbort) {
+ qCWarning(QT_BT_DARWIN) << "called while there "
+ "is no ABORT request";
+ return;
+ }
+
+ pendingAbort = false;
+ currentRequest = OBEXNoop;
+
+ if (delegate) {
+ OBEXError error = kOBEXSuccess;
+ OBEXOpCode response = kOBEXResponseCodeSuccessWithFinalBit;
+ if (check_abort_event(event, error, response))
+ delegate->OBEXAbortSuccess();
+ }
+}
+
+- (OBEXError)OBEXPutFile:(QT_PREPEND_NAMESPACE(QIODevice) *)input withName:(const QString &)name
+{
+ using namespace DarwinBluetooth;
+
+ if (!session || ![self isConnected])
+ return kOBEXSessionNotConnectedError;
+
+ Q_ASSERT_X(currentRequest == OBEXNoop, Q_FUNC_INFO,
+ "the current session has an active request already");
+ Q_ASSERT_X(input, Q_FUNC_INFO, "invalid input stream (null)");
+ Q_ASSERT_X(input->isReadable(), Q_FUNC_INFO, "invalid input stream (not readable)");
+
+ // We send a put request with a couple of headers (size/file name/may be connection ID) +
+ // a payload.
+ const qint64 fileSize = input->size();
+ if (fileSize <= 0 || fileSize >= std::numeric_limits<uint32_t>::max()) {
+ qCWarning(QT_BT_DARWIN) << "invalid input file size";
+ return kOBEXBadArgumentError;
+ }
+
+ ObjCStrongReference<NSMutableData> headers([[NSMutableData alloc] init], false);
+ if (!headers) {
+ qCWarning(QT_BT_DARWIN) << "failed to allocate headers";
+ return kOBEXNoResourcesError;
+ }
+
+ // Now we append headers with: Connection ID (if any),
+ // file name, file size, the first (and probably the only) chunk of data
+ // from the input stream and send a put request.
+
+ if (connectionIDFound) {
+ if (!append_four_byte_header(headers, kOBEXHeaderIDConnectionID, connectionID)) {
+ qCWarning(QT_BT_DARWIN) << "failed to append connection ID header";
+ return kOBEXNoResourcesError;
+ }
+ }
+
+ if (name.length()) {
+ if (!append_unicode_header(headers, kOBEXHeaderIDName, name)) {
+ qCWarning(QT_BT_DARWIN) << "failed to append a unicode string";
+ return kOBEXNoResourcesError;
+ }
+ }
+
+ if (fileSize && !input->isSequential())
+ append_four_byte_header(headers, kOBEXHeaderIDLength, uint32_t(fileSize));
+
+ bool lastChunk = false;
+ ObjCStrongReference<NSMutableData> chunk(next_data_chunk(*input, session, [headers length], lastChunk));
+ if (!chunk || ![chunk length]) {
+ // We do not support PUT-DELETE (?)
+ // At least the first chunk is expected to be non-empty.
+ qCWarning(QT_BT_DARWIN) << "invalid input stream";
+ return kOBEXBadArgumentError;
+ }
+
+ currentRequest = OBEXPut;
+
+ const OBEXError status = [session OBEXPut:lastChunk
+ headersData:[headers mutableBytes]
+ headersDataLength:[headers length]
+ bodyData:[chunk mutableBytes]
+ bodyDataLength:[chunk length]
+ eventSelector:@selector(OBEXPutHandler:)
+ selectorTarget:self
+ refCon:nullptr];
+
+ if (status == kOBEXSuccess) {
+ if (delegate && fileSize && !input->isSequential())
+ delegate->OBEXPutDataSent([chunk length], fileSize);
+
+ bytesSent = [chunk length];
+ headersData = headers.take();
+ bodyData = chunk.take();
+ inputStream = input;
+ } else {
+ // PUT request failed and we now
+ // want to close a connection/session.
+ currentRequest = OBEXNoop;
+ // Try to cleanup (disconnect).
+ [self OBEXDisconnect];
+ }
+
+ return status;
+}
+
+- (void)OBEXPutHandler:(const OBEXSessionEvent*)event
+{
+ using namespace DarwinBluetooth;
+
+ Q_ASSERT_X(session, Q_FUNC_INFO, "invalid OBEX session (nil)");
+
+ if (pendingAbort) {
+ currentRequest = OBEXNoop;
+ [self OBEXAbort];
+ return;
+ }
+
+ if (currentRequest != OBEXPut) {
+ qCWarning(QT_BT_DARWIN) << "called while the current "
+ "request is not a put request";
+ return;
+ }
+
+ OBEXError error = kOBEXSuccess;
+ OBEXOpCode responseCode = kOBEXResponseCodeSuccessWithFinalBit;
+ if (!check_put_event(event, error, responseCode)) {
+ currentRequest = OBEXNoop;
+ if (delegate)
+ delegate->OBEXPutError(error, responseCode);
+ [self OBEXDisconnect];
+ return;
+ }
+
+ // Now try to send more data if we have any.
+ if (responseCode == kOBEXResponseCodeContinueWithFinalBit) {
+ // Send more data.
+ bool lastChunk = false;
+ // 0 for the headers length, no more headers.
+ ObjCStrongReference<NSMutableData> chunk(next_data_chunk(*inputStream, session, 0, lastChunk));
+ if (!chunk && !lastChunk) {
+ qCWarning(QT_BT_DARWIN) << "failed to allocate the next memory chunk";
+ return;
+ }
+
+ void *dataPtr = chunk ? [chunk mutableBytes] : nullptr;
+ const NSUInteger dataSize = chunk ? [chunk length] : 0;
+ const OBEXError status = [session OBEXPut:lastChunk
+ headersData:nullptr
+ headersDataLength:0
+ bodyData:dataPtr
+ bodyDataLength:dataSize
+ eventSelector:@selector(OBEXPutHandler:)
+ selectorTarget:self
+ refCon:nullptr];
+
+ if (status != kOBEXSuccess) {
+ qCWarning(QT_BT_DARWIN) << "failed to send the next memory chunk";
+ currentRequest = OBEXNoop;
+ if (delegate) // Response code is not important here.
+ delegate->OBEXPutError(kOBEXNoResourcesError, 0);
+
+ [self OBEXDisconnect];
+ } else {
+ [bodyData release];
+ bytesSent += [chunk length];
+ bodyData = chunk.take();//retained already.
+
+ if (delegate && !inputStream->isSequential())
+ delegate->OBEXPutDataSent(bytesSent, inputStream->size());
+ }
+ } else if (responseCode == kOBEXResponseCodeSuccessWithFinalBit) {
+ currentRequest = OBEXNoop;
+ if (delegate)
+ delegate->OBEXPutSuccess();
+
+ [self OBEXDisconnect];
+ }
+}
+
+- (void)OBEXDisconnect
+{
+ Q_ASSERT_X(session, Q_FUNC_INFO, "invalid session (nil)");
+
+ currentRequest = DarwinBluetooth::OBEXDisconnect;
+
+ [session OBEXDisconnect:nullptr
+ optionalHeadersLength:0
+ eventSelector:@selector(OBEXDisconnectHandler:)
+ selectorTarget:self
+ refCon:nullptr];
+}
+
+- (void)OBEXDisconnectHandler:(const OBEXSessionEvent*)event
+{
+ Q_UNUSED(event)
+
+ Q_ASSERT_X(session, Q_FUNC_INFO, "invalid session (nil)");
+
+ // Event can have an error type, but there's nothing
+ // we can do - even "cleanup" failed.
+ connected = false;
+}
+
+- (bool)isConnected
+{
+ return device && session && connected;
+}
+
+- (void)closeSession
+{
+ // Clear the delegate and reset the request,
+ // do not try any of OBEX commands - the session will be deleted
+ // immediately.
+ delegate = nullptr;
+ // This will stop any handler (callback) preventing
+ // any read/write to potentially deleted objects.
+ currentRequest = DarwinBluetooth::OBEXNoop;
+}
+
+- (bool)hasActiveRequest
+{
+ return currentRequest != DarwinBluetooth::OBEXNoop && !pendingAbort;
+}
+
+@end
diff --git a/src/bluetooth/darwin/btobexsession_p.h b/src/bluetooth/darwin/btobexsession_p.h
new file mode 100644
index 00000000..11c6d226
--- /dev/null
+++ b/src/bluetooth/darwin/btobexsession_p.h
@@ -0,0 +1,138 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTOBEXSESSION_P_H
+#define BTOBEXSESSION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qvariant.h>
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+
+#include <IOBluetooth/IOBluetooth.h>
+
+// TODO: all this code must be removed in Qt 6?
+
+@class QT_MANGLE_NAMESPACE(DarwinBTOBEXSession);
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothAddress;
+class QIODevice;
+class QString;
+
+namespace DarwinBluetooth
+{
+
+class OBEXSessionDelegate
+{
+public:
+ typedef QT_MANGLE_NAMESPACE(DarwinBTOBEXSession) ObjCOBEXSession;
+
+ virtual ~OBEXSessionDelegate();
+
+ virtual void OBEXConnectError(OBEXError error, OBEXOpCode responseCode) = 0;
+ virtual void OBEXConnectSuccess() = 0;
+
+ virtual void OBEXAbortSuccess() = 0;
+
+ virtual void OBEXPutDataSent(quint32 current, quint32 total) = 0;
+ virtual void OBEXPutSuccess() = 0;
+ virtual void OBEXPutError(OBEXError error, OBEXOpCode responseCode) = 0;
+};
+
+enum OBEXRequest {
+ OBEXNoop,
+ OBEXConnect,
+ OBEXDisconnect,
+ OBEXPut,
+ OBEXGet,
+ OBEXSetPath,
+ OBEXAbort
+};
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+// OBEX Session, it's a "single-shot" operation as our QBluetoothTransferReply is
+// (it does not have an interface to re-send data or re-use the same transfer reply).
+// It either succeeds or fails and tries to cleanup in any case.
+@interface QT_MANGLE_NAMESPACE(DarwinBTOBEXSession) : NSObject
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth::OBEXSessionDelegate) *)aDelegate
+ remoteDevice:(const QBluetoothAddress &)deviceAddress channelID:(quint16)port;
+
+- (void)dealloc;
+
+// Below I have pairs: OBEX operation and its callback method.
+- (OBEXError)OBEXConnect;
+- (void)OBEXConnectHandler:(const OBEXSessionEvent*)event;
+
+- (OBEXError)OBEXAbort;
+- (void)OBEXAbortHandler:(const OBEXSessionEvent*)event;
+
+- (OBEXError)OBEXPutFile:(QT_PREPEND_NAMESPACE(QIODevice) *)inputStream withName:(const QString &)name;
+- (void)OBEXPutHandler:(const OBEXSessionEvent*)event;
+
+// Aux. methods.
+- (bool)isConnected;
+
+// To be called from C++ destructors. OBEXSession is not
+// valid anymore after this call (no more OBEX operations
+// can be executed). It's an ABORT/DISCONNECT sequence.
+// It also resets a delegate to null.
+- (void)closeSession;
+//
+- (bool)hasActiveRequest;
+
+@end
+
+#endif
diff --git a/src/bluetooth/darwin/btperipheralmanager.mm b/src/bluetooth/darwin/btperipheralmanager.mm
new file mode 100644
index 00000000..d4713ace
--- /dev/null
+++ b/src/bluetooth/darwin/btperipheralmanager.mm
@@ -0,0 +1,897 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+
+#include "qlowenergycharacteristicdata.h"
+#include "qlowenergydescriptordata.h"
+#include "btperipheralmanager_p.h"
+#include "qlowenergyservicedata.h"
+#include "btnotifier_p.h"
+#include "qbluetooth.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qlist.h>
+
+#include <algorithm>
+#include <limits>
+
+namespace
+{
+
+CBCharacteristicProperties cb_properties(const QLowEnergyCharacteristicData &data)
+{
+ // Direct 'mapping' is ok.
+ return CBCharacteristicProperties(int(data.properties()));
+}
+
+CBAttributePermissions cb_permissions(const QLowEnergyCharacteristicData &data)
+{
+ using QLEC = QLowEnergyCharacteristic;
+
+ const auto props = data.properties();
+ CBAttributePermissions cbFlags = {};
+
+ if ((props & QLEC::Write) || (props & QLEC::WriteNoResponse)
+ || (props & QLEC::WriteSigned)) {
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsWriteable);
+ }
+
+ if (props & QLEC::Read)
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsReadable);
+
+ if (data.writeConstraints() & QBluetooth::AttEncryptionRequired)
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsWriteEncryptionRequired);
+
+ if (data.readConstraints() & QBluetooth::AttEncryptionRequired)
+ cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsReadEncryptionRequired);
+
+ return cbFlags;
+}
+
+ObjCStrongReference<CBMutableCharacteristic> create_characteristic(const QLowEnergyCharacteristicData &data)
+{
+ const ObjCStrongReference<CBMutableCharacteristic> ch([[CBMutableCharacteristic alloc] initWithType:cb_uuid(data.uuid())
+ properties:cb_properties(data)
+ value:nil
+ permissions:cb_permissions(data)],
+ false /*do not retain*/);
+ return ch;
+}
+
+ObjCStrongReference<CBMutableDescriptor> create_descriptor(const QLowEnergyDescriptorData &data)
+{
+ // CoreBluetooth supports only:
+ /*
+ "That said, only two of these are currently supported when creating local,
+ mutable descriptors: the characteristic user description descriptor and
+ the characteristic format descriptor, represented by the CBUUID constants
+ CBUUIDCharacteristicUserDescriptionString and CBUUIDCharacteristicFormatString"
+ */
+
+ if (data.uuid() != QBluetoothUuid::CharacteristicUserDescription &&
+ data.uuid() != QBluetoothUuid::CharacteristicPresentationFormat) {
+ qCWarning(QT_BT_DARWIN) << "unsupported descriptor" << data.uuid();
+ return {};
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ // Descriptors are immutable with CoreBluetooth, that's why we
+ // have to provide a value here and not able to change it later.
+ ObjCStrongReference<NSObject> value;
+ if (data.uuid() == QBluetoothUuid::CharacteristicUserDescription) {
+ const QString asQString(QString::fromUtf8(data.value()));
+ value.reset(asQString.toNSString());
+ } else {
+ const auto nsData = data_from_bytearray(data.value());
+ value.reset(nsData.data());
+ }
+
+ const ObjCStrongReference<CBMutableDescriptor> d([[CBMutableDescriptor alloc]
+ initWithType:cb_uuid(data.uuid())
+ value:value], false /*do not retain*/);
+ return d;
+}
+
+quint32 qt_countGATTEntries(const QLowEnergyServiceData &data)
+{
+ const auto maxu32 = std::numeric_limits<quint32>::max();
+ // + 1 for a service itself.
+ quint32 nEntries = 1 + quint32(data.includedServices().count());
+ for (const auto &ch : data.characteristics()) {
+ if (maxu32 - 2 < nEntries)
+ return {};
+ nEntries += 2;
+ if (maxu32 - ch.descriptors().count() < nEntries)
+ return {};
+ nEntries += ch.descriptors().count();
+ }
+
+ return nEntries;
+}
+
+bool qt_validate_value_range(const QLowEnergyCharacteristicData &data)
+{
+ if (data.minimumValueLength() > data.maximumValueLength()
+ || data.minimumValueLength() < 0) {
+ return false;
+ }
+
+ return data.value().size() <= data.maximumValueLength();
+}
+
+}
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTPeripheralManager) (PrivateAPI)
+
+- (void)addConnectedCentral:(CBCentral *)central;
+- (void)removeConnectedCentral:(CBCentral *)central;
+- (CBService *)findIncludedService:(const QBluetoothUuid &)qtUUID;
+
+- (void)addIncludedServices:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService;
+
+- (void)addCharacteristicsAndDescriptors:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService;
+
+- (CBATTError)validateWriteRequest:(CBATTRequest *)request;
+
+@end
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTPeripheralManager)
+{
+ ObjCScopedPointer<CBPeripheralManager> manager;
+ LECBManagerNotifier *notifier;
+
+ QLowEnergyHandle lastHandle;
+ // Services in this vector are placed in such order:
+ // the one that has included services, must
+ // follow its included services to avoid exceptions from CBPeripheralManager.
+ std::vector<ObjCStrongReference<CBMutableService>> services;
+ decltype(services.size()) nextServiceToAdd;
+
+ // Lookup map for included services:
+ std::map<QBluetoothUuid, CBService *> serviceIndex;
+ ObjCScopedPointer<NSMutableDictionary> advertisementData;
+
+ GenericLEMap<CBCharacteristic *> charMap;
+ GenericLEMap<ObjCStrongReference<NSMutableData>> charValues;
+
+ QMap<QLowEnergyHandle, ValueRange> valueRanges;
+
+ std::deque<UpdateRequest> updateQueue;
+
+ ObjCScopedPointer<NSMutableSet> connectedCentrals;
+
+ PeripheralState state;
+ NSUInteger maxNotificationValueLength;
+}
+
+- (id)initWith:(LECBManagerNotifier *)aNotifier
+{
+ if (self = [super init]) {
+ Q_ASSERT(aNotifier);
+ notifier = aNotifier;
+ state = PeripheralState::idle;
+ nextServiceToAdd = {};
+ connectedCentrals.reset([[NSMutableSet alloc] init]);
+ maxNotificationValueLength = std::numeric_limits<NSUInteger>::max();
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [self detach];
+ [super dealloc];
+}
+
+- (QSharedPointer<QLowEnergyServicePrivate>)addService:(const QLowEnergyServiceData &)data
+{
+ using QLES = QLowEnergyService;
+
+ const auto nEntries = qt_countGATTEntries(data);
+ if (!nEntries || nEntries > std::numeric_limits<QLowEnergyHandle>::max() - lastHandle) {
+ qCCritical(QT_BT_DARWIN) << "addService: not enough handles";
+ return {};
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ const BOOL primary = data.type() == QLowEnergyServiceData::ServiceTypePrimary;
+ const auto cbUUID = cb_uuid(data.uuid());
+
+ const ObjCStrongReference<CBMutableService>
+ newCBService([[CBMutableService alloc] initWithType:cbUUID primary:primary],
+ false /*do not retain*/);
+
+ if (!newCBService) {
+ qCCritical(QT_BT_DARWIN) << "addService: failed to create CBMutableService";
+ return {};
+ }
+
+ auto newQtService = QSharedPointer<QLowEnergyServicePrivate>::create();
+ newQtService->state = QLowEnergyService::LocalService;
+ newQtService->uuid = data.uuid();
+ newQtService->type = primary ? QLES::PrimaryService : QLES::IncludedService;
+ newQtService->startHandle = ++lastHandle;
+ // Controller will be set by ... controller :)
+
+ [self addIncludedServices:data to:newCBService qtService:newQtService.data()];
+ [self addCharacteristicsAndDescriptors:data to:newCBService qtService:newQtService.data()];
+
+ services.push_back(newCBService);
+ serviceIndex[data.uuid()] = newCBService;
+
+ newQtService->endHandle = lastHandle;
+
+ return newQtService;
+}
+
+- (void) setParameters:(const QLowEnergyAdvertisingParameters &)parameters
+ data:(const QLowEnergyAdvertisingData &)data
+ scanResponse:(const QLowEnergyAdvertisingData &)scanResponse
+{
+ Q_UNUSED(parameters)
+
+ // This is the last method we call on the controller's thread
+ // before starting advertising on the Qt's LE queue.
+ // From Apple's docs:
+ /*
+ - (void)startAdvertising:(NSDictionary *)advertisementData
+
+ Advertises peripheral manager data.
+
+ * advertisementData
+
+ - An optional dictionary containing the data you want to advertise.
+ The possible keys of an advertisementData dictionary are detailed in CBCentralManagerDelegate
+ Protocol Reference. That said, only two of the keys are supported for peripheral manager objects:
+ CBAdvertisementDataLocalNameKey and CBAdvertisementDataServiceUUIDsKey.
+ */
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ advertisementData.reset([[NSMutableDictionary alloc] init]);
+ if (!advertisementData) {
+ qCWarning(QT_BT_DARWIN) << "setParameters: failed to allocate "
+ "NSMutableDictonary (advertisementData)";
+ return;
+ }
+
+ auto localName = scanResponse.localName();
+ if (!localName.size())
+ localName = data.localName();
+
+ if (localName.size()) {
+ [advertisementData setObject:localName.toNSString()
+ forKey:CBAdvertisementDataLocalNameKey];
+ }
+
+ if (!data.services().count() && !scanResponse.services().count())
+ return;
+
+ const ObjCScopedPointer<NSMutableArray> uuids([[NSMutableArray alloc] init]);
+ if (!uuids) {
+ qCWarning(QT_BT_DARWIN) << "setParameters: failed to allocate "
+ "NSMutableArray (services uuids)";
+ return;
+ }
+
+
+ for (const auto &qtUUID : data.services()) {
+ const auto cbUUID = cb_uuid(qtUUID);
+ if (cbUUID)
+ [uuids addObject:cbUUID];
+ }
+
+ for (const auto &qtUUID : scanResponse.services()) {
+ const auto cbUUID = cb_uuid(qtUUID);
+ if (cbUUID)
+ [uuids addObject:cbUUID];
+ }
+
+ if ([uuids count]) {
+ [advertisementData setObject:uuids
+ forKey:CBAdvertisementDataServiceUUIDsKey];
+ }
+}
+
+- (void)startAdvertising
+{
+ state = PeripheralState::waitingForPowerOn;
+ if (manager.data())
+ [manager setDelegate:nil];
+ manager.reset([[CBPeripheralManager alloc] initWithDelegate:self
+ queue:DarwinBluetooth::qt_LE_queue()]);
+}
+
+- (void)stopAdvertising
+{
+ [manager stopAdvertising];
+ state = PeripheralState::idle;
+}
+
+- (void)detach
+{
+ if (notifier) {
+ notifier->disconnect();
+ notifier->deleteLater();
+ notifier = nullptr;
+ }
+
+ if (state == PeripheralState::advertising) {
+ [manager stopAdvertising];
+ [manager setDelegate:nil];
+ state = PeripheralState::idle;
+ }
+}
+
+- (void)write:(const QByteArray &)value
+ charHandle:(QLowEnergyHandle)charHandle
+{
+ if (!notifier)
+ return;
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if (!charMap.contains(charHandle) || !valueRanges.contains(charHandle)) {
+ emit notifier->CBManagerError(QLowEnergyController::UnknownError);
+ return;
+ }
+
+ const auto & range = valueRanges[charHandle];
+ if (value.size() < int(range.first) || value.size() > int(range.second)
+#ifdef Q_OS_IOS
+ || value.size() > DarwinBluetooth::maxValueLength) {
+#else
+ ) {
+#endif
+ qCWarning(QT_BT_DARWIN) << "ignoring value of invalid length" << value.size();
+ return;
+ }
+
+ emit notifier->characteristicWritten(charHandle, value);
+
+ const auto nsData = mutable_data_from_bytearray(value);
+ charValues[charHandle] = nsData;
+ // We copy data here: sending update requests is async (see sendUpdateRequests),
+ // by the time we're allowed to actually send them, the data can change again
+ // and we'll send an 'out of order' value.
+ const ObjCStrongReference<NSData> copy([NSData dataWithData:nsData], true);
+ updateQueue.push_back(UpdateRequest{charHandle, copy});
+ [self sendUpdateRequests];
+}
+
+- (void) addServicesToPeripheral
+{
+ Q_ASSERT(manager.data());
+
+ if (nextServiceToAdd < services.size())
+ [manager addService:services[nextServiceToAdd++]];
+}
+
+// CBPeripheralManagerDelegate:
+
+- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
+{
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability-new"
+
+ if (peripheral != manager || !notifier)
+ return;
+
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
+ if (peripheral.state == CBManagerStatePoweredOn) {
+#else
+ if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
+#endif
+ // "Bluetooth is currently powered on and is available to use."
+ if (state == PeripheralState::waitingForPowerOn) {
+ [manager removeAllServices];
+ nextServiceToAdd = {};
+ state = PeripheralState::advertising;
+ [self addServicesToPeripheral];
+ }
+ return;
+ }
+
+ /*
+ "A state with a value lower than CBPeripheralManagerStatePoweredOn implies that
+ advertising has stopped and that any connected centrals have been disconnected."
+ */
+
+ [connectedCentrals removeAllObjects];
+
+ if (state == PeripheralState::advertising) {
+ state = PeripheralState::waitingForPowerOn;
+ } else if (state == PeripheralState::connected) {
+ state = PeripheralState::idle;
+ emit notifier->disconnected();
+ }
+
+ // The next four states are _below_ "powered off"; according to the docs:
+ /*
+ "In addition, the local database is cleared and all services must be
+ explicitly added again."
+ */
+
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_10_0) || QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
+ if (peripheral.state == CBManagerStateUnauthorized ||
+ peripheral.state == CBManagerStateUnsupported) {
+#else
+ if (peripheral.state == CBPeripheralManagerStateUnauthorized ||
+ peripheral.state == CBPeripheralManagerStateUnsupported) {
+#endif
+ emit notifier->LEnotSupported();
+ state = PeripheralState::idle;
+ }
+
+#pragma clang diagnostic pop
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ willRestoreState:(NSDictionary *)dict
+{
+ Q_UNUSED(peripheral)
+ Q_UNUSED(dict)
+ // NOOP atm.
+}
+
+- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
+ error:(NSError *)error
+{
+ if (peripheral != manager || !notifier)
+ return;
+
+ if (error) {
+ NSLog(@"failed to start advertising, error: %@", error);
+ state = PeripheralState::idle;
+ emit notifier->CBManagerError(QLowEnergyController::AdvertisingError);
+ }
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didAddService:(CBService *)service error:(NSError *)error
+{
+ Q_UNUSED(service)
+
+ if (peripheral != manager || !notifier)
+ return;
+
+ if (error) {
+ NSLog(@"failed to add a service, error: %@", error);
+ emit notifier->CBManagerError(QLowEnergyController::AdvertisingError);
+ state = PeripheralState::idle;
+ return;
+ }
+
+ if (nextServiceToAdd == services.size())
+ [manager startAdvertising:[advertisementData count] ? advertisementData.data() : nil];
+ else
+ [self addServicesToPeripheral];
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
+{
+ Q_UNUSED(characteristic)
+
+ if (peripheral != manager || !notifier)
+ return;
+
+ [self addConnectedCentral:central];
+
+ if (const auto handle = charMap.key(characteristic))
+ emit notifier->notificationEnabled(handle, true);
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic
+{
+ Q_UNUSED(characteristic)
+
+ if (peripheral != manager || !notifier)
+ return;
+
+ [self removeConnectedCentral:central];
+
+ if (![connectedCentrals count]) {
+ if (const auto handle = charMap.key(characteristic))
+ emit notifier->notificationEnabled(handle, false);
+ }
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveReadRequest:(CBATTRequest *)request
+{
+ if (peripheral != manager || !notifier)
+ return;
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ const auto handle = charMap.key(request.characteristic);
+ if (!handle || !charValues.contains(handle)) {
+ qCWarning(QT_BT_DARWIN) << "invalid read request, unknown characteristic";
+ [manager respondToRequest:request withResult:CBATTErrorInvalidHandle];
+ return;
+ }
+
+ const auto &value = charValues[handle];
+ if (request.offset > [value length]) {
+ qCWarning(QT_BT_DARWIN) << "invalid offset in a read request";
+ [manager respondToRequest:request withResult:CBATTErrorInvalidOffset];
+ return;
+ }
+
+ [self addConnectedCentral:request.central];
+
+ NSData *dataToSend = nil;
+ if (!request.offset) {
+ dataToSend = value;
+ } else {
+ dataToSend = [value subdataWithRange:
+ NSMakeRange(request.offset, [value length] - request.offset)];
+ }
+
+ request.value = dataToSend;
+ [manager respondToRequest:request withResult:CBATTErrorSuccess];
+}
+
+
+- (void)writeValueForCharacteristic:(QLowEnergyHandle) charHandle
+ withWriteRequest:(CBATTRequest *)request
+{
+ Q_ASSERT(charHandle);
+ Q_ASSERT(request);
+
+ Q_ASSERT(valueRanges.contains(charHandle));
+ const auto &range = valueRanges[charHandle];
+ Q_ASSERT(request.offset <= range.second
+ && request.value.length <= range.second - request.offset);
+
+ Q_ASSERT(charValues.contains(charHandle));
+ NSMutableData *const value = charValues[charHandle];
+ if (request.offset + request.value.length > value.length)
+ [value increaseLengthBy:request.offset + request.value.length - value.length];
+
+ [value replaceBytesInRange:NSMakeRange(request.offset, request.value.length)
+ withBytes:request.value.bytes];
+}
+
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveWriteRequests:(NSArray *)requests
+{
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if (peripheral != manager || !notifier) {
+ // Detached already.
+ return;
+ }
+
+ // We first test if all requests are valid
+ // since CoreBluetooth requires "all or none"
+ // and respond only _once_ to the first one.
+ for (CBATTRequest *request in requests) {
+ const auto status = [self validateWriteRequest:request];
+ if (status != CBATTErrorSuccess) {
+ [manager respondToRequest:[requests objectAtIndex:0]
+ withResult:status];
+ return;
+ }
+ }
+
+ std::map<QLowEnergyHandle, NSUInteger> updated;
+
+ for (CBATTRequest *request in requests) {
+ // Transition to 'connected' if needed.
+ [self addConnectedCentral:request.central];
+ const auto charHandle = charMap.key(request.characteristic);
+ const auto prevLen = updated[charHandle];
+ updated[charHandle] = std::max(request.offset + request.value.length,
+ prevLen);
+ [self writeValueForCharacteristic:charHandle withWriteRequest:request];
+ }
+
+ for (const auto pair : updated) {
+ const auto handle = pair.first;
+ NSMutableData *value = charValues[handle];
+ value.length = pair.second;
+ emit notifier->characteristicUpdated(handle, qt_bytearray(value));
+ const ObjCStrongReference<NSData> copy([NSData dataWithData:value],
+ true);
+ updateQueue.push_back(UpdateRequest{handle, copy});
+ }
+
+ if (requests.count) {
+ [manager respondToRequest:[requests objectAtIndex:0]
+ withResult:CBATTErrorSuccess];
+ }
+
+ [self sendUpdateRequests];
+}
+
+- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral
+{
+ if (peripheral != manager || !notifier) {
+ // Detached.
+ return;
+ }
+
+ [self sendUpdateRequests];
+}
+
+- (void)sendUpdateRequests
+{
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ while (updateQueue.size()) {
+ const auto &request = updateQueue.front();
+ if (charMap.contains(request.charHandle)) {
+ if ([connectedCentrals count]
+ && maxNotificationValueLength < [request.value length]) {
+ qCWarning(QT_BT_DARWIN) << "value of length" << [request.value length]
+ << "will possibly be truncated to"
+ << maxNotificationValueLength;
+ }
+ const BOOL res = [manager updateValue:request.value
+ forCharacteristic:static_cast<CBMutableCharacteristic *>(charMap[request.charHandle])
+ onSubscribedCentrals:nil];
+ if (!res) {
+ // Have to wait for the 'ManagerIsReadyToUpdate'.
+ break;
+ }
+ }
+
+ updateQueue.pop_front();
+ }
+}
+
+// Private API:
+
+- (void)addConnectedCentral:(CBCentral *)central
+{
+ if (!central)
+ return;
+
+ if (!notifier) {
+ // We were detached.
+ return;
+ }
+
+ maxNotificationValueLength = std::min(maxNotificationValueLength,
+ central.maximumUpdateValueLength);
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if (state == PeripheralState::advertising) {
+ state = PeripheralState::connected;
+ [manager stopAdvertising];
+ emit notifier->connected();
+ }
+
+ if (![connectedCentrals containsObject:central.identifier])
+ [connectedCentrals addObject:central.identifier];
+}
+
+- (void)removeConnectedCentral:(CBCentral *)central
+{
+ if (!notifier) {
+ // Detached.
+ return;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ if ([connectedCentrals containsObject:central.identifier])
+ [connectedCentrals removeObject:central.identifier];
+
+ if (state == PeripheralState::connected && ![connectedCentrals count]) {
+ state = PeripheralState::idle;
+ emit notifier->disconnected();
+ }
+
+ if (![connectedCentrals count])
+ maxNotificationValueLength = std::numeric_limits<NSUInteger>::max();
+}
+
+- (CBService *)findIncludedService:(const QBluetoothUuid &)qtUUID
+{
+ const auto it = serviceIndex.find(qtUUID);
+ if (it == serviceIndex.end())
+ return nil;
+
+ return it->second;
+}
+
+- (void)addIncludedServices:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService
+{
+ Q_ASSERT(cbService);
+ Q_ASSERT(qtService);
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ ObjCScopedPointer<NSMutableArray> included([[NSMutableArray alloc] init]);
+ if (!included) {
+ qCWarning(QT_BT_DARWIN) << "addIncludedSerivces: failed "
+ "to allocate NSMutableArray";
+ return;
+ }
+
+ for (auto includedService : data.includedServices()) {
+ if (CBService *cbs = [self findIncludedService:includedService->serviceUuid()]) {
+ [included addObject:cbs];
+ qtService->includedServices << includedService->serviceUuid();
+ ++lastHandle;
+ } else {
+ qCWarning(QT_BT_DARWIN) << "can not use" << includedService->serviceUuid()
+ << "as included, it has to be added first";
+ }
+ }
+
+ if ([included count])
+ cbService.includedServices = included;
+}
+
+- (void)addCharacteristicsAndDescriptors:(const QLowEnergyServiceData &)data
+ to:(CBMutableService *)cbService
+ qtService:(QLowEnergyServicePrivate *)qtService
+{
+ Q_ASSERT(cbService);
+ Q_ASSERT(qtService);
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ ObjCScopedPointer<NSMutableArray> newCBChars([[NSMutableArray alloc] init]);
+ if (!newCBChars) {
+ qCWarning(QT_BT_DARWIN) << "addCharacteristicsAndDescritptors: "
+ "failed to allocate NSMutableArray "
+ "(characteristics)";
+ return;
+ }
+
+ for (const auto &ch : data.characteristics()) {
+ if (!qt_validate_value_range(ch)) {
+ qCWarning(QT_BT_DARWIN) << "addCharacteristicsAndDescritptors: "
+ "invalid value size/min-max length";
+ continue;
+ }
+
+#ifdef Q_OS_IOS
+ if (ch.value().length() > DarwinBluetooth::maxValueLength) {
+ qCWarning(QT_BT_DARWIN) << "addCharacteristicsAndDescritptors: "
+ "value exceeds the maximal permitted "
+ "value length ("
+ << DarwinBluetooth::maxValueLength
+ << "octets) on the platform";
+ continue;
+ }
+#endif
+
+ const auto cbChar(create_characteristic(ch));
+ if (!cbChar) {
+ qCWarning(QT_BT_DARWIN) << "addCharacteristicsAndDescritptors: "
+ "failed to allocate a characteristic";
+ continue;
+ }
+
+ const auto nsData(mutable_data_from_bytearray(ch.value()));
+ if (!nsData) {
+ qCWarning(QT_BT_DARWIN) << "addCharacteristicsAndDescritptors: "
+ "addService: failed to allocate NSData (char value)";
+ continue;
+ }
+
+ [newCBChars addObject:cbChar];
+
+ const auto declHandle = ++lastHandle;
+ // CB part:
+ charMap[declHandle] = cbChar;
+ charValues[declHandle] = nsData;
+ valueRanges[declHandle] = ValueRange(ch.minimumValueLength(), ch.maximumValueLength());
+ // QT part:
+ QLowEnergyServicePrivate::CharData charData;
+ charData.valueHandle = declHandle;
+ charData.uuid = ch.uuid();
+ charData.properties = ch.properties();
+ charData.value = ch.value();
+
+ const ObjCScopedPointer<NSMutableArray> newCBDescs([[NSMutableArray alloc] init]);
+ if (!newCBDescs) {
+ qCWarning(QT_BT_DARWIN) << "addCharacteristicsAndDescritptors: "
+ "failed to allocate NSMutableArray "
+ "(descriptors)";
+ continue;
+ }
+
+ for (const auto &desc : ch.descriptors()) {
+ // CB part:
+ const auto cbDesc(create_descriptor(desc));
+ const auto descHandle = ++lastHandle;
+ if (cbDesc) {
+ // See comments in create_descriptor on
+ // why cbDesc can be nil.
+ [newCBDescs addObject:cbDesc];
+ }
+ // QT part:
+ QLowEnergyServicePrivate::DescData descData;
+ descData.uuid = desc.uuid();
+ descData.value = desc.value();
+ charData.descriptorList.insert(descHandle, descData);
+ }
+
+ if ([newCBDescs count])
+ cbChar.data().descriptors = newCBDescs.data(); // retains
+
+ qtService->characteristicList.insert(declHandle, charData);
+ }
+
+ if ([newCBChars count])
+ cbService.characteristics = newCBChars.data();
+}
+
+- (CBATTError)validateWriteRequest:(CBATTRequest *)request
+{
+ Q_ASSERT(request);
+
+ QT_BT_MAC_AUTORELEASEPOOL
+
+ const auto handle = charMap.key(request.characteristic);
+ if (!handle || !charValues.contains(handle))
+ return CBATTErrorInvalidHandle;
+
+ Q_ASSERT(valueRanges.contains(handle));
+
+ const auto &range = valueRanges[handle];
+ if (request.offset > range.second)
+ return CBATTErrorInvalidOffset;
+
+ if (request.value.length > range.second - request.offset)
+ return CBATTErrorInvalidAttributeValueLength;
+
+ return CBATTErrorSuccess;
+}
+
+@end
diff --git a/src/bluetooth/darwin/btperipheralmanager_p.h b/src/bluetooth/darwin/btperipheralmanager_p.h
new file mode 100644
index 00000000..e09165ac
--- /dev/null
+++ b/src/bluetooth/darwin/btperipheralmanager_p.h
@@ -0,0 +1,163 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTPERIPHERALMANAGER_P_H
+#define BTPERIPHERALMANAGER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of internal files. This header file may change from version to version
+// without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "btutility_p.h"
+
+#include "qlowenergyadvertisingparameters.h"
+#include "qlowenergyserviceprivate_p.h"
+#include "qbluetoothuuid.h"
+#include "qbluetooth.h"
+
+#include <QtCore/qsharedpointer.h>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qsysinfo.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qpair.h>
+#include <QtCore/qmap.h>
+
+#include <vector>
+#include <deque>
+#include <map>
+
+#include <Foundation/Foundation.h>
+
+#include <CoreBluetooth/CoreBluetooth.h>
+
+QT_BEGIN_NAMESPACE
+
+class QLowEnergyServiceData;
+
+namespace DarwinBluetooth
+{
+
+class LECBManagerNotifier;
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+
+// Exposing names in a header is ugly, but constant QT_PREPEND_NAMESPACE is even worse ...
+// After all, this header is to be included only in its own and controller's *.mm files.
+
+QT_USE_NAMESPACE
+
+using namespace DarwinBluetooth;
+
+
+template<class Type>
+using GenericLEMap = QMap<QLowEnergyHandle, Type>;
+
+enum class PeripheralState
+{
+ idle,
+ waitingForPowerOn,
+ advertising,
+ connected
+};
+
+struct UpdateRequest
+{
+ UpdateRequest() = default;
+ UpdateRequest(QLowEnergyHandle handle, const ObjCStrongReference<NSData> &val)
+ : charHandle(handle),
+ value(val)
+ {
+ }
+
+ QLowEnergyHandle charHandle = {};
+ ObjCStrongReference<NSData> value;
+};
+
+using ValueRange = QPair<NSUInteger, NSUInteger>;
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTPeripheralManager) : NSObject<CBPeripheralManagerDelegate>
+
+- (id)initWith:(LECBManagerNotifier *)notifier;
+- (void)dealloc;
+
+- (QSharedPointer<QLowEnergyServicePrivate>)addService:(const QLowEnergyServiceData &)data;
+- (void) setParameters:(const QLowEnergyAdvertisingParameters &)parameters
+ data:(const QLowEnergyAdvertisingData &)data
+ scanResponse:(const QLowEnergyAdvertisingData &)scanResponse;
+
+// To be executed on the Qt's special BTLE dispatch queue.
+- (void)startAdvertising;
+- (void)stopAdvertising;
+- (void)detach;
+
+- (void)write:(const QByteArray &)value
+ charHandle:(QLowEnergyHandle)charHandle;
+
+
+// CBPeripheralManagerDelegate's callbacks (BTLE queue).
+- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ willRestoreState:(NSDictionary *)dict;
+- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
+ error:(NSError *)error;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didAddService:(CBService *)service error:(NSError *)error;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
+ didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveReadRequest:(CBATTRequest *)request;
+- (void)peripheralManager:(CBPeripheralManager *)peripheral
+ didReceiveWriteRequests:(NSArray *)requests;
+- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral;
+
+@end
+
+#endif
diff --git a/src/bluetooth/darwin/btraii.mm b/src/bluetooth/darwin/btraii.mm
new file mode 100644
index 00000000..a1bf2a8d
--- /dev/null
+++ b/src/bluetooth/darwin/btraii.mm
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "btraii_p.h"
+
+#include <qdebug.h>
+
+#include <Foundation/Foundation.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+StrongReference::StrongReference(void *object, RetainPolicy policy)
+ : objCInstance(object)
+{
+ if (policy == RetainPolicy::doInitialRetain)
+ objCInstance = [getAs<NSObject>() retain];
+}
+
+StrongReference::StrongReference(const StrongReference &other)
+{
+ objCInstance = [other.getAs<NSObject>() retain];
+}
+
+StrongReference::StrongReference(StrongReference &&other)
+{
+ std::swap(objCInstance, other.objCInstance);
+}
+
+StrongReference::~StrongReference()
+{
+ [getAs<NSObject>() release];
+}
+
+StrongReference &StrongReference::operator = (const StrongReference &other) noexcept
+{
+ if (this != &other) {
+ [getAs<NSObject>() release];
+ objCInstance = [other.getAs<NSObject>() retain];
+ }
+
+ return *this;
+}
+
+StrongReference &StrongReference::operator = (StrongReference &&other) noexcept
+{
+ swap(other);
+ return *this;
+}
+
+void StrongReference::reset()
+{
+ [getAs<NSObject>() release];
+ objCInstance = nullptr;
+}
+
+void StrongReference::reset(void *obj, RetainPolicy policy)
+{
+ [getAs<NSObject>() release];
+ objCInstance = obj;
+
+ if (policy == RetainPolicy::doInitialRetain) {
+ auto newInstance = static_cast<NSObject *>(obj);
+ Q_ASSERT(newInstance);
+ objCInstance = [newInstance retain];
+ }
+}
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/darwin/btraii_p.h b/src/bluetooth/darwin/btraii_p.h
new file mode 100644
index 00000000..6053d63b
--- /dev/null
+++ b/src/bluetooth/darwin/btraii_p.h
@@ -0,0 +1,136 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTRAII_P_H
+#define BTRAII_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+enum class RetainPolicy
+{
+ noInitialRetain,
+ doInitialRetain
+};
+
+// The class StrongReference and its descendant ScopedGuard
+// are RAII classes dealing with raw pointers to NSObject class
+// and its descendants (and thus hiding Objective-C's retain/
+// release semantics). The header itself is meant to be included
+// into *.cpp files so it's a pure C++ code without any Objective-C
+// syntax. Thus it's a bit clunky - the type information is 'erased'
+// and has to be enforced by the code using these smart pointers.
+// That's because these types are Objective-C classes - thus require
+// Objective-C compiler to work. Member-function template 'getAs' is
+// a convenience shortcut giving the desired pointer type in
+// Objective-C++ files (*.mm).
+
+// TODO: on top of these classes I can build ObjCStrongReference (it's
+// now inside osxbtutils_p.h, a template class that does have type
+// information needed but works only in Objective-C++ environment.
+class StrongReference
+{
+public:
+ StrongReference() = default;
+ StrongReference(void *object, RetainPolicy policy);
+ StrongReference(const StrongReference &other);
+ StrongReference(StrongReference &&other);
+
+ ~StrongReference();
+
+ StrongReference &operator = (const StrongReference &other) noexcept;
+ StrongReference &operator = (StrongReference &&other) noexcept;
+
+ void swap(StrongReference &other) noexcept
+ {
+ std::swap(objCInstance, other.objCInstance);
+ }
+
+ void reset();
+ void reset(void *newInstance, RetainPolicy policy);
+
+ template<class ObjCType>
+ ObjCType *getAs() const
+ {
+ return static_cast<ObjCType *>(objCInstance);
+ }
+
+ operator bool() const
+ {
+ return !!objCInstance;
+ }
+
+private:
+ void *objCInstance = nullptr;
+};
+
+class ScopedPointer final : public StrongReference
+{
+public:
+ ScopedPointer() = default;
+ ScopedPointer(void *instance, RetainPolicy policy)
+ : StrongReference(instance, policy)
+ {
+ }
+
+private:
+ Q_DISABLE_COPY_MOVE(ScopedPointer)
+};
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+#endif // BTRAII_P_H
diff --git a/src/bluetooth/darwin/btrfcommchannel.mm b/src/bluetooth/darwin/btrfcommchannel.mm
new file mode 100644
index 00000000..0cac3c6d
--- /dev/null
+++ b/src/bluetooth/darwin/btrfcommchannel.mm
@@ -0,0 +1,261 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "btrfcommchannel_p.h"
+#include "qbluetoothaddress.h"
+#include "btdelegates_p.h"
+#include "btutility_p.h"
+
+QT_USE_NAMESPACE
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTRFCOMMChannel)
+{
+ QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *delegate;
+ IOBluetoothDevice *device;
+ IOBluetoothRFCOMMChannel *channel;
+ bool connected;
+}
+
+- (id)initWithDelegate:(DarwinBluetooth::ChannelDelegate *)aDelegate
+{
+ Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)");
+
+ if (self = [super init]) {
+ delegate = aDelegate;
+ device = nil;
+ channel = nil;
+ connected = false;
+ }
+
+ return self;
+}
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth::ChannelDelegate) *)aDelegate
+ channel:(IOBluetoothRFCOMMChannel *)aChannel
+{
+ // This type of channel does not require connect, it's created with
+ // already open channel.
+ Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)");
+ Q_ASSERT_X(aChannel, Q_FUNC_INFO, "invalid channel (nil)");
+
+ if (self = [super init]) {
+ delegate = aDelegate;
+ channel = [aChannel retain];
+ [channel setDelegate:self];
+ device = [[channel getDevice] retain];
+ connected = true;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ if (channel) {
+ [channel setDelegate:nil];
+ [channel closeChannel];
+ [channel release];
+ }
+
+ [device release];
+
+ [super dealloc];
+}
+
+// A single async connection (you can not reuse this object).
+- (IOReturn)connectAsyncToDevice:(const QBluetoothAddress &)address
+ withChannelID:(BluetoothRFCOMMChannelID)channelID
+{
+ if (address.isNull()) {
+ qCCritical(QT_BT_DARWIN) << "invalid peer address";
+ return kIOReturnNoDevice;
+ }
+
+ // Can never be called twice.
+ if (connected || device || channel) {
+ qCCritical(QT_BT_DARWIN) << "connection is already active";
+ return kIOReturnStillOpen;
+ }
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ const BluetoothDeviceAddress iobtAddress = DarwinBluetooth::iobluetooth_address(address);
+ device = [IOBluetoothDevice deviceWithAddress:&iobtAddress];
+ if (!device) { // TODO: do I always check this BTW??? Apple's docs say nothing about nil.
+ qCCritical(QT_BT_DARWIN) << "failed to create a device";
+ return kIOReturnNoDevice;
+ }
+
+ const IOReturn status = [device openRFCOMMChannelAsync:&channel
+ withChannelID:channelID delegate:self];
+ if (status != kIOReturnSuccess) {
+ qCCritical(QT_BT_DARWIN) << "failed to open L2CAP channel";
+ // device is still autoreleased.
+ device = nil;
+ return status;
+ }
+
+ [channel retain];// What if we're closed already?
+ [device retain];
+
+ return kIOReturnSuccess;
+}
+
+- (void)rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel
+ data:(void *)dataPointer length:(size_t)dataLength
+{
+ Q_UNUSED(rfcommChannel)
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+
+ // Not sure if it can ever happen and if
+ // assert is better.
+ if (!dataPointer || !dataLength)
+ return;
+
+ delegate->readChannelData(dataPointer, dataLength);
+}
+
+- (void)rfcommChannelOpenComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel
+ status:(IOReturn)error
+{
+ Q_UNUSED(rfcommChannel)
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+
+ if (error != kIOReturnSuccess) {
+ delegate->setChannelError(error);
+ } else {
+ connected = true;
+ delegate->channelOpenComplete();
+ }
+}
+
+- (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel
+{
+ Q_UNUSED(rfcommChannel)
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+ delegate->channelClosed();
+ connected = false;
+}
+
+- (void)rfcommChannelControlSignalsChanged:(IOBluetoothRFCOMMChannel*)rfcommChannel
+{
+ Q_UNUSED(rfcommChannel)
+}
+
+- (void)rfcommChannelFlowControlChanged:(IOBluetoothRFCOMMChannel*)rfcommChannel
+{
+ Q_UNUSED(rfcommChannel)
+}
+
+- (void)rfcommChannelWriteComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel
+ refcon:(void*)refcon status:(IOReturn)error
+{
+ Q_UNUSED(rfcommChannel)
+ Q_UNUSED(refcon)
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+
+ if (error != kIOReturnSuccess)
+ delegate->setChannelError(error);
+ else
+ delegate->writeComplete();
+}
+
+- (void)rfcommChannelQueueSpaceAvailable:(IOBluetoothRFCOMMChannel*)rfcommChannel
+{
+ Q_UNUSED(rfcommChannel)
+}
+
+- (BluetoothRFCOMMChannelID)getChannelID
+{
+ if (channel)
+ return [channel getChannelID];
+
+ return 0;
+}
+
+- (BluetoothDeviceAddress)peerAddress
+{
+ const BluetoothDeviceAddress *const addr = device ? [device getAddress]
+ : nullptr;
+ if (addr)
+ return *addr;
+
+ return BluetoothDeviceAddress();
+}
+
+- (NSString *)peerName
+{
+ if (device)
+ return device.name;
+
+ return nil;
+}
+
+- (BluetoothRFCOMMMTU)getMTU
+{
+ if (channel)
+ return [channel getMTU];
+
+ return 0;
+}
+
+- (IOReturn) writeSync:(void*)data length:(UInt16)length
+{
+ Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)");
+ Q_ASSERT_X(length, Q_FUNC_INFO, "invalid data size");
+ Q_ASSERT_X(connected && channel, Q_FUNC_INFO, "invalid RFCOMM channel");
+
+ return [channel writeSync:data length:length];
+}
+
+- (IOReturn) writeAsync:(void*)data length:(UInt16)length
+{
+ Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)");
+ Q_ASSERT_X(length, Q_FUNC_INFO, "invalid data size");
+ Q_ASSERT_X(connected && channel, Q_FUNC_INFO, "invalid RFCOMM channel");
+
+ return [channel writeAsync:data length:length refcon:nullptr];
+}
+
+
+@end
diff --git a/src/bluetooth/darwin/btrfcommchannel_p.h b/src/bluetooth/darwin/btrfcommchannel_p.h
new file mode 100644
index 00000000..25299092
--- /dev/null
+++ b/src/bluetooth/darwin/btrfcommchannel_p.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTRFCOMMCHANNEL_P_H
+#define BTRFCOMMCHANNEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+
+#include <IOBluetooth/IOBluetooth.h>
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothAddress;
+
+namespace DarwinBluetooth {
+
+class ChannelDelegate;
+
+}
+
+QT_END_NAMESPACE
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTRFCOMMChannel) : NSObject<IOBluetoothRFCOMMChannelDelegate>
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *)aDelegate;
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth)::ChannelDelegate *)aDelegate
+ channel:(IOBluetoothRFCOMMChannel *)aChannel;
+
+- (void)dealloc;
+
+// A single async connection (can connect only once).
+- (IOReturn)connectAsyncToDevice:(const QT_PREPEND_NAMESPACE(QBluetoothAddress) &)address
+ withChannelID:(BluetoothRFCOMMChannelID)channelID;
+
+- (void)rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel
+ data:(void *)dataPointer length:(size_t)dataLength;
+- (void)rfcommChannelOpenComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel
+ status:(IOReturn)error;
+- (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel;
+- (void)rfcommChannelControlSignalsChanged:(IOBluetoothRFCOMMChannel*)rfcommChannel;
+- (void)rfcommChannelFlowControlChanged:(IOBluetoothRFCOMMChannel*)rfcommChannel;
+- (void)rfcommChannelWriteComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel
+ refcon:(void*)refcon status:(IOReturn)error;
+- (void)rfcommChannelQueueSpaceAvailable:(IOBluetoothRFCOMMChannel*)rfcommChannel;
+
+//
+- (BluetoothRFCOMMChannelID)getChannelID;
+- (BluetoothDeviceAddress)peerAddress;
+- (NSString *)peerName;
+
+- (BluetoothRFCOMMMTU)getMTU;
+
+- (IOReturn) writeSync:(void*)data length:(UInt16)length;
+- (IOReturn) writeAsync:(void*)data length:(UInt16)length;
+
+
+@end
+
+#endif
diff --git a/src/bluetooth/darwin/btsdpinquiry.mm b/src/bluetooth/darwin/btsdpinquiry.mm
new file mode 100644
index 00000000..336d7541
--- /dev/null
+++ b/src/bluetooth/darwin/btsdpinquiry.mm
@@ -0,0 +1,329 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbluetoothserviceinfo.h"
+#include "btsdpinquiry_p.h"
+#include "qbluetoothuuid.h"
+#include "btdelegates_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qvariant.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+namespace {
+
+QBluetoothUuid sdp_element_to_uuid(IOBluetoothSDPDataElement *element)
+{
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if (!element || [element getTypeDescriptor] != kBluetoothSDPDataElementTypeUUID)
+ return {};
+
+ return qt_uuid([[element getUUIDValue] getUUIDWithLength:16]);
+}
+
+QBluetoothUuid extract_service_ID(IOBluetoothSDPServiceRecord *record)
+{
+ Q_ASSERT(record);
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ return sdp_element_to_uuid([record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceID]);
+}
+
+QVector<QBluetoothUuid> extract_service_class_ID_list(IOBluetoothSDPServiceRecord *record)
+{
+ Q_ASSERT(record);
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ IOBluetoothSDPDataElement *const idList = [record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceClassIDList];
+ if (!idList || [idList getTypeDescriptor] != kBluetoothSDPDataElementTypeDataElementSequence)
+ return {};
+
+ QVector<QBluetoothUuid> uuids;
+ NSArray *const arr = [idList getArrayValue];
+ for (IOBluetoothSDPDataElement *dataElement in arr) {
+ const auto qtUuid = sdp_element_to_uuid(dataElement);
+ if (!qtUuid.isNull())
+ uuids.push_back(qtUuid);
+ }
+
+ return uuids;
+}
+
+QBluetoothServiceInfo::Sequence service_class_ID_list_to_sequence(const QVector<QBluetoothUuid> &uuids)
+{
+ if (uuids.isEmpty())
+ return {};
+
+ QBluetoothServiceInfo::Sequence sequence;
+ for (const auto &uuid : uuids) {
+ Q_ASSERT(!uuid.isNull());
+ sequence.append(QVariant::fromValue(uuid));
+ }
+
+ return sequence;
+}
+
+} // unnamed namespace
+
+QVariant extract_attribute_value(IOBluetoothSDPDataElement *dataElement)
+{
+ Q_ASSERT_X(dataElement, Q_FUNC_INFO, "invalid data element (nil)");
+
+ // TODO: error handling and diagnostic messages.
+
+ // All "temporary" obj-c objects are autoreleased.
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ const BluetoothSDPDataElementTypeDescriptor typeDescriptor = [dataElement getTypeDescriptor];
+
+ switch (typeDescriptor) {
+ case kBluetoothSDPDataElementTypeNil:
+ break;
+ case kBluetoothSDPDataElementTypeUnsignedInt:
+ return [[dataElement getNumberValue] unsignedIntValue];
+ case kBluetoothSDPDataElementTypeSignedInt:
+ return [[dataElement getNumberValue] intValue];
+ case kBluetoothSDPDataElementTypeUUID:
+ return QVariant::fromValue(sdp_element_to_uuid(dataElement));
+ case kBluetoothSDPDataElementTypeString:
+ case kBluetoothSDPDataElementTypeURL:
+ return QString::fromNSString([dataElement getStringValue]);
+ case kBluetoothSDPDataElementTypeBoolean:
+ return [[dataElement getNumberValue] boolValue];
+ case kBluetoothSDPDataElementTypeDataElementSequence:
+ case kBluetoothSDPDataElementTypeDataElementAlternative: // TODO: check this!
+ {
+ QBluetoothServiceInfo::Sequence sequence;
+ NSArray *const arr = [dataElement getArrayValue];
+ for (IOBluetoothSDPDataElement *element in arr)
+ sequence.append(extract_attribute_value(element));
+
+ return QVariant::fromValue(sequence);
+ }
+ break;// Coding style.
+ default:;
+ }
+
+ return QVariant();
+}
+
+void extract_service_record(IOBluetoothSDPServiceRecord *record, QBluetoothServiceInfo &serviceInfo)
+{
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if (!record)
+ return;
+
+ NSDictionary *const attributes = record.attributes;
+ NSEnumerator *const keys = attributes.keyEnumerator;
+ for (NSNumber *key in keys) {
+ const quint16 attributeID = [key unsignedShortValue];
+ IOBluetoothSDPDataElement *const element = [attributes objectForKey:key];
+ const QVariant attributeValue = DarwinBluetooth::extract_attribute_value(element);
+ serviceInfo.setAttribute(attributeID, attributeValue);
+ }
+
+ const QBluetoothUuid serviceUuid = extract_service_ID(record);
+ if (!serviceUuid.isNull())
+ serviceInfo.setServiceUuid(serviceUuid);
+
+ const QVector<QBluetoothUuid> uuids(extract_service_class_ID_list(record));
+ const auto sequence = service_class_ID_list_to_sequence(uuids);
+ if (!sequence.isEmpty())
+ serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, sequence);
+}
+
+QVector<QBluetoothUuid> extract_services_uuids(IOBluetoothDevice *device)
+{
+ QVector<QBluetoothUuid> uuids;
+
+ // All "temporary" obj-c objects are autoreleased.
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if (!device || !device.services)
+ return uuids;
+
+ NSArray * const records = device.services;
+ for (IOBluetoothSDPServiceRecord *record in records) {
+ const QBluetoothUuid serviceID = extract_service_ID(record);
+ if (!serviceID.isNull())
+ uuids.push_back(serviceID);
+
+ const QVector<QBluetoothUuid> idList(extract_service_class_ID_list(record));
+ if (idList.size())
+ uuids.append(idList);
+ }
+
+ return uuids;
+}
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+using namespace DarwinBluetooth;
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTSDPInquiry)
+{
+ QT_PREPEND_NAMESPACE(DarwinBluetooth::SDPInquiryDelegate) *delegate;
+ IOBluetoothDevice *device;
+ bool isActive;
+}
+
+- (id)initWithDelegate:(DarwinBluetooth::SDPInquiryDelegate *)aDelegate
+{
+ Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)");
+
+ if (self = [super init]) {
+ delegate = aDelegate;
+ isActive = false;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ //[device closeConnection]; //??? - synchronous, "In the future this API will be changed to allow asynchronous operation."
+ [device release];
+ [super dealloc];
+}
+
+- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address
+{
+ Q_ASSERT_X(!isActive, Q_FUNC_INFO, "SDP query in progress");
+
+ QList<QBluetoothUuid> emptyFilter;
+ return [self performSDPQueryWithDevice:address filters:emptyFilter];
+}
+
+- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address
+ filters:(const QList<QBluetoothUuid> &)qtFilters
+{
+ Q_ASSERT_X(!isActive, Q_FUNC_INFO, "SDP query in progress");
+ Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid target device address");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ // We first try to allocate "filters":
+ ObjCScopedPointer<NSMutableArray> array;
+ if (qtFilters.size()) {
+ array.reset([[NSMutableArray alloc] init]);
+ if (!array) {
+ qCCritical(QT_BT_DARWIN) << "failed to allocate an uuid filter";
+ return kIOReturnError;
+ }
+
+ for (const QBluetoothUuid &qUuid : qtFilters) {
+ ObjCStrongReference<IOBluetoothSDPUUID> uuid(iobluetooth_uuid(qUuid));
+ if (uuid)
+ [array addObject:uuid];
+ }
+
+ if (int([array count]) != qtFilters.size()) {
+ qCCritical(QT_BT_DARWIN) << "failed to create an uuid filter";
+ return kIOReturnError;
+ }
+ }
+
+ const BluetoothDeviceAddress iobtAddress(iobluetooth_address(address));
+ ObjCScopedPointer<IOBluetoothDevice> newDevice([[IOBluetoothDevice deviceWithAddress:&iobtAddress] retain]);
+ if (!newDevice) {
+ qCCritical(QT_BT_DARWIN) << "failed to create an IOBluetoothDevice object";
+ return kIOReturnError;
+ }
+
+ ObjCScopedPointer<IOBluetoothDevice> oldDevice(device);
+ device = newDevice.data();
+
+ IOReturn result = kIOReturnSuccess;
+ if (qtFilters.size())
+ result = [device performSDPQuery:self uuids:array];
+ else
+ result = [device performSDPQuery:self];
+
+ if (result != kIOReturnSuccess) {
+ qCCritical(QT_BT_DARWIN) << "failed to start an SDP query";
+ device = oldDevice.take();
+ } else {
+ isActive = true;
+ newDevice.take();
+ }
+
+ return result;
+}
+
+- (void)stopSDPQuery
+{
+ // There is no API to stop it,
+ // but there is a 'stop' member-function in Qt and
+ // after it's called sdpQueryComplete must be somehow ignored.
+
+ [device release];
+ device = nil;
+}
+
+- (void)sdpQueryComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status
+{
+ // Can happen - there is no legal way to cancel an SDP query,
+ // after the 'reset' device can never be
+ // the same as the cancelled one.
+ if (device != aDevice)
+ return;
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+
+ isActive = false;
+
+ if (status != kIOReturnSuccess)
+ delegate->SDPInquiryError(aDevice, status);
+ else
+ delegate->SDPInquiryFinished(aDevice);
+}
+
+@end
diff --git a/src/bluetooth/darwin/btsdpinquiry_p.h b/src/bluetooth/darwin/btsdpinquiry_p.h
new file mode 100644
index 00000000..6cce5703
--- /dev/null
+++ b/src/bluetooth/darwin/btsdpinquiry_p.h
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTSDPINQUIRY_H
+#define BTSDPINQUIRY_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qbluetoothaddress.h"
+#include "qbluetoothuuid.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qvector.h>
+
+#include <Foundation/Foundation.h>
+
+#include <IOBluetooth/IOBluetooth.h>
+
+@class QT_MANGLE_NAMESPACE(DarwinBTSDPInquiry);
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothServiceInfo;
+class QVariant;
+
+namespace DarwinBluetooth {
+
+class SDPInquiryDelegate;
+
+void extract_service_record(IOBluetoothSDPServiceRecord *record, QBluetoothServiceInfo &serviceInfo);
+QVariant extract_attribute_value(IOBluetoothSDPDataElement *dataElement);
+QVector<QBluetoothUuid> extract_services_uuids(IOBluetoothDevice *device);
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTSDPInquiry) : NSObject
+
+- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(DarwinBluetooth::SDPInquiryDelegate) *)aDelegate;
+- (void)dealloc;
+
+- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address;
+- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address
+ filters:(const QList<QBluetoothUuid> &)filters;
+
+- (void)stopSDPQuery;
+
+- (void)sdpQueryComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status;
+
+@end
+
+#endif
diff --git a/src/bluetooth/darwin/btservicerecord.mm b/src/bluetooth/darwin/btservicerecord.mm
new file mode 100644
index 00000000..82067558
--- /dev/null
+++ b/src/bluetooth/darwin/btservicerecord.mm
@@ -0,0 +1,458 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbluetoothserviceinfo.h"
+#include "btservicerecord_p.h"
+
+#include <QtCore/qvariant.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qurl.h>
+
+#include <IOBluetooth/IOBluetooth.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+//
+// Returns a dictionary containing the Bluetooth RFCOMM service definition
+// corresponding to the provided |uuid| and |options|.
+namespace {
+
+typedef ObjCStrongReference<NSMutableDictionary> Dictionary;
+typedef ObjCStrongReference<IOBluetoothSDPUUID> SDPUUid;
+typedef ObjCStrongReference<NSNumber> Number;
+typedef QBluetoothServiceInfo QSInfo;
+typedef QSInfo::Sequence Sequence;
+typedef QSInfo::AttributeId AttributeId;
+
+}
+
+#if 0
+QBluetoothUuid profile_uuid(const QBluetoothServiceInfo &serviceInfo)
+{
+ // Strategy to pick service uuid:
+ // 1.) use serviceUuid()
+ // 2.) use first custom uuid if available
+ // 3.) use first service class uuid
+ QBluetoothUuid serviceUuid(serviceInfo.serviceUuid());
+
+ if (serviceUuid.isNull()) {
+ const QVariant var(serviceInfo.attribute(QBluetoothServiceInfo::ServiceClassIds));
+ if (var.isValid()) {
+ const Sequence seq(var.value<Sequence>());
+
+ for (int i = 0; i < seq.count(); ++i) {
+ QBluetoothUuid uuid(seq.at(i).value<QBluetoothUuid>());
+ if (uuid.isNull())
+ continue;
+
+ const int size = uuid.minimumSize();
+ if (size == 2 || size == 4) { // Base UUID derived
+ if (serviceUuid.isNull())
+ serviceUuid = uuid;
+ } else {
+ return uuid;
+ }
+ }
+ }
+ }
+
+ return serviceUuid;
+}
+#endif
+
+template<class IntType>
+Number variant_to_nsnumber(const QVariant &);
+
+template<>
+Number variant_to_nsnumber<unsigned char>(const QVariant &var)
+{
+ return Number([NSNumber numberWithUnsignedChar:var.value<unsigned char>()], true);
+}
+
+template<>
+Number variant_to_nsnumber<unsigned short>(const QVariant &var)
+{
+ return Number([NSNumber numberWithUnsignedShort:var.value<unsigned short>()], true);
+}
+
+template<>
+Number variant_to_nsnumber<unsigned>(const QVariant &var)
+{
+ return Number([NSNumber numberWithUnsignedInt:var.value<unsigned>()], true);
+}
+
+template<>
+Number variant_to_nsnumber<char>(const QVariant &var)
+{
+ return Number([NSNumber numberWithChar:var.value<char>()], true);
+}
+
+template<>
+Number variant_to_nsnumber<short>(const QVariant &var)
+{
+ return Number([NSNumber numberWithShort:var.value<short>()], true);
+}
+
+template<>
+Number variant_to_nsnumber<int>(const QVariant &var)
+{
+ return Number([NSNumber numberWithInt:var.value<int>()], true);
+}
+
+template<class ValueType>
+void add_attribute(const QVariant &var, AttributeId key, Dictionary dict)
+{
+ Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");
+
+ if (!var.canConvert<ValueType>())
+ return;
+
+ const Number num(variant_to_nsnumber<ValueType>(var));
+ [dict setObject:num forKey:[NSString stringWithFormat:@"%d", int(key)]];
+}
+
+template<>
+void add_attribute<QString>(const QVariant &var, AttributeId key, Dictionary dict)
+{
+ Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");
+
+ if (!var.canConvert<QString>())
+ return;
+
+ const QString string(var.value<QString>());
+ if (string.length()) {
+ if (NSString *const nsString = string.toNSString())
+ [dict setObject:nsString forKey:[NSString stringWithFormat:@"%d", int(key)]];
+ }
+}
+
+template<>
+void add_attribute<QBluetoothUuid>(const QVariant &var, AttributeId key, Dictionary dict)
+{
+ Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");
+
+ if (!var.canConvert<QBluetoothUuid>())
+ return;
+
+ SDPUUid ioUUID(iobluetooth_uuid(var.value<QBluetoothUuid>()));
+ [dict setObject:ioUUID forKey:[NSString stringWithFormat:@"%d", int(key)]];
+}
+
+template<>
+void add_attribute<QUrl>(const QVariant &var, AttributeId key, Dictionary dict)
+{
+ Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");
+
+ if (!var.canConvert<QUrl>())
+ return;
+
+ Q_UNUSED(var)
+ Q_UNUSED(key)
+ Q_UNUSED(dict)
+
+ // TODO: not clear how should I pass an url in a dictionary, NSURL does not work.
+}
+
+template<class ValueType>
+void add_attribute(const QVariant &var, NSMutableArray *list);
+
+template<class ValueType>
+void add_attribute(const QVariant &var, NSMutableArray *list)
+{
+ Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");
+
+ if (!var.canConvert<ValueType>())
+ return;
+
+ const Number num(variant_to_nsnumber<ValueType>(var));
+ [list addObject:num];
+}
+
+template<>
+void add_attribute<QString>(const QVariant &var, NSMutableArray *list)
+{
+ Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");
+
+ if (!var.canConvert<QString>())
+ return;
+
+ const QString string(var.value<QString>());
+ if (string.length()) {
+ if (NSString *const nsString = string.toNSString())
+ [list addObject:nsString];
+ }
+}
+
+template<>
+void add_attribute<QBluetoothUuid>(const QVariant &var, NSMutableArray *list)
+{
+ Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");
+
+ if (!var.canConvert<QBluetoothUuid>())
+ return;
+
+ SDPUUid ioUUID(iobluetooth_uuid(var.value<QBluetoothUuid>()));
+ [list addObject:ioUUID];
+}
+
+template<>
+void add_attribute<QUrl>(const QVariant &var, NSMutableArray *list)
+{
+ Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");
+
+ if (!var.canConvert<QUrl>())
+ return;
+
+ Q_UNUSED(var)
+ Q_UNUSED(list)
+ // TODO: not clear how should I pass an url in a dictionary, NSURL does not work.
+}
+
+void add_rfcomm_protocol_descriptor_list(uint16 channelID, Dictionary dict)
+{
+ Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ // Objective-C has literals (for arrays and dictionaries), but it will not compile
+ // on 10.7 or below, so quite a lot of code here.
+
+ NSMutableArray *const descriptorList = [NSMutableArray array];
+
+ IOBluetoothSDPUUID *const l2capUUID = [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16L2CAP];
+ NSArray *const l2capList = [NSArray arrayWithObject:l2capUUID];
+
+ [descriptorList addObject:l2capList];
+ //
+ IOBluetoothSDPUUID *const rfcommUUID = [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16RFCOMM];
+ NSMutableDictionary *const rfcommDict = [NSMutableDictionary dictionary];
+ [rfcommDict setObject:[NSNumber numberWithInt:1] forKey:@"DataElementType"];
+ [rfcommDict setObject:[NSNumber numberWithInt:1] forKey:@"DataElementSize"];
+ [rfcommDict setObject:[NSNumber numberWithInt:channelID] forKey:@"DataElementValue"];
+ //
+ NSMutableArray *const rfcommList = [NSMutableArray array];
+ [rfcommList addObject:rfcommUUID];
+ [rfcommList addObject:rfcommDict];
+
+ [descriptorList addObject:rfcommList];
+ [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%d",
+ kBluetoothSDPAttributeIdentifierProtocolDescriptorList]];
+}
+
+void add_l2cap_protocol_descriptor_list(uint16 psm, Dictionary dict)
+{
+ Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ // Objective-C has literals (for arrays and dictionaries), but it will not compile
+ // on 10.7 or below, so quite a lot of code here.
+
+ NSMutableArray *const descriptorList = [NSMutableArray array];
+ NSMutableArray *const l2capList = [NSMutableArray array];
+
+ IOBluetoothSDPUUID *const l2capUUID = [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16L2CAP];
+ [l2capList addObject:l2capUUID];
+
+ NSMutableDictionary *const l2capDict = [NSMutableDictionary dictionary];
+ [l2capDict setObject:[NSNumber numberWithInt:1] forKey:@"DataElementType"];
+ [l2capDict setObject:[NSNumber numberWithInt:2] forKey:@"DataElementSize"];
+ [l2capDict setObject:[NSNumber numberWithInt:psm] forKey:@"DataElementValue"];
+ [l2capList addObject:l2capDict];
+
+ [descriptorList addObject:l2capList];
+ [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%d",
+ kBluetoothSDPAttributeIdentifierProtocolDescriptorList]];
+}
+
+bool add_attribute(const QVariant &var, AttributeId key, NSMutableArray *list)
+{
+ Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");
+
+ if (var.canConvert<Sequence>())
+ return false;
+
+ if (var.canConvert<QString>()) {
+ //ServiceName, ServiceDescription, ServiceProvider.
+ add_attribute<QString>(var, list);
+ } else if (var.canConvert<QBluetoothUuid>()) {
+ add_attribute<QBluetoothUuid>(var, list);
+ } else {
+ // Here we need 'key' to understand the type.
+ // We can have different integer types actually, so I have to check
+ // the 'key' to be sure the conversion is reasonable.
+ switch (key) {
+ case QSInfo::ServiceRecordHandle:
+ case QSInfo::ServiceRecordState:
+ case QSInfo::ServiceInfoTimeToLive:
+ add_attribute<unsigned>(var, list);
+ break;
+ case QSInfo::ServiceAvailability:
+ add_attribute<unsigned char>(var, list);
+ break;
+ case QSInfo::IconUrl:
+ case QSInfo::DocumentationUrl:
+ case QSInfo::ClientExecutableUrl:
+ add_attribute<QUrl>(var, list);
+ break;
+ default:;
+ }
+ }
+
+ return true;
+}
+
+bool add_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeId key, Dictionary dict)
+{
+ Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dict (nil)");
+
+ const QVariant var(serviceInfo.attribute(key));
+ if (var.canConvert<Sequence>())
+ return false;
+
+ if (var.canConvert<QString>()) {
+ //ServiceName, ServiceDescription, ServiceProvider.
+ add_attribute<QString>(var, key, dict);
+ } else if (var.canConvert<QBluetoothUuid>()) {
+ add_attribute<QBluetoothUuid>(serviceInfo.attribute(key), key, dict);
+ } else {
+ // We can have different integer types actually, so I have to check
+ // the 'key' to be sure the conversion is reasonable.
+ switch (key) {
+ case QSInfo::ServiceRecordHandle:
+ case QSInfo::ServiceRecordState:
+ case QSInfo::ServiceInfoTimeToLive:
+ add_attribute<unsigned>(serviceInfo.attribute(key), key, dict);
+ break;
+ case QSInfo::ServiceAvailability:
+ add_attribute<unsigned char>(serviceInfo.attribute(key), key, dict);
+ break;
+ case QSInfo::IconUrl:
+ case QSInfo::DocumentationUrl:
+ case QSInfo::ClientExecutableUrl:
+ add_attribute<QUrl>(serviceInfo.attribute(key), key, dict);
+ break;
+ default:;
+ }
+ }
+
+ return true;
+}
+
+bool add_sequence_attribute(const QVariant &var, AttributeId key, NSMutableArray *list)
+{
+ // Add a "nested" sequence.
+ Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");
+
+ if (var.isNull() || !var.canConvert<Sequence>())
+ return false;
+
+ const Sequence sequence(var.value<Sequence>());
+ for (const QVariant &var : sequence) {
+ if (var.canConvert<Sequence>()) {
+ NSMutableArray *const nested = [NSMutableArray array];
+ add_sequence_attribute(var, key, nested);
+ [list addObject:nested];
+ } else {
+ add_attribute(var, key, list);
+ }
+ }
+
+ return true;
+}
+
+bool add_sequence_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeId key, Dictionary dict)
+{
+ Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");
+
+ const QVariant &var(serviceInfo.attribute(key));
+ if (var.isNull() || !var.canConvert<Sequence>())
+ return false;
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ NSMutableArray *const list = [NSMutableArray array];
+ const Sequence sequence(var.value<Sequence>());
+ for (const QVariant &element : sequence) {
+ if (!add_sequence_attribute(element, key, list))
+ add_attribute(element, key, list);
+ }
+ [dict setObject:list forKey:[NSString stringWithFormat:@"%d", int(key)]];
+
+ return true;
+}
+
+Dictionary iobluetooth_service_dictionary(const QBluetoothServiceInfo &serviceInfo)
+{
+ Dictionary dict;
+
+ if (serviceInfo.socketProtocol() == QBluetoothServiceInfo::UnknownProtocol)
+ return dict;
+
+ const QList<quint16> attributeIds(serviceInfo.attributes());
+ if (!attributeIds.size())
+ return dict;
+
+ dict.reset([[NSMutableDictionary alloc] init]);
+
+ for (quint16 key : attributeIds) {
+ if (key == QSInfo::ProtocolDescriptorList) // We handle it in a special way.
+ continue;
+ // TODO: check if non-sequence QVariant still must be
+ // converted into NSArray for some attribute ID.
+ if (!add_sequence_attribute(serviceInfo, AttributeId(key), dict))
+ add_attribute(serviceInfo, AttributeId(key), dict);
+ }
+
+ if (serviceInfo.socketProtocol() == QBluetoothServiceInfo::L2capProtocol) {
+ add_l2cap_protocol_descriptor_list(serviceInfo.protocolServiceMultiplexer(),
+ dict);
+ } else {
+ add_rfcomm_protocol_descriptor_list(serviceInfo.serverChannel(), dict);
+ }
+
+ return dict;
+}
+
+}
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/darwin/btservicerecord_p.h b/src/bluetooth/darwin/btservicerecord_p.h
new file mode 100644
index 00000000..6b5b0374
--- /dev/null
+++ b/src/bluetooth/darwin/btservicerecord_p.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTSERVICERECORD_P_H
+#define BTSERVICERECORD_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "btutility_p.h"
+
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothServiceInfo;
+
+namespace DarwinBluetooth {
+
+ObjCStrongReference<NSMutableDictionary> iobluetooth_service_dictionary(const QBluetoothServiceInfo &serviceInfo);
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/bluetooth/darwin/btsocketlistener.mm b/src/bluetooth/darwin/btsocketlistener.mm
new file mode 100644
index 00000000..505fec14
--- /dev/null
+++ b/src/bluetooth/darwin/btsocketlistener.mm
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "btsocketlistener_p.h"
+#include "btdelegates_p.h"
+#include "btutility_p.h"
+
+#include <QtCore/qdebug.h>
+
+QT_USE_NAMESPACE
+
+@implementation QT_MANGLE_NAMESPACE(DarwinBTSocketListener)
+{
+ IOBluetoothUserNotification *connectionNotification;
+ QT_PREPEND_NAMESPACE(DarwinBluetooth::SocketListener) *delegate;
+ quint16 port;
+}
+
+- (id)initWithListener:(DarwinBluetooth::SocketListener *)aDelegate
+{
+ Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)");
+ if (self = [super init]) {
+ connectionNotification = nil;
+ delegate = aDelegate;
+ port = 0;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [connectionNotification unregister];
+ [connectionNotification release];
+
+ [super dealloc];
+}
+
+- (bool)listenRFCOMMConnectionsWithChannelID:(BluetoothRFCOMMChannelID)channelID
+{
+ Q_ASSERT_X(!connectionNotification, Q_FUNC_INFO, "already listening");
+
+ connectionNotification = [IOBluetoothRFCOMMChannel registerForChannelOpenNotifications:self
+ selector:@selector(rfcommOpenNotification:channel:)
+ withChannelID:channelID
+ direction:kIOBluetoothUserNotificationChannelDirectionIncoming];
+ connectionNotification = [connectionNotification retain];
+ if (connectionNotification)
+ port = channelID;
+
+ return connectionNotification;
+}
+
+- (bool)listenL2CAPConnectionsWithPSM:(BluetoothL2CAPPSM)psm
+{
+ Q_ASSERT_X(!connectionNotification, Q_FUNC_INFO, "already listening");
+
+ connectionNotification = [IOBluetoothL2CAPChannel registerForChannelOpenNotifications:self
+ selector:@selector(l2capOpenNotification:channel:)
+ withPSM:psm
+ direction:kIOBluetoothUserNotificationChannelDirectionIncoming];
+ connectionNotification = [connectionNotification retain];
+ if (connectionNotification)
+ port = psm;
+
+ return connectionNotification;
+}
+
+- (void)rfcommOpenNotification:(IOBluetoothUserNotification *)notification
+ channel:(IOBluetoothRFCOMMChannel *)newChannel
+{
+ Q_UNUSED(notification)
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+ delegate->openNotifyRFCOMM(newChannel);
+}
+
+- (void)l2capOpenNotification:(IOBluetoothUserNotification *)notification
+ channel:(IOBluetoothL2CAPChannel *)newChannel
+{
+ Q_UNUSED(notification)
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+ delegate->openNotifyL2CAP(newChannel);
+}
+
+- (quint16)port
+{
+ return port;
+}
+
+@end
diff --git a/src/bluetooth/darwin/btsocketlistener_p.h b/src/bluetooth/darwin/btsocketlistener_p.h
new file mode 100644
index 00000000..4b9b267a
--- /dev/null
+++ b/src/bluetooth/darwin/btsocketlistener_p.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTSOCKETLISTENER_P_H
+#define BTSOCKETLISTENER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+
+#include <IOBluetooth/IOBluetooth.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace DarwinBluetooth {
+
+class SocketListener;
+
+}
+
+QT_END_NAMESPACE
+
+// A single DarwinBTSocketListener can be started only once with
+// RFCOMM or L2CAP protocol. It must be deleted to stop listening.
+
+@interface QT_MANGLE_NAMESPACE(DarwinBTSocketListener) : NSObject
+
+- (id)initWithListener:(QT_PREPEND_NAMESPACE(DarwinBluetooth::SocketListener) *)aDelegate;
+- (void)dealloc;
+
+- (bool)listenRFCOMMConnectionsWithChannelID:(BluetoothRFCOMMChannelID)channelID;
+- (bool)listenL2CAPConnectionsWithPSM:(BluetoothL2CAPPSM)psm;
+
+- (void)rfcommOpenNotification:(IOBluetoothUserNotification *)notification
+ channel:(IOBluetoothRFCOMMChannel *)newChannel;
+
+- (void)l2capOpenNotification:(IOBluetoothUserNotification *)notification
+ channel:(IOBluetoothL2CAPChannel *)newChannel;
+
+- (quint16)port;
+
+@end
+
+#endif
diff --git a/src/bluetooth/darwin/btutility.mm b/src/bluetooth/darwin/btutility.mm
new file mode 100644
index 00000000..6f223a82
--- /dev/null
+++ b/src/bluetooth/darwin/btutility.mm
@@ -0,0 +1,372 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qlowenergycharacteristicdata.h"
+#include "qbluetoothaddress.h"
+#include "btutility_p.h"
+#include "qbluetoothuuid.h"
+
+#include <QtCore/qendian.h>
+#include <QtCore/qstring.h>
+
+#ifndef QT_IOS_BLUETOOTH
+
+#import <IOBluetooth/objc/IOBluetoothSDPUUID.h>
+#import <CoreFoundation/CoreFoundation.h>
+#import <CoreBluetooth/CBUUID.h>
+
+#endif
+
+#include <algorithm>
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(QT_BT_DARWIN, "qt.bluetooth.darwin")
+
+namespace DarwinBluetooth {
+
+const int defaultLEScanTimeoutMS = 25000;
+// We use it only on iOS for now:
+const int maxValueLength = 512;
+
+QString qt_address(NSString *address)
+{
+ if (address && address.length) {
+ NSString *const fixed = [address stringByReplacingOccurrencesOfString:@"-" withString:@":"];
+ return QString::fromNSString(fixed);
+ }
+
+ return QString();
+}
+
+#ifndef QT_IOS_BLUETOOTH
+
+
+QBluetoothAddress qt_address(const BluetoothDeviceAddress *a)
+{
+ if (a) {
+ // TODO: can a byte order be different in BluetoothDeviceAddress?
+ const quint64 qAddress = a->data[5] |
+ qint64(a->data[4]) << 8 |
+ qint64(a->data[3]) << 16 |
+ qint64(a->data[2]) << 24 |
+ qint64(a->data[1]) << 32 |
+ qint64(a->data[0]) << 40;
+ return QBluetoothAddress(qAddress);
+ }
+
+ return QBluetoothAddress();
+}
+
+BluetoothDeviceAddress iobluetooth_address(const QBluetoothAddress &qAddress)
+{
+ BluetoothDeviceAddress a = {};
+ if (!qAddress.isNull()) {
+ const quint64 val = qAddress.toUInt64();
+ a.data[0] = (val >> 40) & 0xff;
+ a.data[1] = (val >> 32) & 0xff;
+ a.data[2] = (val >> 24) & 0xff;
+ a.data[3] = (val >> 16) & 0xff;
+ a.data[4] = (val >> 8) & 0xff;
+ a.data[5] = val & 0xff;
+ }
+
+ return a;
+}
+
+ObjCStrongReference<IOBluetoothSDPUUID> iobluetooth_uuid(const QBluetoothUuid &uuid)
+{
+ const unsigned nBytes = 128 / std::numeric_limits<unsigned char>::digits;
+ const quint128 intVal(uuid.toUInt128());
+
+ const ObjCStrongReference<IOBluetoothSDPUUID> iobtUUID([IOBluetoothSDPUUID uuidWithBytes:intVal.data
+ length:nBytes], true);
+ return iobtUUID;
+}
+
+QBluetoothUuid qt_uuid(IOBluetoothSDPUUID *uuid)
+{
+ QBluetoothUuid qtUuid;
+ if (!uuid || [uuid length] != 16) // TODO: issue any diagnostic?
+ return qtUuid;
+
+ // TODO: ensure the correct byte-order!!!
+ quint128 uuidVal = {};
+ const quint8 *const source = static_cast<const quint8 *>([uuid bytes]);
+ std::copy(source, source + 16, uuidVal.data);
+ return QBluetoothUuid(uuidVal);
+}
+
+QString qt_error_string(IOReturn errorCode)
+{
+ switch (errorCode) {
+ case kIOReturnSuccess:
+ // NoError in many classes == an empty string description.
+ return QString();
+ case kIOReturnNoMemory:
+ return QString::fromLatin1("memory allocation failed");
+ case kIOReturnNoResources:
+ return QString::fromLatin1("failed to obtain a resource");
+ case kIOReturnBusy:
+ return QString::fromLatin1("device is busy");
+ case kIOReturnStillOpen:
+ return QString::fromLatin1("device(s) still open");
+ // Others later ...
+ case kIOReturnError: // "general error" (IOReturn.h)
+ default:
+ return QString::fromLatin1("unknown error");
+ }
+}
+
+void qt_test_iobluetooth_runloop()
+{
+ // IOBluetooth heavily relies on a CFRunLoop machinery in a way it dispatches
+ // its callbacks. Technically, having a QThread with CFRunLoop-based event
+ // dispatcher would suffice. At the moment of writing we do not have such
+ // event dispatcher, so we only can work on the main thread.
+ if (CFRunLoopGetMain() != CFRunLoopGetCurrent()) {
+ qCWarning(QT_BT_DARWIN) << "IOBluetooth works only on the main thread or a"
+ << "thread with a running CFRunLoop";
+ }
+}
+
+#endif // !QT_IOS_BLUETOOTH
+
+
+// Apple has: CBUUID, NSUUID, CFUUID, IOBluetoothSDPUUID
+// and it's handy to have several converters:
+
+QBluetoothUuid qt_uuid(CBUUID *uuid)
+{
+ // Apples' docs say "128 bit" and "16-bit UUIDs are implicitly
+ // pre-filled with the Bluetooth Base UUID."
+ // But Core Bluetooth can return CBUUID objects of length 2
+ // (16-bit, so they are not pre-filled?).
+
+ if (!uuid)
+ return QBluetoothUuid();
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if (uuid.data.length == 2) {
+ // CBUUID's docs say nothing about byte-order.
+ // Seems to be in big-endian.
+ const uchar *const src = static_cast<const uchar *>(uuid.data.bytes);
+ return QBluetoothUuid(qFromBigEndian<quint16>(src));
+ } else if (uuid.data.length == 16) {
+ quint128 qtUuidData = {};
+ const quint8 *const source = static_cast<const quint8 *>(uuid.data.bytes);
+ std::copy(source, source + 16, qtUuidData.data);
+
+ return QBluetoothUuid(qtUuidData);
+ }
+
+ qCDebug(QT_BT_DARWIN) << "qt_uuid, invalid CBUUID, 2 or 16 bytes expected, but got "
+ << uuid.data.length << " bytes length";
+ return QBluetoothUuid();
+}
+
+CFStrongReference<CFUUIDRef> cf_uuid(const QBluetoothUuid &qtUuid)
+{
+ const quint128 qtUuidData = qtUuid.toUInt128();
+ const quint8 *const data = qtUuidData.data;
+
+ CFUUIDBytes bytes = {data[0], data[1], data[2], data[3],
+ data[4], data[5], data[6], data[7],
+ data[8], data[9], data[10], data[11],
+ data[12], data[13], data[14], data[15]};
+
+ CFUUIDRef cfUuid = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, bytes);
+ return CFStrongReference<CFUUIDRef>(cfUuid, false);// false == already retained.
+}
+
+ObjCStrongReference<CBUUID> cb_uuid(const QBluetoothUuid &qtUuid)
+{
+ CFStrongReference<CFUUIDRef> cfUuid(cf_uuid(qtUuid));
+ if (!cfUuid)
+ return ObjCStrongReference<CBUUID>();
+
+ ObjCStrongReference<CBUUID> cbUuid([CBUUID UUIDWithCFUUID:cfUuid], true); //true == retain.
+ return cbUuid;
+}
+
+bool equal_uuids(const QBluetoothUuid &qtUuid, CBUUID *cbUuid)
+{
+ const QBluetoothUuid qtUuid2(qt_uuid(cbUuid));
+ return qtUuid == qtUuid2;
+}
+
+bool equal_uuids(CBUUID *cbUuid, const QBluetoothUuid &qtUuid)
+{
+ return equal_uuids(qtUuid, cbUuid);
+}
+
+QByteArray qt_bytearray(NSData *data)
+{
+ QByteArray value;
+ if (!data || !data.length)
+ return value;
+
+ value.resize(data.length);
+ const char *const src = static_cast<const char *>(data.bytes);
+ std::copy(src, src + data.length, value.data());
+
+ return value;
+}
+
+template<class Integer>
+QByteArray qt_bytearray(Integer n)
+{
+ QByteArray value;
+ value.resize(sizeof n);
+ const char *const src = reinterpret_cast<char *>(&n);
+ std::copy(src, src + sizeof n, value.data());
+
+ return value;
+}
+
+QByteArray qt_bytearray(NSString *string)
+{
+ if (!string)
+ return QByteArray();
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+ NSData *const utf8Data = [string dataUsingEncoding:NSUTF8StringEncoding];
+
+ return qt_bytearray(utf8Data);
+}
+
+QByteArray qt_bytearray(NSObject *obj)
+{
+ // descriptor.value has type 'id'.
+ // While the Apple's docs say this about descriptors:
+ //
+ // - CBUUIDCharacteristicExtendedPropertiesString
+ // The string representation of the UUID for the extended properties descriptor.
+ // The corresponding value for this descriptor is an NSNumber object.
+ //
+ // - CBUUIDCharacteristicUserDescriptionString
+ // The string representation of the UUID for the user description descriptor.
+ // The corresponding value for this descriptor is an NSString object.
+ //
+ // ... etc.
+ //
+ // This is not true. On OS X, they all seem to be NSData (or derived from NSData),
+ // and they can be something else on iOS (NSNumber, NSString, etc.)
+ if (!obj)
+ return QByteArray();
+
+ QT_BT_MAC_AUTORELEASEPOOL;
+
+ if ([obj isKindOfClass:[NSData class]]) {
+ return qt_bytearray(static_cast<NSData *>(obj));
+ } else if ([obj isKindOfClass:[NSString class]]) {
+ return qt_bytearray(static_cast<NSString *>(obj));
+ } else if ([obj isKindOfClass:[NSNumber class]]) {
+ NSNumber *const nsNumber = static_cast<NSNumber *>(obj);
+ return qt_bytearray([nsNumber unsignedShortValue]);
+ }
+ // TODO: Where can be more types, but Core Bluetooth does not support them,
+ // or at least it's not documented.
+
+ return QByteArray();
+}
+
+ObjCStrongReference<NSData> data_from_bytearray(const QByteArray & qtData)
+{
+ if (!qtData.size())
+ return ObjCStrongReference<NSData>([[NSData alloc] init], false);
+
+ ObjCStrongReference<NSData> result([NSData dataWithBytes:qtData.constData() length:qtData.size()], true);
+ return result;
+}
+
+ObjCStrongReference<NSMutableData> mutable_data_from_bytearray(const QByteArray &qtData)
+{
+ using MutableData = ObjCStrongReference<NSMutableData>;
+
+ if (!qtData.size())
+ return MutableData([[NSMutableData alloc] init], false);
+
+ MutableData result([[NSMutableData alloc] initWithLength:qtData.size()], false);
+ [result replaceBytesInRange:NSMakeRange(0, qtData.size())
+ withBytes:qtData.constData()];
+ return result;
+}
+
+// A small RAII class for a dispatch queue.
+class SerialDispatchQueue
+{
+public:
+ explicit SerialDispatchQueue(const char *label)
+ {
+ Q_ASSERT(label);
+
+ queue = dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL);
+ if (!queue) {
+ qCCritical(QT_BT_DARWIN) << "failed to create dispatch queue with label"
+ << label;
+ }
+ }
+ ~SerialDispatchQueue()
+ {
+ if (queue)
+ dispatch_release(queue);
+ }
+
+ dispatch_queue_t data() const
+ {
+ return queue;
+ }
+private:
+ dispatch_queue_t queue;
+
+ Q_DISABLE_COPY(SerialDispatchQueue)
+};
+
+dispatch_queue_t qt_LE_queue()
+{
+ static const SerialDispatchQueue leQueue("qt-bluetooth-LE-queue");
+ return leQueue.data();
+}
+
+} // namespace DarwinBluetooth
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/darwin/btutility_p.h b/src/bluetooth/darwin/btutility_p.h
new file mode 100644
index 00000000..3108b515
--- /dev/null
+++ b/src/bluetooth/darwin/btutility_p.h
@@ -0,0 +1,339 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef BTUTILITY_P_H
+#define BTUTILITY_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qglobal.h>
+
+#include <Foundation/Foundation.h>
+
+#include <CoreBluetooth/CoreBluetooth.h>
+
+#ifdef Q_OS_MACOS
+#include <IOBluetooth/IOBluetooth.h>
+#endif // Q_OS_MACOS
+
+QT_BEGIN_NAMESPACE
+
+class QLowEnergyCharacteristicData;
+class QBluetoothAddress;
+class QBluetoothUuid;
+
+namespace DarwinBluetooth {
+
+struct NSObjectDeleter {
+ static void cleanup(NSObject *obj)
+ {
+ [obj release];
+ }
+};
+
+template<class T>
+class ObjCScopedPointer : public QScopedPointer<NSObject, NSObjectDeleter>
+{
+public:
+ // TODO: remove default argument, add 'retain' parameter,
+ // add a default ctor??? This will make the semantics more
+ // transparent + will simplify the future transition to ARC
+ // (if it will ever happen).
+ explicit ObjCScopedPointer(T *ptr = nullptr) : QScopedPointer(ptr){}
+ operator T*() const
+ {
+ return data();
+ }
+
+ T *data()const
+ {
+ return static_cast<T *>(QScopedPointer::data());
+ }
+
+ T *take()
+ {
+ return static_cast<T *>(QScopedPointer::take());
+ }
+};
+
+#define QT_BT_MAC_AUTORELEASEPOOL const QMacAutoReleasePool pool;
+
+template<class T>
+class ObjCStrongReference {
+public:
+ ObjCStrongReference()
+ : m_ptr(nil)
+ {
+ }
+ ObjCStrongReference(T *obj, bool retain)
+ {
+ if (retain)
+ m_ptr = [obj retain];
+ else
+ m_ptr = obj; // For example, created with initWithXXXX.
+ }
+ ObjCStrongReference(const ObjCStrongReference &rhs)
+ {
+ m_ptr = [rhs.m_ptr retain];
+ }
+ ObjCStrongReference &operator = (const ObjCStrongReference &rhs)
+ {
+ // "Old-style" implementation:
+ if (this != &rhs && m_ptr != rhs.m_ptr) {
+ [m_ptr release];
+ m_ptr = [rhs.m_ptr retain];
+ }
+
+ return *this;
+ }
+
+#ifdef Q_COMPILER_RVALUE_REFS
+ ObjCStrongReference(ObjCStrongReference &&xval)
+ {
+ m_ptr = xval.m_ptr;
+ xval.m_ptr = nil;
+ }
+
+ ObjCStrongReference &operator = (ObjCStrongReference &&xval)
+ {
+ m_ptr = xval.m_ptr;
+ xval.m_ptr = nil;
+ return *this;
+ }
+#endif
+
+ ~ObjCStrongReference()
+ {
+ [m_ptr release];
+ }
+
+ void reset(T *newVal)
+ {
+ if (m_ptr != newVal) {
+ [m_ptr release];
+ m_ptr = [newVal retain];
+ }
+ }
+
+ void resetWithoutRetain(T *newVal)
+ {
+ if (m_ptr != newVal) {
+ [m_ptr release];
+ m_ptr = newVal;
+ }
+ }
+
+ operator T *() const
+ {
+ return m_ptr;
+ }
+
+ T *data() const
+ {
+ return m_ptr;
+ }
+
+ T *take()
+ {
+ T * p = m_ptr;
+ m_ptr = nil;
+ return p;
+ }
+private:
+ T *m_ptr;
+};
+
+// The type 'T' is some XXXRef from CoreFoundation and co.
+// In principle, we can do a trick removing a pointer from a type
+// when template is instantiated, but it's quite a lot of ugly pp-tokens
+// like DarwinBluetooth::CFStrongReference<DarwinBluetooth::remove_pointer<CFUUIDRref>> strongReference;
+// so instead we use 'T' everywhere, not 'T *' as can expected
+// from a smart pointer.
+template<class T>
+class CFStrongReference {
+public:
+ CFStrongReference()
+ : m_ptr(nullptr)
+ {
+ }
+
+ CFStrongReference(T obj, bool retain)
+ : m_ptr(obj)
+ {
+ if (m_ptr && retain)
+ CFRetain(m_ptr);
+ }
+
+ CFStrongReference(const CFStrongReference &rhs)
+ {
+ if ((m_ptr = rhs.m_ptr))
+ CFRetain(m_ptr);
+ }
+
+ CFStrongReference &operator = (const CFStrongReference &rhs)
+ {
+ // "Old-style" implementation:
+ if (this != &rhs && m_ptr != rhs.m_ptr) {
+ if (m_ptr)
+ CFRelease(m_ptr);
+ if ((m_ptr = rhs.m_ptr))
+ CFRetain(m_ptr);
+ }
+
+ return *this;
+ }
+
+#ifdef Q_COMPILER_RVALUE_REFS
+ CFStrongReference(CFStrongReference &&xval)
+ {
+ m_ptr = xval.m_ptr;
+ xval.m_ptr = nullptr;
+ }
+
+ CFStrongReference &operator = (CFStrongReference &&xval)
+ {
+ m_ptr = xval.m_ptr;
+ xval.m_ptr = nullptr;
+ return *this;
+ }
+#endif
+
+ ~CFStrongReference()
+ {
+ if (m_ptr)
+ CFRelease(m_ptr);
+ }
+
+ void reset(T newVal)
+ {
+ if (m_ptr != newVal) {
+ if (m_ptr)
+ CFRelease(m_ptr);
+ if ((m_ptr = newVal))
+ CFRetain(m_ptr);
+ }
+ }
+
+ operator T() const
+ {
+ return m_ptr;
+ }
+
+ T data() const
+ {
+ return m_ptr;
+ }
+
+ T take()
+ {
+ T p = m_ptr;
+ m_ptr = nullptr;
+ return p;
+ }
+private:
+ T m_ptr;
+};
+
+QString qt_address(NSString *address);
+
+#ifndef QT_IOS_BLUETOOTH
+
+QBluetoothAddress qt_address(const BluetoothDeviceAddress *address);
+BluetoothDeviceAddress iobluetooth_address(const QBluetoothAddress &address);
+
+ObjCStrongReference<IOBluetoothSDPUUID> iobluetooth_uuid(const QBluetoothUuid &uuid);
+QBluetoothUuid qt_uuid(IOBluetoothSDPUUID *uuid);
+QString qt_error_string(IOReturn errorCode);
+void qt_test_iobluetooth_runloop();
+
+#endif // !QT_IOS_BLUETOOTH
+
+QBluetoothUuid qt_uuid(CBUUID *uuid);
+CFStrongReference<CFUUIDRef> cf_uuid(const QBluetoothUuid &qtUuid);
+ObjCStrongReference<CBUUID> cb_uuid(const QBluetoothUuid &qtUuid);
+bool equal_uuids(const QBluetoothUuid &qtUuid, CBUUID *cbUuid);
+bool equal_uuids(CBUUID *cbUuid, const QBluetoothUuid &qtUuid);
+QByteArray qt_bytearray(NSData *data);
+QByteArray qt_bytearray(NSObject *data);
+
+ObjCStrongReference<NSData> data_from_bytearray(const QByteArray &qtData);
+ObjCStrongReference<NSMutableData> mutable_data_from_bytearray(const QByteArray &qtData);
+
+dispatch_queue_t qt_LE_queue();
+
+extern const int defaultLEScanTimeoutMS;
+extern const int maxValueLength;
+
+} // namespace DarwinBluetooth
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_DARWIN)
+
+QT_END_NAMESPACE
+
+#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(101300) && QT_MACOS_DEPLOYMENT_TARGET_BELOW(101300)
+
+ // In the macOS 10.13 SDK, the identifier property was moved from the CBPeripheral
+ // and CBCentral classes to a new CBPeer base class. Because CBPeer is only available
+ // on macOS 10.13 and above, the same is true for -[CBPeer identifier]. However,
+ // since we know that the derived classes have always had this property,
+ // we'll explicitly mark its availability here. This will not adversely affect
+ // using the identifier through the CBPeer base class, which will still require macOS 10.13.
+
+@interface CBPeripheral (UnguardedWorkaround)
+@property (readonly, nonatomic) NSUUID *identifier NS_AVAILABLE(10_7, 5_0);
+@end
+
+@interface CBCentral (UnguardedWorkaround)
+@property (readonly, nonatomic) NSUUID *identifier NS_AVAILABLE(10_7, 5_0);
+@end
+
+#endif // QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE
+
+#endif // BTUTILITY_P_H
diff --git a/src/bluetooth/darwin/darwinbt.pri b/src/bluetooth/darwin/darwinbt.pri
new file mode 100644
index 00000000..b419007a
--- /dev/null
+++ b/src/bluetooth/darwin/darwinbt.pri
@@ -0,0 +1,48 @@
+SOURCES += darwin/uistrings.cpp \
+ darwin/btnotifier.cpp \
+ darwin/btdelegates.cpp \
+ darwin/btledeviceinquiry.mm \
+ darwin/btcentralmanager.mm
+
+HEADERS += darwin/uistrings_p.h \
+ darwin/btgcdtimer_p.h \
+ darwin/btraii_p.h \
+ darwin/btdelegates_p.h \
+ darwin/btutility_p.h \
+ darwin/btledeviceinquiry_p.h \
+ darwin/btcentralmanager_p.h \
+ darwin/btnotifier_p.h
+
+OBJECTIVE_SOURCES += darwin/btgcdtimer.mm \
+ darwin/btraii.mm \
+ darwin/btutility.mm
+
+#QMAKE_CXXFLAGS_WARN_ON += -Wno-nullability-completeness
+
+macos {
+ HEADERS += darwin/btdevicepair_p.h \
+ darwin/btdeviceinquiry_p.h \
+ darwin/btconnectionmonitor_p.h \
+ darwin/btsdpinquiry_p.h \
+ darwin/btrfcommchannel_p.h \
+ darwin/btl2capchannel_p.h \
+ darwin/btservicerecord_p.h \
+ darwin/btsocketlistener_p.h \
+ darwin/btobexsession_p.h
+
+ OBJECTIVE_SOURCES += darwin/btdevicepair.mm \
+ darwin/btdeviceinquiry.mm \
+ darwin/btconnectionmonitor.mm \
+ darwin/btsdpinquiry.mm \
+ darwin/btrfcommchannel.mm \
+ darwin/btl2capchannel.mm \
+ darwin/btservicerecord.mm \
+ darwin/btsocketlistener.mm \
+ darwin/btobexsession.mm
+}
+
+macos | ios {
+ HEADERS += darwin/btperipheralmanager_p.h
+
+ OBJECTIVE_SOURCES += darwin/btperipheralmanager.mm
+}
diff --git a/src/bluetooth/darwin/uistrings.cpp b/src/bluetooth/darwin/uistrings.cpp
new file mode 100644
index 00000000..7260417f
--- /dev/null
+++ b/src/bluetooth/darwin/uistrings.cpp
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "uistrings_p.h"
+
+// Translatable messages should go into this .cpp file for them to
+// be picked up by lupdate.
+
+QT_BEGIN_NAMESPACE
+
+const char DEV_DISCOVERY[] = "QBluetoothDeviceDiscoveryAgent";
+const char DD_POWERED_OFF[] = QT_TRANSLATE_NOOP("QBluetoothDeviceDiscoveryAgent", "Device is powered off");
+const char DD_INVALID_ADAPTER[] = QT_TRANSLATE_NOOP("QBluetoothDeviceDiscoveryAgent", "Cannot find valid Bluetooth adapter.");
+const char DD_IO[] = QT_TRANSLATE_NOOP("QBluetoothDeviceDiscoveryAgent", "Input Output Error");
+const char DD_NOTSUPPORTED[] = QT_TRANSLATE_NOOP("QBluetoothDeviceDiscoveryAgent", "Bluetooth LE is not supported");
+const char DD_UNKNOWN_ERROR[] = QT_TRANSLATE_NOOP("QBluetoothDeviceDiscoveryAgent", "Unknown error");
+const char DD_NOT_STARTED[] = QT_TRANSLATE_NOOP("QBluetoothDeviceDiscoveryAgent", "Cannot start device inquiry");
+const char DD_NOT_STARTED_LE[] = QT_TRANSLATE_NOOP("QBluetoothDeviceDiscoveryAgent", "Cannot start low energy device inquiry");
+const char DD_NOT_STOPPED[] = QT_TRANSLATE_NOOP("QBluetoothDeviceDiscoveryAgent", "Discovery cannot be stopped");
+
+const char SERVICE_DISCOVERY[] = "QBluetoothServiceDiscoveryAgent";
+const char SD_LOCAL_DEV_OFF[] = QT_TRANSLATE_NOOP("QBluetoothServiceDiscoveryAgent", "Local device is powered off");
+const char SD_MINIMAL_FAILED[] = QT_TRANSLATE_NOOP("QBluetoothServiceDiscoveryAgent", "Minimal service discovery failed");
+const char SD_INVALID_ADDRESS[] = QT_TRANSLATE_NOOP("QBluetoothServiceDiscoveryAgent", "Invalid Bluetooth adapter address");
+
+const char SOCKET[] = "QBluetoothSocket";
+const char SOC_NETWORK_ERROR[] = QT_TRANSLATE_NOOP("QBluetoothSocket", "Network Error");
+const char SOC_NOWRITE[] = QT_TRANSLATE_NOOP("QBluetoothSocket", "Cannot write while not connected");
+const char SOC_CONNECT_IN_PROGRESS[] = QT_TRANSLATE_NOOP("QBluetoothSocket", "Trying to connect while connection is in progress");
+const char SOC_SERVICE_NOT_FOUND[] = QT_TRANSLATE_NOOP("QBluetoothSocket", "Service cannot be found");
+const char SOC_INVAL_DATASIZE[] = QT_TRANSLATE_NOOP("QBluetoothSocket", "Invalid data/data size");
+const char SOC_NOREAD[] = QT_TRANSLATE_NOOP("QBluetoothSocket", "Cannot read while not connected");
+
+const char TRANSFER_REPLY[] = "QBluetoothTransferReply";
+const char TR_INVAL_TARGET[] = QT_TRANSLATE_NOOP("QBluetoothTransferReply", "Invalid target address");
+const char TR_SESSION_NO_START[] = QT_TRANSLATE_NOOP("QBluetoothTransferReply", "Push session cannot be started");
+const char TR_CONNECT_FAILED[] = QT_TRANSLATE_NOOP("QBluetoothTransferReply", "Push session cannot connect");
+const char TR_FILE_NOT_EXIST[] = QT_TRANSLATE_NOOP("QBluetoothTransferReply", "Source file does not exist");
+const char TR_NOT_READ_IODEVICE[] = QT_TRANSLATE_NOOP("QBluetoothTransferReply", "QIODevice cannot be read. Make sure it is open for reading.");
+const char TR_SESSION_FAILED[] = QT_TRANSLATE_NOOP("QBluetoothTransferReply", "Push session failed");
+const char TR_INVALID_DEVICE[] = QT_TRANSLATE_NOOP("QBluetoothTransferReply", "Invalid input device (null)");
+const char TR_OP_CANCEL[] = QT_TRANSLATE_NOOP("QBluetoothTransferReply", "Operation canceled");
+const char TR_IN_PROGRESS[] = QT_TRANSLATE_NOOP("QBluetoothTransferReply", "Transfer already started");
+const char TR_SERVICE_NO_FOUND[] = QT_TRANSLATE_NOOP("QBluetoothTransferReply", "Push service not found");
+
+const char LE_CONTROLLER[] = "QLowEnergyController";
+const char LEC_RDEV_NO_FOUND[] = QT_TRANSLATE_NOOP("QLowEnergyController", "Remote device cannot be found");
+const char LEC_NO_LOCAL_DEV[] = QT_TRANSLATE_NOOP("QLowEnergyController", "Cannot find local adapter");
+const char LEC_IO_ERROR[] = QT_TRANSLATE_NOOP("QLowEnergyController", "Error occurred during connection I/O");
+const char LEC_UNKNOWN_ERROR[] = QT_TRANSLATE_NOOP("QLowEnergyController", "Unknown Error");
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/darwin/uistrings_p.h b/src/bluetooth/darwin/uistrings_p.h
new file mode 100644
index 00000000..999a8c6d
--- /dev/null
+++ b/src/bluetooth/darwin/uistrings_p.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TRANSLATIONS_H
+#define TRANSLATIONS_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QString>
+
+QT_BEGIN_NAMESPACE
+
+// QBluetoothDeviceDiscoveryAgent related strings
+extern const char DEV_DISCOVERY[];
+extern const char DD_POWERED_OFF[];
+extern const char DD_INVALID_ADAPTER[];
+extern const char DD_IO[];
+extern const char DD_NOTSUPPORTED[];
+extern const char DD_UNKNOWN_ERROR[];
+extern const char DD_NOT_STARTED[];
+extern const char DD_NOT_STARTED_LE[];
+extern const char DD_NOT_STOPPED[];
+
+// QBluetoothServiceDiscoveryAgent related strings
+extern const char SERVICE_DISCOVERY[];
+extern const char SD_LOCAL_DEV_OFF[];
+extern const char SD_MINIMAL_FAILED[];
+extern const char SD_INVALID_ADDRESS[];
+
+// QBluetoothSocket related strings
+extern const char SOCKET[];
+extern const char SOC_NETWORK_ERROR[];
+extern const char SOC_NOWRITE[];
+extern const char SOC_CONNECT_IN_PROGRESS[];
+extern const char SOC_SERVICE_NOT_FOUND[];
+extern const char SOC_INVAL_DATASIZE[];
+extern const char SOC_NOREAD[];
+
+// QBluetoothTransferReply related strings
+extern const char TRANSFER_REPLY[];
+extern const char TR_INVAL_TARGET[];
+extern const char TR_SESSION_NO_START[];
+extern const char TR_CONNECT_FAILED[];
+extern const char TR_FILE_NOT_EXIST[];
+extern const char TR_NOT_READ_IODEVICE[];
+extern const char TR_SESSION_FAILED[];
+extern const char TR_INVALID_DEVICE[];
+extern const char TR_OP_CANCEL[];
+extern const char TR_IN_PROGRESS[];
+extern const char TR_SERVICE_NO_FOUND[];
+
+// QLowEnergyController related strings
+extern const char LE_CONTROLLER[];
+extern const char LEC_RDEV_NO_FOUND[];
+extern const char LEC_NO_LOCAL_DEV[];
+extern const char LEC_IO_ERROR[];
+extern const char LEC_UNKNOWN_ERROR[];
+
+QT_END_NAMESPACE
+
+#endif // TRANSLATIONS_H
+