1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtBluetooth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "osx/osxbtconnectionmonitor_p.h"
41#include "qbluetoothlocaldevice_p.h"
42#include "qbluetoothlocaldevice.h"
43#include "osx/osxbtdevicepair_p.h"
44#include "osx/osxbtutility_p.h"
45#include "osx/osxbluetooth_p.h"
46
47#include <QtCore/qloggingcategory.h>
48#include <QtCore/qstring.h>
49#include <QtCore/qglobal.h>
50#include <QtCore/qdebug.h>
51#include <QtCore/qmap.h>
52
53
54#include <Foundation/Foundation.h>
55
56#include <algorithm>
57
58QT_BEGIN_NAMESPACE
59
60class QBluetoothLocalDevicePrivate : public OSXBluetooth::PairingDelegate,
61                                     public OSXBluetooth::ConnectionMonitor
62{
63    friend class QBluetoothLocalDevice;
64public:
65    typedef QBluetoothLocalDevice::Pairing Pairing;
66
67    QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *, const QBluetoothAddress & =
68                                 QBluetoothAddress());
69
70    bool isValid() const;
71    void requestPairing(const QBluetoothAddress &address, Pairing pairing);
72    Pairing pairingStatus(const QBluetoothAddress &address) const;
73
74private:
75
76    // PairingDelegate:
77    void connecting(ObjCPairingRequest *pair) override;
78    void requestPIN(ObjCPairingRequest *pair) override;
79    void requestUserConfirmation(ObjCPairingRequest *pair,
80                                 BluetoothNumericValue) override;
81    void passkeyNotification(ObjCPairingRequest *pair,
82                             BluetoothPasskey passkey) override;
83    void error(ObjCPairingRequest *pair, IOReturn errorCode) override;
84    void pairingFinished(ObjCPairingRequest *pair) override;
85
86    // ConnectionMonitor
87    void deviceConnected(const QBluetoothAddress &deviceAddress) override;
88    void deviceDisconnected(const QBluetoothAddress &deviceAddress) override;
89
90    void emitPairingFinished(const QBluetoothAddress &deviceAddress, Pairing pairing, bool queued);
91    void emitError(QBluetoothLocalDevice::Error error, bool queued);
92
93    void unpair(const QBluetoothAddress &deviceAddress);
94
95    QBluetoothLocalDevice *q_ptr;
96
97    typedef OSXBluetooth::ObjCScopedPointer<IOBluetoothHostController> HostController;
98    HostController hostController;
99
100    typedef OSXBluetooth::ObjCStrongReference<ObjCPairingRequest> PairingRequest;
101    typedef QMap<QBluetoothAddress, PairingRequest> RequestMap;
102
103    RequestMap pairingRequests;
104
105    OSXBluetooth::ObjCScopedPointer<ObjCConnectionMonitor> connectionMonitor;
106    QList<QBluetoothAddress> discoveredDevices;
107};
108
109QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *q,
110                                                           const QBluetoothAddress &address) :
111        q_ptr(q)
112{
113    registerQBluetoothLocalDeviceMetaType();
114
115    Q_ASSERT_X(q, Q_FUNC_INFO, "invalid q_ptr (null)");
116
117    QT_BT_MAC_AUTORELEASEPOOL;
118
119    HostController defaultController([[IOBluetoothHostController defaultController] retain]);
120    if (!defaultController) {
121        qCCritical(QT_BT_OSX) << "failed to init a host controller object";
122        return;
123    }
124
125    if (!address.isNull()) {
126        NSString *const hciAddress = [defaultController addressAsString];
127        if (!hciAddress) {
128            qCCritical(QT_BT_OSX) << "failed to obtain an address";
129            return;
130        }
131
132        BluetoothDeviceAddress iobtAddress = {};
133        if (IOBluetoothNSStringToDeviceAddress(hciAddress, &iobtAddress) != kIOReturnSuccess) {
134            qCCritical(QT_BT_OSX) << "invalid local device's address";
135            return;
136        }
137
138        if (address != OSXBluetooth::qt_address(&iobtAddress)) {
139            qCCritical(QT_BT_OSX) << "invalid local device's address";
140            return;
141        }
142    }
143
144    hostController.reset(defaultController.take());
145
146    // This one is optional, if it fails to initialize, we do not care at all.
147    connectionMonitor.reset([[ObjCConnectionMonitor alloc] initWithMonitor:this]);
148}
149
150bool QBluetoothLocalDevicePrivate::isValid() const
151{
152    return hostController.data();
153}
154
155void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &address, Pairing pairing)
156{
157    Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid local device");
158    Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid device address");
159
160    using OSXBluetooth::device_with_address;
161    using OSXBluetooth::ObjCStrongReference;
162
163    // That's a really special case on OS X.
164    if (pairing == QBluetoothLocalDevice::Unpaired)
165        return unpair(address);
166
167    QT_BT_MAC_AUTORELEASEPOOL;
168
169    if (pairing == QBluetoothLocalDevice::AuthorizedPaired)
170        pairing = QBluetoothLocalDevice::Paired;
171
172    RequestMap::iterator pos = pairingRequests.find(address);
173    if (pos != pairingRequests.end()) {
174        if ([pos.value() isActive]) // Still trying to pair, continue.
175            return;
176
177        // 'device' is autoreleased:
178        IOBluetoothDevice *const device = [pos.value() targetDevice];
179        if ([device isPaired]) {
180            emitPairingFinished(address, pairing, true);
181        } else if ([pos.value() start] != kIOReturnSuccess) {
182            qCCritical(QT_BT_OSX) << "failed to start a new pairing request";
183            emitError(QBluetoothLocalDevice::PairingError, true);
184        }
185        return;
186    }
187
188    // That's a totally new request ('Paired', since we are here).
189    // Even if this device is paired (not by our local device), I still create a pairing request,
190    // it'll just finish with success (skipping any intermediate steps).
191    PairingRequest newRequest([[ObjCPairingRequest alloc] initWithTarget:address delegate:this], false);
192    if (!newRequest) {
193        qCCritical(QT_BT_OSX) << "failed to allocate a new pairing request";
194        emitError(QBluetoothLocalDevice::PairingError, true);
195        return;
196    }
197
198    pos = pairingRequests.insert(address, newRequest);
199    const IOReturn result = [newRequest start];
200    if (result != kIOReturnSuccess) {
201        pairingRequests.erase(pos);
202        qCCritical(QT_BT_OSX) << "failed to start a new pairing request";
203        emitError(QBluetoothLocalDevice::PairingError, true);
204    }
205}
206
207QBluetoothLocalDevice::Pairing QBluetoothLocalDevicePrivate::pairingStatus(const QBluetoothAddress &address)const
208{
209    Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid local device");
210    Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid address");
211
212    using OSXBluetooth::device_with_address;
213    using OSXBluetooth::ObjCStrongReference;
214
215    QT_BT_MAC_AUTORELEASEPOOL;
216
217    RequestMap::const_iterator it = pairingRequests.find(address);
218    if (it != pairingRequests.end()) {
219        // All Obj-C objects are autoreleased.
220        IOBluetoothDevice *const device = [it.value() targetDevice];
221        if (device && [device isPaired])
222            return QBluetoothLocalDevice::Paired;
223    } else {
224        // Try even if device was not paired by this local device ...
225        const ObjCStrongReference<IOBluetoothDevice> device(device_with_address(address));
226        if (device && [device isPaired])
227            return QBluetoothLocalDevice::Paired;
228    }
229
230    return QBluetoothLocalDevice::Unpaired;
231}
232
233void QBluetoothLocalDevicePrivate::connecting(ObjCPairingRequest *pair)
234{
235    Q_UNUSED(pair)
236}
237
238void QBluetoothLocalDevicePrivate::requestPIN(ObjCPairingRequest *pair)
239{
240    Q_UNUSED(pair)
241}
242
243void QBluetoothLocalDevicePrivate::requestUserConfirmation(ObjCPairingRequest *pair, BluetoothNumericValue intPin)
244{
245    Q_UNUSED(pair)
246    Q_UNUSED(intPin)
247}
248
249void QBluetoothLocalDevicePrivate::passkeyNotification(ObjCPairingRequest *pair,
250                                                       BluetoothPasskey passkey)
251{
252    Q_UNUSED(pair)
253    Q_UNUSED(passkey)
254}
255
256void QBluetoothLocalDevicePrivate::error(ObjCPairingRequest *pair, IOReturn errorCode)
257{
258    Q_UNUSED(pair)
259    Q_UNUSED(errorCode)
260
261    emitError(QBluetoothLocalDevice::PairingError, false);
262}
263
264void QBluetoothLocalDevicePrivate::pairingFinished(ObjCPairingRequest *pair)
265{
266    Q_ASSERT_X(pair, Q_FUNC_INFO, "invalid pairing request (nil)");
267
268    const QBluetoothAddress &deviceAddress = [pair targetAddress];
269    Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO,
270               "invalid target address");
271
272    emitPairingFinished(deviceAddress, QBluetoothLocalDevice::Paired, false);
273}
274
275void QBluetoothLocalDevicePrivate::deviceConnected(const QBluetoothAddress &deviceAddress)
276{
277    if (!discoveredDevices.contains(deviceAddress))
278        discoveredDevices.append(deviceAddress);
279
280    QMetaObject::invokeMethod(q_ptr, "deviceConnected", Qt::QueuedConnection,
281                              Q_ARG(QBluetoothAddress, deviceAddress));
282}
283
284void QBluetoothLocalDevicePrivate::deviceDisconnected(const QBluetoothAddress &deviceAddress)
285{
286    QList<QBluetoothAddress>::iterator devicePos =std::find(discoveredDevices.begin(),
287                                                            discoveredDevices.end(),
288                                                            deviceAddress);
289
290    if (devicePos != discoveredDevices.end())
291        discoveredDevices.erase(devicePos);
292
293    QMetaObject::invokeMethod(q_ptr, "deviceDisconnected", Qt::QueuedConnection,
294                              Q_ARG(QBluetoothAddress, deviceAddress));
295}
296
297void QBluetoothLocalDevicePrivate::emitError(QBluetoothLocalDevice::Error error, bool queued)
298{
299    if (queued) {
300        QMetaObject::invokeMethod(q_ptr, "error", Qt::QueuedConnection,
301                                  Q_ARG(QBluetoothLocalDevice::Error, error));
302    } else {
303        emit q_ptr->error(QBluetoothLocalDevice::PairingError);
304    }
305}
306
307void QBluetoothLocalDevicePrivate::emitPairingFinished(const QBluetoothAddress &deviceAddress,
308                                                       Pairing pairing, bool queued)
309{
310    Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO, "invalid target device address");
311    Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
312
313    if (queued) {
314        QMetaObject::invokeMethod(q_ptr, "pairingFinished", Qt::QueuedConnection,
315                                  Q_ARG(QBluetoothAddress, deviceAddress),
316                                  Q_ARG(QBluetoothLocalDevice::Pairing, pairing));
317    } else {
318        emit q_ptr->pairingFinished(deviceAddress, pairing);
319    }
320}
321
322void QBluetoothLocalDevicePrivate::unpair(const QBluetoothAddress &deviceAddress)
323{
324    Q_ASSERT_X(!deviceAddress.isNull(), Q_FUNC_INFO,
325               "invalid target address");
326
327    emitPairingFinished(deviceAddress, QBluetoothLocalDevice::Unpaired, true);
328}
329
330QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) :
331    QObject(parent),
332    d_ptr(new QBluetoothLocalDevicePrivate(this))
333{
334}
335
336QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent) :
337    QObject(parent),
338    d_ptr(new QBluetoothLocalDevicePrivate(this, address))
339{
340}
341
342QBluetoothLocalDevice::~QBluetoothLocalDevice()
343{
344    delete d_ptr;
345}
346
347bool QBluetoothLocalDevice::isValid() const
348{
349    return d_ptr->isValid();
350}
351
352
353QString QBluetoothLocalDevice::name() const
354{
355    QT_BT_MAC_AUTORELEASEPOOL;
356
357    if (isValid()) {
358        if (NSString *const nsn = [d_ptr->hostController nameAsString])
359            return QString::fromNSString(nsn);
360        qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to obtain a name";
361    }
362
363    return QString();
364}
365
366QBluetoothAddress QBluetoothLocalDevice::address() const
367{
368    QT_BT_MAC_AUTORELEASEPOOL;
369
370    if (isValid()) {
371        if (NSString *const nsa = [d_ptr->hostController addressAsString])
372            return QBluetoothAddress(OSXBluetooth::qt_address(nsa));
373
374        qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to obtain an address";
375    } else {
376        qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device";
377    }
378
379    return QBluetoothAddress();
380}
381
382void QBluetoothLocalDevice::powerOn()
383{
384    if (!isValid())
385        qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device";
386}
387
388void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode mode)
389{
390    Q_UNUSED(mode)
391
392    if (!isValid())
393        qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device";
394}
395
396QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const
397{
398    if (!isValid() || ![d_ptr->hostController powerState])
399        return HostPoweredOff;
400
401    return HostConnectable;
402}
403
404QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const
405{
406    QT_BT_MAC_AUTORELEASEPOOL;
407
408    QList<QBluetoothAddress> connectedDevices;
409
410    // Take the devices known to IOBluetooth to be paired and connected first:
411    NSArray *const pairedDevices = [IOBluetoothDevice pairedDevices];
412    for (IOBluetoothDevice *device in pairedDevices) {
413        if ([device isConnected]) {
414            const QBluetoothAddress address(OSXBluetooth::qt_address([device getAddress]));
415            if (!address.isNull())
416                connectedDevices.append(address);
417        }
418    }
419
420    // Add devices, discovered by the connection monitor:
421    connectedDevices += d_ptr->discoveredDevices;
422    // Find something more elegant? :)
423    // But after all, addresses are integers.
424    std::sort(connectedDevices.begin(), connectedDevices.end());
425    connectedDevices.erase(std::unique(connectedDevices.begin(),
426                                       connectedDevices.end()),
427                           connectedDevices.end());
428
429    return connectedDevices;
430}
431
432QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices()
433{
434    QList<QBluetoothHostInfo> localDevices;
435
436    QBluetoothLocalDevice defaultAdapter;
437    if (!defaultAdapter.isValid() || defaultAdapter.address().isNull()) {
438        qCCritical(QT_BT_OSX) << Q_FUNC_INFO <<"no valid device found";
439        return localDevices;
440    }
441
442    QBluetoothHostInfo deviceInfo;
443    deviceInfo.setName(defaultAdapter.name());
444    deviceInfo.setAddress(defaultAdapter.address());
445
446    localDevices.append(deviceInfo);
447
448    return localDevices;
449}
450
451void QBluetoothLocalDevice::pairingConfirmation(bool confirmation)
452{
453    Q_UNUSED(confirmation)
454}
455
456
457void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing)
458{
459    if (!isValid())
460        qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device";
461
462    if (!isValid() || address.isNull()) {
463        d_ptr->emitError(PairingError, true);
464        return;
465    }
466
467    OSXBluetooth::qt_test_iobluetooth_runloop();
468
469    return d_ptr->requestPairing(address, pairing);
470}
471
472QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(const QBluetoothAddress &address) const
473{
474    if (!isValid())
475        qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid local device";
476
477    if (!isValid() || address.isNull())
478        return Unpaired;
479
480    return d_ptr->pairingStatus(address);
481}
482
483QT_END_NAMESPACE
484