diff options
Diffstat (limited to 'src/bluetooth')
25 files changed, 750 insertions, 72 deletions
diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp index 9c9c0409..5acf761d 100644 --- a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp +++ b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp @@ -237,7 +237,7 @@ static const MinorClassJavaToQtMapping minorMappings[] = { { Q_NULLPTR, 0 }, // index 64 & separator }; -/*! Advertising Data Type (AD type) for LE scan records, as defined in Bluetooth CSS v6. */ +/* Advertising Data Type (AD type) for LE scan records, as defined in Bluetooth CSS v6. */ enum ADType { ADType16BitUuidIncomplete = 0x02, ADType16BitUuidComplete = 0x03, diff --git a/src/bluetooth/android/inputstreamthread.cpp b/src/bluetooth/android/inputstreamthread.cpp index ecd9218e..982c477b 100644 --- a/src/bluetooth/android/inputstreamthread.cpp +++ b/src/bluetooth/android/inputstreamthread.cpp @@ -77,6 +77,12 @@ qint64 InputStreamThread::bytesAvailable() const return m_socket_p->buffer.size(); } +bool InputStreamThread::canReadLine() const +{ + QMutexLocker locker(&m_mutex); + return m_socket_p->buffer.canReadLine(); +} + qint64 InputStreamThread::readData(char *data, qint64 maxSize) { QMutexLocker locker(&m_mutex); diff --git a/src/bluetooth/android/inputstreamthread_p.h b/src/bluetooth/android/inputstreamthread_p.h index 741333ac..a6ee0655 100644 --- a/src/bluetooth/android/inputstreamthread_p.h +++ b/src/bluetooth/android/inputstreamthread_p.h @@ -68,6 +68,7 @@ public: explicit InputStreamThread(QBluetoothSocketPrivate *socket_p); qint64 bytesAvailable() const; + bool canReadLine() const; bool run(); qint64 readData(char *data, qint64 maxSize); diff --git a/src/bluetooth/bluez/bluetoothmanagement.cpp b/src/bluetooth/bluez/bluetoothmanagement.cpp new file mode 100644 index 00000000..9df74b34 --- /dev/null +++ b/src/bluetooth/bluez/bluetoothmanagement.cpp @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 <QtCore/qloggingcategory.h> +#include <QtCore/qsocketnotifier.h> +#include <QtCore/qtimer.h> + +#include "bluetoothmanagement_p.h" +#include "bluez_data_p.h" +#include "../qbluetoothsocket_p.h" + +#include <unistd.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <linux/capability.h> + + +QT_BEGIN_NAMESPACE + +// Packet data structures for Mgmt API bluez.git/doc/mgmt-api.txt + +enum class EventCode { + DeviceFound = 0x0012, +}; + +struct MgmtHdr { + quint16 cmdCode; + quint16 controllerIndex; + quint16 length; +} __attribute__((packed)); + +struct MgmtEventDeviceFound { + bdaddr_t bdaddr; + quint8 type; + quint8 rssi; + quint32 flags; + quint16 eirLength; + quint8 eirData[0]; +} __attribute__((packed)); + + +/* + * This class encapsulates access to the Bluetooth Management API as introduced by + * Linux kernel 3.4. Some Bluetooth information is not exposed via the usual DBus + * API (e.g. the random/public address type info). In those cases we have to fall back + * to this mgmt API. + * + * Note that opening such a Bluetooth mgmt socket requires CAP_NET_ADMIN (root) capability. + * + * Documentation can be found in bluez-git/doc/mgmt-api.txt + */ + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) + +// These structs and defines come straight from linux/capability.h. +// To avoid missing definitions we re-define them if not existing. +// In addition, we don't want to pull in a libcap2 dependency +struct capHdr { + quint32 version; + int pid; +}; + +struct capData { + quint32 effective; + quint32 permitted; + quint32 inheritable; +}; + +#ifndef _LINUX_CAPABILITY_VERSION_3 +#define _LINUX_CAPABILITY_VERSION_3 0x20080522 +#endif + +#ifndef _LINUX_CAPABILITY_U32S_3 +#define _LINUX_CAPABILITY_U32S_3 2 +#endif + +#ifndef CAP_NET_ADMIN +#define CAP_NET_ADMIN 12 +#endif + +#ifndef CAP_TO_INDEX +#define CAP_TO_INDEX(x) ((x) >> 5) /* 1 << 5 == bits in __u32 */ +#endif + +#ifndef CAP_TO_MASK +#define CAP_TO_MASK(x) (1 << ((x) & 31)) /* mask for indexed __u32 */ +#endif + +const int msecInADay = 1000*60*60*24; + +inline uint qHash(const QBluetoothAddress& address) +{ + return qHash(address.toUInt64()); +} + +static int sysCallCapGet(capHdr *header, capData *data) +{ + return syscall(__NR_capget, header, data); +} + +/*! + * Checks that the current process has the effective CAP_NET_ADMIN permission. + */ +static bool hasBtMgmtPermission() +{ + // We only care for cap version 3 introduced by kernel 2.6.26 + // because the new BlueZ management API only exists since kernel 3.4. + + struct capHdr header = {}; + struct capData data[_LINUX_CAPABILITY_U32S_3] = {{}}; + header.version = _LINUX_CAPABILITY_VERSION_3; + header.pid = getpid(); + + if (sysCallCapGet(&header, data) < 0) { + qCWarning(QT_BT_BLUEZ, "BluetoothManangement: getCap failed with %s", + qPrintable(qt_error_string(errno))); + return false; + } + + return (data[CAP_TO_INDEX(CAP_NET_ADMIN)].effective & CAP_TO_MASK(CAP_NET_ADMIN)); +} + +BluetoothManagement::BluetoothManagement(QObject *parent) : QObject(parent) +{ + bool hasPermission = hasBtMgmtPermission(); + if (!hasPermission) { + qCInfo(QT_BT_BLUEZ, "Missing CAP_NET_ADMIN permission. Cannot determine whether " + "a found address is of random or public type."); + return; + } + + sockaddr_hci hciAddr; + + fd = ::socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_HCI); + if (fd < 0) { + qCWarning(QT_BT_BLUEZ, "Cannot open Bluetooth Management socket: %s", + qPrintable(qt_error_string(errno))); + return; + } + + memset(&hciAddr, 0, sizeof(hciAddr)); + hciAddr.hci_dev = HCI_DEV_NONE; + hciAddr.hci_channel = HCI_CHANNEL_CONTROL; + hciAddr.hci_family = AF_BLUETOOTH; + + if (::bind(fd, (struct sockaddr *)(&hciAddr), sizeof(hciAddr)) < 0) { + qCWarning(QT_BT_BLUEZ, "Cannot bind Bluetooth Management socket: %s", + qPrintable(qt_error_string(errno))); + ::close(fd); + fd = -1; + return; + } + + notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); + connect(notifier, &QSocketNotifier::activated, this, &BluetoothManagement::_q_readNotifier); + + // ensure cache is regularly cleaned (once every 24h) + QTimer* timer = new QTimer(this); + timer->setInterval(msecInADay); + timer->setTimerType(Qt::VeryCoarseTimer); + connect(timer, &QTimer::timeout, this, &BluetoothManagement::cleanupOldAddressFlags); + timer->start(); +} + +Q_GLOBAL_STATIC(BluetoothManagement, bluetoothKernelManager) + +BluetoothManagement *BluetoothManagement::instance() +{ + return bluetoothKernelManager(); +} + +void BluetoothManagement::_q_readNotifier() +{ + char *dst = buffer.reserve(QPRIVATELINEARBUFFER_BUFFERSIZE); + int readCount = ::read(fd, dst, QPRIVATELINEARBUFFER_BUFFERSIZE); + buffer.chop(QPRIVATELINEARBUFFER_BUFFERSIZE - (readCount < 0 ? 0 : readCount)); + if (readCount < 0) { + qCWarning(QT_BT_BLUEZ, "Management Control read error %s", qPrintable(qt_error_string(errno))); + return; + } + + // do we have at least one complete mgmt header? + if ((uint)buffer.size() < sizeof(MgmtHdr)) + return; + + QByteArray data = buffer.readAll(); + + while (true) { + if ((uint)data.size() < sizeof(MgmtHdr)) + break; + + const MgmtHdr *hdr = reinterpret_cast<const MgmtHdr*>(data.constData()); + const int nextPackageSize = qFromLittleEndian(hdr->length) + sizeof(MgmtHdr); + const int remainingPackageSize = data.length() - nextPackageSize; + + if (data.length() < nextPackageSize) + break; // not a complete event header -> wait for next notifier + + switch (static_cast<EventCode>(qFromLittleEndian(hdr->cmdCode))) { + case EventCode::DeviceFound: + { + const MgmtEventDeviceFound *event = reinterpret_cast<const MgmtEventDeviceFound*> + (data.constData() + sizeof(MgmtHdr)); + + if (event->type == BDADDR_LE_RANDOM) { + const bdaddr_t address = event->bdaddr; + quint64 bdaddr; + + convertAddress(address.b, &bdaddr); + const QBluetoothAddress qtAddress(bdaddr); + qCDebug(QT_BT_BLUEZ) << "BluetoothManagement: found random device" + << qtAddress; + processRandomAddressFlagInformation(qtAddress); + } + + break; + } + default: + qCDebug(QT_BT_BLUEZ) << "BluetoothManagement: Ignored event:" + << hex << qFromLittleEndian(hdr->cmdCode); + break; + } + + if (data.length() > nextPackageSize) + data = data.right(remainingPackageSize); + else + data.clear(); + + if (data.isEmpty()) + break; + } + + if (!data.isEmpty()) + buffer.ungetBlock(data.constData(), data.size()); +} + +void BluetoothManagement::processRandomAddressFlagInformation(const QBluetoothAddress &address) +{ + // insert or update + QMutexLocker locker(&accessLock); + privateFlagAddresses[address] = QDateTime::currentDateTimeUtc(); +} + +/* + * Ensure that private address cache is not older than 24h. + */ +void BluetoothManagement::cleanupOldAddressFlags() +{ + const auto cutOffTime = QDateTime::currentDateTimeUtc().addDays(-1); + + QMutexLocker locker(&accessLock); + + auto i = privateFlagAddresses.begin(); + while (i != privateFlagAddresses.end()) { + if (i.value() < cutOffTime) + i = privateFlagAddresses.erase(i); + else + i++; + } +} + +bool BluetoothManagement::isAddressRandom(const QBluetoothAddress &address) const +{ + if (fd == -1 || address.isNull()) + return false; + + QMutexLocker locker(&accessLock); + return privateFlagAddresses.contains(address); +} + +bool BluetoothManagement::isMonitoringEnabled() const +{ + return (fd == -1) ? false : true; +} + + +QT_END_NAMESPACE diff --git a/src/bluetooth/bluez/bluetoothmanagement_p.h b/src/bluetooth/bluez/bluetoothmanagement_p.h new file mode 100644 index 00000000..954f6e03 --- /dev/null +++ b/src/bluetooth/bluez/bluetoothmanagement_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 BLUETOOTHMANAGEMENT_P_H +#define BLUETOOTHMANAGEMENT_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/qdatetime.h> +#include <QtCore/qmutex.h> +#include <QtCore/qobject.h> + +#include <QtBluetooth/qbluetoothaddress.h> + +#ifndef QPRIVATELINEARBUFFER_BUFFERSIZE +#define QPRIVATELINEARBUFFER_BUFFERSIZE Q_INT64_C(16384) +#endif +#include "../qprivatelinearbuffer_p.h" + +QT_BEGIN_NAMESPACE + +class QSocketNotifier; + +class BluetoothManagement : public QObject +{ + Q_OBJECT + +public: + explicit BluetoothManagement(QObject *parent = nullptr); + static BluetoothManagement *instance(); + + bool isAddressRandom(const QBluetoothAddress &address) const; + bool isMonitoringEnabled() const; + +private slots: + void _q_readNotifier(); + void processRandomAddressFlagInformation(const QBluetoothAddress &address); + void cleanupOldAddressFlags(); + +private: + void readyRead(); + + int fd = -1; + QSocketNotifier* notifier; + QPrivateLinearBuffer buffer; + QHash<QBluetoothAddress, QDateTime> privateFlagAddresses; + mutable QMutex accessLock; +}; + + +QT_END_NAMESPACE + +#endif // BLUETOOTHMANAGEMENT_P_H diff --git a/src/bluetooth/bluez/bluez.pri b/src/bluetooth/bluez/bluez.pri index 3ff2e9d8..b99f2712 100644 --- a/src/bluetooth/bluez/bluez.pri +++ b/src/bluetooth/bluez/bluez.pri @@ -19,7 +19,8 @@ HEADERS += bluez/manager_p.h \ bluez/obex_transfer1_bluez5_p.h \ bluez/bluez_data_p.h \ bluez/hcimanager_p.h \ - bluez/remotedevicemanager_p.h + bluez/remotedevicemanager_p.h \ + bluez/bluetoothmanagement_p.h SOURCES += bluez/manager.cpp \ bluez/adapter.cpp \ @@ -41,4 +42,5 @@ SOURCES += bluez/manager.cpp \ bluez/obex_objectpush1_bluez5.cpp \ bluez/obex_transfer1_bluez5.cpp \ bluez/hcimanager.cpp \ - bluez/remotedevicemanager.cpp + bluez/remotedevicemanager.cpp \ + bluez/bluetoothmanagement.cpp diff --git a/src/bluetooth/bluez/bluez_data_p.h b/src/bluetooth/bluez/bluez_data_p.h index 25edc661..684cd5b8 100644 --- a/src/bluetooth/bluez/bluez_data_p.h +++ b/src/bluetooth/bluez/bluez_data_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -204,7 +204,10 @@ template<> inline void putBtData(quint128 src, void *dst) // HCI related -#define HCI_MAX_DEV 16 +#define HCI_MAX_DEV 16 +#define HCI_DEV_NONE 0xffff + +#define HCI_CHANNEL_CONTROL 0x3 #define HCI_MAX_EVENT_SIZE 260 diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent.cpp index b033ae3c..02ea30d1 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent.cpp @@ -143,7 +143,15 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT) information via \l discoveredDevices() once the discovery has finished. This will yield the most recent RSSI information. - \sa QBluetoothDeviceInfo::rssi() + If \l lowEnergyDiscoveryTimeout() is larger than 0 the signal is only ever + emitted when at least one attribute of \a info changes. This reflects the desire to + receive updates as more precise information becomes available. The exception to this + behavior is the case when \l lowEnergyDiscoveryTimeout is set to \c 0. A timeout of \c 0 + expresses the desire to monitor the appearance and disappearance of Low Energy devices + over time. Under this condition the \l deviceDiscovered() signal is emitted even if + \a info has not changed since the last signal emission. + + \sa QBluetoothDeviceInfo::rssi(), lowEnergyDiscoveryTimeout() */ /*! diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp index 3d34b477..0d6c86f9 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp @@ -304,7 +304,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::processDiscoveredDevices( for (int i = 0; i < discoveredDevices.size(); i++) { if (discoveredDevices[i].address() == info.address()) { - if (discoveredDevices[i] == info) { + if (discoveredDevices[i] == info && lowEnergySearchTimeout > 0) { qCDebug(QT_BT_ANDROID) << "Duplicate: " << info.address() << "isLeScanResult:" << isLeResult; return; diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp index cda1bd17..f2ca4233 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp @@ -51,6 +51,7 @@ #include "bluez/adapter1_bluez5_p.h" #include "bluez/device1_bluez5_p.h" #include "bluez/properties_p.h" +#include "bluez/bluetoothmanagement_p.h" QT_BEGIN_NAMESPACE @@ -82,6 +83,8 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( SIGNAL(InterfacesAdded(QDBusObjectPath,InterfaceList)), q, SLOT(_q_InterfacesAdded(QDBusObjectPath,InterfaceList))); + // start private address monitoring + BluetoothManagement::instance(); } else { manager = new OrgBluezManagerInterface(QStringLiteral("org.bluez"), QStringLiteral("/"), QDBusConnection::systemBus(), parent); @@ -430,7 +433,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFoundBluez5(const QString& dev for (int i = 0; i < discoveredDevices.size(); i++) { if (discoveredDevices[i].address() == deviceInfo.address()) { - if (discoveredDevices[i] == deviceInfo) { + if (discoveredDevices[i] == deviceInfo && lowEnergySearchTimeout > 0) { qCDebug(QT_BT_BLUEZ) << "Duplicate: " << btAddress.toString(); return; } diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm index b308f7cc..9e3f6a57 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm @@ -525,7 +525,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(const QBluetoothDeviceIn for (int i = 0, e = discoveredDevices.size(); i < e; ++i) { if (isLE ? discoveredDevices[i].deviceUuid() == newDeviceInfo.deviceUuid(): discoveredDevices[i].address() == newDeviceInfo.address()) { - if (discoveredDevices[i] == newDeviceInfo) + if (discoveredDevices[i] == newDeviceInfo && (!isLE || lowEnergySearchTimeout > 0)) return; discoveredDevices.replace(i, newDeviceInfo); diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h index 45764c1a..7b57abb2 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h @@ -163,7 +163,6 @@ private: private slots: void registerDevice(const QBluetoothDeviceInfo &info); void onScanFinished(); - void onScanCanceled(); private: void disconnectAndClearWorker(); diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp index d8d68d4b..1aaaf0a4 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp @@ -109,7 +109,6 @@ public slots: Q_SIGNALS: void deviceFound(const QBluetoothDeviceInfo &info); void scanFinished(); - void scanCanceled(); public: quint8 requestedModes; @@ -250,10 +249,7 @@ void QWinRTBluetoothDeviceDiscoveryWorker::setupLEDeviceWatcher() void QWinRTBluetoothDeviceDiscoveryWorker::handleLeTimeout() { - if (m_pendingPairedDevices == 0) - emit scanFinished(); - else - emit scanCanceled(); + emit scanFinished(); deleteLater(); } @@ -552,8 +548,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent this, &QBluetoothDeviceDiscoveryAgentPrivate::registerDevice); connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished, this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished); - connect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanCanceled, - this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled); worker->start(); if (lowEnergySearchTimeout > 0 && methods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) { // otherwise no timeout and stop() required @@ -613,21 +607,12 @@ void QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished() emit q->finished(); } -void QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled() -{ - Q_Q(QBluetoothDeviceDiscoveryAgent); - disconnectAndClearWorker(); - emit q->canceled(); -} - void QBluetoothDeviceDiscoveryAgentPrivate::disconnectAndClearWorker() { Q_Q(QBluetoothDeviceDiscoveryAgent); if (!worker) return; - disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanCanceled, - this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanCanceled); disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::scanFinished, this, &QBluetoothDeviceDiscoveryAgentPrivate::onScanFinished); disconnect(worker, &QWinRTBluetoothDeviceDiscoveryWorker::deviceFound, diff --git a/src/bluetooth/qbluetoothservicediscoveryagent.cpp b/src/bluetooth/qbluetoothservicediscoveryagent.cpp index 8998d608..7daab4b7 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent.cpp +++ b/src/bluetooth/qbluetoothservicediscoveryagent.cpp @@ -442,7 +442,7 @@ void QBluetoothServiceDiscoveryAgentPrivate::startDeviceDiscovery() setDiscoveryState(DeviceDiscovery); - deviceDiscoveryAgent->start(); + deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::ClassicMethod); } /*! diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp index ba5bcb0a..51db091e 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp +++ b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp @@ -244,6 +244,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_processFetchedUuids( //could not find any service for the current address/device -> go to next one if (address.isNull() || uuids.isEmpty()) { + if (discoveredDevices.count() == 1) { + Q_Q(QBluetoothServiceDiscoveryAgent); + QTimer::singleShot(4000, q, SLOT(_q_fetchUuidsTimeout())); + } _q_serviceDiscoveryFinished(); return; } diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm index 4a52b379..25bb2447 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm +++ b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm @@ -141,7 +141,7 @@ void QBluetoothServiceDiscoveryAgentPrivate::startDeviceDiscovery() state = DeviceDiscovery; setupDeviceDiscoveryAgent(); - deviceDiscoveryAgent->start(); + deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::ClassicMethod); } void QBluetoothServiceDiscoveryAgentPrivate::stopDeviceDiscovery() diff --git a/src/bluetooth/qbluetoothsocket.cpp b/src/bluetooth/qbluetoothsocket.cpp index 3e961142..2f38ed04 100644 --- a/src/bluetooth/qbluetoothsocket.cpp +++ b/src/bluetooth/qbluetoothsocket.cpp @@ -300,7 +300,7 @@ qint64 QBluetoothSocket::bytesAvailable() const qint64 QBluetoothSocket::bytesToWrite() const { Q_D(const QBluetoothSocket); - return d->txBuffer.size(); + return d->bytesToWrite(); } /*! @@ -624,7 +624,7 @@ void QBluetoothSocket::setSocketState(QBluetoothSocket::SocketState state) bool QBluetoothSocket::canReadLine() const { Q_D(const QBluetoothSocket); - return d->buffer.canReadLine() || QIODevice::canReadLine(); + return d->canReadLine(); } /*! diff --git a/src/bluetooth/qbluetoothsocket_android.cpp b/src/bluetooth/qbluetoothsocket_android.cpp index 56d4f77b..d0b901ae 100644 --- a/src/bluetooth/qbluetoothsocket_android.cpp +++ b/src/bluetooth/qbluetoothsocket_android.cpp @@ -46,6 +46,7 @@ #include <QtCore/QTime> #include <QtCore/private/qjni_p.h> #include <QtAndroidExtras/QAndroidJniEnvironment> +#include <QtAndroid> QT_BEGIN_NAMESPACE @@ -56,6 +57,7 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID) Q_DECLARE_METATYPE(QAndroidJniObject) +Q_BLUETOOTH_EXPORT bool useReverseUuidWorkAroundConnect = true; /* BluetoothSocket.connect() can block up to 10s. Therefore it must be * in a separate thread. Unfortunately if BluetoothSocket.close() is @@ -78,10 +80,12 @@ class SocketConnectWorker : public QObject Q_OBJECT public: SocketConnectWorker(const QAndroidJniObject& socket, - const QAndroidJniObject& targetUuid) + const QAndroidJniObject& targetUuid, + const QBluetoothUuid& qtTargetUuid) : QObject(), mSocketObject(socket), - mTargetUuid(targetUuid) + mTargetUuid(targetUuid), + mQtTargetUuid(qtTargetUuid) { static int t = qRegisterMetaType<QAndroidJniObject>(); Q_UNUSED(t); @@ -90,7 +94,8 @@ public: signals: void socketConnectDone(const QAndroidJniObject &socket); void socketConnectFailed(const QAndroidJniObject &socket, - const QAndroidJniObject &targetUuid); + const QAndroidJniObject &targetUuid, + const QBluetoothUuid &qtUuid); public slots: void connectSocket() { @@ -102,7 +107,7 @@ public slots: env->ExceptionDescribe(); env->ExceptionClear(); - emit socketConnectFailed(mSocketObject, mTargetUuid); + emit socketConnectFailed(mSocketObject, mTargetUuid, mQtTargetUuid); QThread::currentThread()->quit(); return; } @@ -130,6 +135,8 @@ public slots: private: QAndroidJniObject mSocketObject; QAndroidJniObject mTargetUuid; + // same as mTargetUuid above - just the Qt C++ version rather than jni uuid + QBluetoothUuid mQtTargetUuid; }; class WorkerThread: public QThread @@ -143,10 +150,11 @@ public: // Runs in same thread as QBluetoothSocketPrivate void setupWorker(QBluetoothSocketPrivate* d_ptr, const QAndroidJniObject& socketObject, - const QAndroidJniObject& uuidObject, bool useFallback) + const QAndroidJniObject& uuidObject, bool useFallback, + const QBluetoothUuid& qtUuid = QBluetoothUuid()) { SocketConnectWorker* worker = new SocketConnectWorker( - socketObject, uuidObject); + socketObject, uuidObject, qtUuid); worker->moveToThread(this); connect(this, &QThread::finished, worker, &QObject::deleteLater); @@ -172,6 +180,30 @@ private: QPointer<SocketConnectWorker> workerPointer; }; +/* + * This function is part of a workaround for QTBUG-61392 + * + * Returns null uuid if the given \a serviceUuid is not a uuid + * derived from the Bluetooth base uuid. + */ +static QBluetoothUuid reverseUuid(const QBluetoothUuid &serviceUuid) +{ + if (serviceUuid.isNull()) + return QBluetoothUuid(); + + bool isBaseUuid = false; + serviceUuid.toUInt32(&isBaseUuid); + if (isBaseUuid) + return QBluetoothUuid(); + + const quint128 original = serviceUuid.toUInt128(); + quint128 reversed; + for (int i = 0; i < 16; i++) + reversed.data[15-i] = original.data[i]; + + return QBluetoothUuid(reversed); +} + QBluetoothSocketPrivate::QBluetoothSocketPrivate() : socket(-1), socketType(QBluetoothServiceInfo::UnknownProtocol), @@ -206,7 +238,7 @@ bool QBluetoothSocketPrivate::ensureNativeSocket(QBluetoothServiceInfo::Protocol bool QBluetoothSocketPrivate::fallBackConnect(QAndroidJniObject uuid, int channel) { - qCWarning(QT_BT_ANDROID) << "Falling back to workaround."; + qCWarning(QT_BT_ANDROID) << "Falling back to getServiceChannel() workaround."; QAndroidJniEnvironment env; @@ -320,6 +352,60 @@ bool QBluetoothSocketPrivate::fallBackConnect(QAndroidJniObject uuid, int channe return true; } +/* + * Workaround for QTBUG-61392 + */ +bool QBluetoothSocketPrivate::fallBackReversedConnect(const QBluetoothUuid &uuid) +{ + Q_Q(QBluetoothSocket); + + qCWarning(QT_BT_ANDROID) << "Falling back to reverse uuid workaround."; + const QBluetoothUuid reverse = reverseUuid(uuid); + if (reverse.isNull()) + return false; + + //cut leading { and trailing } {xxx-xxx} + QString tempUuid = reverse.toString(); + tempUuid.chop(1); //remove trailing '}' + tempUuid.remove(0, 1); //remove first '{' + + QAndroidJniEnvironment env; + const QAndroidJniObject inputString = QAndroidJniObject::fromString(tempUuid); + const QAndroidJniObject uuidObject = QAndroidJniObject::callStaticObjectMethod("java/util/UUID", "fromString", + "(Ljava/lang/String;)Ljava/util/UUID;", + inputString.object<jstring>()); + + if (secFlags == QBluetooth::NoSecurity) { + qCDebug(QT_BT_ANDROID) << "Connnecting via insecure rfcomm"; + socketObject = remoteDevice.callObjectMethod("createInsecureRfcommSocketToServiceRecord", + "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", + uuidObject.object<jobject>()); + } else { + qCDebug(QT_BT_ANDROID) << "Connnecting via secure rfcomm"; + socketObject = remoteDevice.callObjectMethod("createRfcommSocketToServiceRecord", + "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", + uuidObject.object<jobject>()); + } + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + + socketObject = remoteDevice = QAndroidJniObject(); + errorString = QBluetoothSocket::tr("Cannot connect to %1", + "%1 = uuid").arg(reverse.toString()); + q->setSocketError(QBluetoothSocket::ServiceNotFoundError); + q->setSocketState(QBluetoothSocket::UnconnectedState); + return false; + } + + WorkerThread *workerThread = new WorkerThread(); + workerThread->setupWorker(this, socketObject, uuidObject, USE_FALLBACK); + workerThread->start(); + emit connectJavaSocket(); + + return true; +} /* * The call order during a connectToService() is as follows: @@ -329,11 +415,14 @@ bool QBluetoothSocketPrivate::fallBackConnect(QAndroidJniObject uuid, int channe * 3. if threaded connect succeeds call socketConnectSuccess() via signals * -> done * 4. if threaded connect fails call defaultSocketConnectFailed() via signals - * 5. call fallBackConnect() - * 6. if threaded connect on fallback channel succeeds call socketConnectSuccess() + * 5. call fallBackConnect() if Android version 22 or below + * -> Android 23+ complete failure of entire connectToService() + * 6. call fallBackReversedConnect() if Android version 23 or above + * -> if failure entire connectToService() fails + * 7. if threaded connect on one of above fallbacks succeeds call socketConnectSuccess() * via signals * -> done - * 7. if threaded connect on fallback channel fails call fallbackSocketConnectFailed() + * 8. if threaded connect on fallback channel fails call fallbackSocketConnectFailed() * -> complete failure of entire connectToService() * */ void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, @@ -414,7 +503,7 @@ void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, } WorkerThread *workerThread = new WorkerThread(); - workerThread->setupWorker(this, socketObject, uuidObject, !USE_FALLBACK); + workerThread->setupWorker(this, socketObject, uuidObject, !USE_FALLBACK, uuid); workerThread->start(); emit connectJavaSocket(); } @@ -480,7 +569,8 @@ void QBluetoothSocketPrivate::socketConnectSuccess(const QAndroidJniObject &sock } void QBluetoothSocketPrivate::defaultSocketConnectFailed( - const QAndroidJniObject &socket, const QAndroidJniObject &targetUuid) + const QAndroidJniObject &socket, const QAndroidJniObject &targetUuid, + const QBluetoothUuid &qtTargetUuid) { Q_Q(QBluetoothSocket); @@ -489,7 +579,12 @@ void QBluetoothSocketPrivate::defaultSocketConnectFailed( if (socket != socketObject) return; - bool success = fallBackConnect(targetUuid, FALLBACK_CHANNEL); + bool success = false; + if (QtAndroid::androidSdkVersion() <= 22) + success = fallBackConnect(targetUuid, FALLBACK_CHANNEL); + else if (useReverseUuidWorkAroundConnect) // version 23+ has Android bug (see QTBUG-61392) + success = fallBackReversedConnect(qtTargetUuid); + if (!success) { errorString = QBluetoothSocket::tr("Connection to service failed"); socketObject = remoteDevice = QAndroidJniObject(); @@ -627,7 +722,6 @@ qint64 QBluetoothSocketPrivate::writeData(const char *data, qint64 maxSize) env->SetByteArrayRegion(nativeData, 0, (qint32)maxSize, reinterpret_cast<const jbyte*>(data)); outputStream.callMethod<void>("write", "([BII)V", nativeData, 0, (qint32)maxSize); env->DeleteLocalRef(nativeData); - emit q->bytesWritten(maxSize); if (env->ExceptionCheck()) { qCWarning(QT_BT_ANDROID) << "Error while writing"; @@ -638,6 +732,7 @@ qint64 QBluetoothSocketPrivate::writeData(const char *data, qint64 maxSize) return -1; } + emit q->bytesWritten(maxSize); return maxSize; } @@ -784,6 +879,20 @@ qint64 QBluetoothSocketPrivate::bytesAvailable() const return 0; } +qint64 QBluetoothSocketPrivate::bytesToWrite() const +{ + return 0; // nothing because always unbuffered +} + +bool QBluetoothSocketPrivate::canReadLine() const +{ + // We cannot access buffer directly as it is part of different thread + if (inputThread) + return inputThread->canReadLine(); + + return false; +} + QT_END_NAMESPACE #include <qbluetoothsocket_android.moc> diff --git a/src/bluetooth/qbluetoothsocket_bluez.cpp b/src/bluetooth/qbluetoothsocket_bluez.cpp index 42c5503b..6aef811a 100644 --- a/src/bluetooth/qbluetoothsocket_bluez.cpp +++ b/src/bluetooth/qbluetoothsocket_bluez.cpp @@ -592,4 +592,14 @@ qint64 QBluetoothSocketPrivate::bytesAvailable() const return buffer.size(); } +qint64 QBluetoothSocketPrivate::bytesToWrite() const +{ + return txBuffer.size(); +} + +bool QBluetoothSocketPrivate::canReadLine() const +{ + return buffer.canReadLine(); +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothsocket_p.cpp b/src/bluetooth/qbluetoothsocket_p.cpp index 6007b924..39d483d6 100644 --- a/src/bluetooth/qbluetoothsocket_p.cpp +++ b/src/bluetooth/qbluetoothsocket_p.cpp @@ -158,4 +158,14 @@ qint64 QBluetoothSocketPrivate::bytesAvailable() const return 0; } +bool QBluetoothSocketPrivate::canReadLine() const +{ + return false; +} + +qint64 QBluetoothSocketPrivate::bytesToWrite() const +{ + return 0; +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothsocket_p.h b/src/bluetooth/qbluetoothsocket_p.h index 956f8f02..9aabf660 100644 --- a/src/bluetooth/qbluetoothsocket_p.h +++ b/src/bluetooth/qbluetoothsocket_p.h @@ -127,6 +127,7 @@ public: #endif #ifdef QT_ANDROID_BLUETOOTH bool fallBackConnect(QAndroidJniObject uuid, int channel); + bool fallBackReversedConnect(const QBluetoothUuid &uuid); #endif @@ -165,6 +166,8 @@ public: QBluetoothSocket::OpenMode openMode = QBluetoothSocket::ReadWrite); qint64 bytesAvailable() const; + bool canReadLine() const; + qint64 bytesToWrite() const; public: QPrivateLinearBuffer buffer; @@ -197,7 +200,8 @@ public: public slots: void socketConnectSuccess(const QAndroidJniObject &socket); void defaultSocketConnectFailed(const QAndroidJniObject & socket, - const QAndroidJniObject &targetUuid); + const QAndroidJniObject &targetUuid, + const QBluetoothUuid &qtTargetUuid); void fallbackSocketConnectFailed(const QAndroidJniObject &socket, const QAndroidJniObject &targetUuid); void inputThreadError(int errorCode); @@ -263,7 +267,7 @@ public slots: #endif // QT_OSX_BLUETOOTH -static inline void convertAddress(quint64 from, quint8 (&to)[6]) +static inline void convertAddress(const quint64 from, quint8 (&to)[6]) { to[0] = (from >> 0) & 0xff; to[1] = (from >> 8) & 0xff; @@ -273,7 +277,7 @@ static inline void convertAddress(quint64 from, quint8 (&to)[6]) to[5] = (from >> 40) & 0xff; } -static inline quint64 convertAddress(quint8 (&from)[6], quint64 *to = 0) +static inline quint64 convertAddress(const quint8 (&from)[6], quint64 *to = 0) { const quint64 result = (quint64(from[0]) << 0) | (quint64(from[1]) << 8) | @@ -286,6 +290,15 @@ static inline quint64 convertAddress(quint8 (&from)[6], quint64 *to = 0) return result; } +#ifdef Q_OS_ANDROID +// QTBUG-61392 related +// Private API to disable the silent behavior to reverse a remote service's +// UUID. In rare cases the workaround behavior might not be desirable as +// it may lead to connects to incorrect services. +extern bool useReverseUuidWorkAroundConnect; + +#endif + QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothsocket_winrt.cpp b/src/bluetooth/qbluetoothsocket_winrt.cpp index 1f4e6233..4a9d1b93 100644 --- a/src/bluetooth/qbluetoothsocket_winrt.cpp +++ b/src/bluetooth/qbluetoothsocket_winrt.cpp @@ -569,6 +569,16 @@ qint64 QBluetoothSocketPrivate::bytesAvailable() const return buffer.size(); } +qint64 QBluetoothSocketPrivate::bytesToWrite() const +{ + return 0; // nothing because always unbuffered +} + +bool QBluetoothSocketPrivate::canReadLine() const +{ + return buffer.canReadLine(); +} + void QBluetoothSocketPrivate::handleNewData(const QVector<QByteArray> &data) { // Defer putting the data into the list until the next event loop iteration diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp index a3aad282..053ca980 100644 --- a/src/bluetooth/qlowenergycontroller.cpp +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -691,6 +691,19 @@ QLowEnergyController::RemoteAddressType QLowEnergyController::remoteAddressType( /*! Sets the remote address \a type. The type is required to connect to the remote Bluetooth Low Energy device. + + This attribute is only required to be set on Linux/BlueZ systems with older + Linux kernels (v3.3 or lower), or if CAP_NET_ADMIN is not set for the executable. + The default value of the attribute is \l RandomAddress. + + \note All other platforms handle this flag transparently and therefore applications + can ignore it entirely. On Linux, the address type flag is not directly exposed + by BlueZ although some use cases do require this information. The only way to detect + the flag is via the Linux kernel's Bluetooth Management API (kernel + version 3.4+ required). This API requires CAP_NET_ADMIN capabilities though. If the + local QtBluetooth process has this capability set QtBluetooth will use the API. This + assumes that \l QBluetoothDeviceDiscoveryAgent was used prior to calling + \l QLowEnergyController::connectToDevice(). */ void QLowEnergyController::setRemoteAddressType( QLowEnergyController::RemoteAddressType type) @@ -918,13 +931,24 @@ QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData return nullptr; } + Q_D(QLowEnergyController); + QLowEnergyService *newService = d->addServiceHelper(service); + if (newService) + newService->setParent(parent); + + return newService; +} + +QLowEnergyService *QLowEnergyControllerPrivate::addServiceHelper( + const QLowEnergyServiceData &service) +{ // Spec says services "should" be grouped by uuid length (16-bit first, then 128-bit). // Since this is not mandatory, we ignore it here and let the caller take responsibility // for it. const auto servicePrivate = QSharedPointer<QLowEnergyServicePrivate>::create(); servicePrivate->state = QLowEnergyService::LocalService; - servicePrivate->setController(d_ptr); + servicePrivate->setController(this); servicePrivate->uuid = service.uuid(); servicePrivate->type = service.type() == QLowEnergyServiceData::ServiceTypePrimary ? QLowEnergyService::PrimaryService : QLowEnergyService::IncludedService; @@ -934,13 +958,13 @@ QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData } // Spec v4.2, Vol 3, Part G, Section 3. - const QLowEnergyHandle oldLastHandle = d_ptr->lastLocalHandle; - servicePrivate->startHandle = ++d_ptr->lastLocalHandle; // Service declaration. - d_ptr->lastLocalHandle += servicePrivate->includedServices.count(); // Include declarations. + const QLowEnergyHandle oldLastHandle = this->lastLocalHandle; + servicePrivate->startHandle = ++this->lastLocalHandle; // Service declaration. + this->lastLocalHandle += servicePrivate->includedServices.count(); // Include declarations. foreach (const QLowEnergyCharacteristicData &cd, service.characteristics()) { - const QLowEnergyHandle declHandle = ++d_ptr->lastLocalHandle; + const QLowEnergyHandle declHandle = ++this->lastLocalHandle; QLowEnergyServicePrivate::CharData charData; - charData.valueHandle = ++d_ptr->lastLocalHandle; + charData.valueHandle = ++this->lastLocalHandle; charData.uuid = cd.uuid(); charData.properties = cd.properties(); charData.value = cd.value(); @@ -948,21 +972,21 @@ QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData QLowEnergyServicePrivate::DescData descData; descData.uuid = dd.uuid(); descData.value = dd.value(); - charData.descriptorList.insert(++d_ptr->lastLocalHandle, descData); + charData.descriptorList.insert(++this->lastLocalHandle, descData); } servicePrivate->characteristicList.insert(declHandle, charData); } - servicePrivate->endHandle = d_ptr->lastLocalHandle; - const bool handleOverflow = d_ptr->lastLocalHandle <= oldLastHandle; + servicePrivate->endHandle = this->lastLocalHandle; + const bool handleOverflow = this->lastLocalHandle <= oldLastHandle; if (handleOverflow) { qCWarning(QT_BT) << "Not enough attribute handles left to create this service"; - d_ptr->lastLocalHandle = oldLastHandle; + this->lastLocalHandle = oldLastHandle; return nullptr; } - d_ptr->localServices.insert(servicePrivate->uuid, servicePrivate); - d_ptr->addToGenericAttributeList(service, servicePrivate->startHandle); - return new QLowEnergyService(servicePrivate, parent); + this->localServices.insert(servicePrivate->uuid, servicePrivate); + this->addToGenericAttributeList(service, servicePrivate->startHandle); + return new QLowEnergyService(servicePrivate); } /*! diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index 1649fe8c..0744bcc4 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -46,6 +46,7 @@ #include "bluez/hcimanager_p.h" #include "bluez/remotedevicemanager_p.h" #include "bluez/bluez5_helper_p.h" +#include "bluez/bluetoothmanagement_p.h" #include <QtCore/QFileInfo> #include <QtCore/QLoggingCategory> @@ -529,6 +530,8 @@ void QLowEnergyControllerPrivate::connectToDevice() if (l2cpSocket) delete l2cpSocket; + createServicesForCentralIfRequired(); + // check for active running connections // BlueZ 5.37+ (maybe even earlier versions) can have pending BTLE connections // Only one active L2CP socket to CID 0x4 possible at a time @@ -598,10 +601,20 @@ void QLowEnergyControllerPrivate::establishL2cpClientSocket() this, SLOT(l2cpErrorChanged(QBluetoothSocket::SocketError))); connect(l2cpSocket, SIGNAL(readyRead()), this, SLOT(l2cpReadyRead())); - if (addressType == QLowEnergyController::PublicAddress) - l2cpSocket->d_ptr->lowEnergySocketType = BDADDR_LE_PUBLIC; - else if (addressType == QLowEnergyController::RandomAddress) - l2cpSocket->d_ptr->lowEnergySocketType = BDADDR_LE_RANDOM; + quint32 addressTypeToUse = (addressType == QLowEnergyController::PublicAddress) + ? BDADDR_LE_PUBLIC : BDADDR_LE_RANDOM; + if (BluetoothManagement::instance()->isMonitoringEnabled()) { + // if monitoring is possible and it's private then we force it to the relevant option + if (BluetoothManagement::instance()->isAddressRandom(remoteDevice)) { + addressTypeToUse = BDADDR_LE_RANDOM; + } + } + + qCDebug(QT_BT_BLUEZ) << "addresstypeToUse:" + << (addressTypeToUse == BDADDR_LE_RANDOM + ? QStringLiteral("Random") : QStringLiteral("Public")); + + l2cpSocket->d_ptr->lowEnergySocketType = addressTypeToUse; int sockfd = l2cpSocket->socketDescriptor(); if (sockfd < 0) { @@ -633,6 +646,72 @@ void QLowEnergyControllerPrivate::establishL2cpClientSocket() loadSigningDataIfNecessary(LocalSigningKey); } +void QLowEnergyControllerPrivate::createServicesForCentralIfRequired() +{ + //only enable when requested + //for now we use env variable to activate the feature + if (Q_LIKELY(!qEnvironmentVariableIsSet("QT_DEFAULT_CENTRAL_SERVICES"))) + return; //nothing to do + + //do not add the services each time we start a connection + if (localServices.contains(QBluetoothUuid(QBluetoothUuid::GenericAccess))) + return; + + qCDebug(QT_BT_BLUEZ) << "Creating default GAP/GATT services"; + + //populate Generic Access service + //for now the values are static + QLowEnergyServiceData gapServiceData; + gapServiceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + gapServiceData.setUuid(QBluetoothUuid::GenericAccess); + + QLowEnergyCharacteristicData gapDeviceName; + gapDeviceName.setUuid(QBluetoothUuid::DeviceName); + gapDeviceName.setProperties(QLowEnergyCharacteristic::Read); + + QBluetoothLocalDevice mainAdapter; + gapDeviceName.setValue(mainAdapter.name().toLatin1()); //static name + + QLowEnergyCharacteristicData gapAppearance; + gapAppearance.setUuid(QBluetoothUuid::Appearance); + gapAppearance.setProperties(QLowEnergyCharacteristic::Read); + gapAppearance.setValue(QByteArray::fromHex("80")); // Generic Computer (0x80) + + QLowEnergyCharacteristicData gapPrivacyFlag; + gapPrivacyFlag.setUuid(QBluetoothUuid::PeripheralPrivacyFlag); + gapPrivacyFlag.setProperties(QLowEnergyCharacteristic::Read); + gapPrivacyFlag.setValue(QByteArray::fromHex("00")); // disable privacy + + gapServiceData.addCharacteristic(gapDeviceName); + gapServiceData.addCharacteristic(gapAppearance); + gapServiceData.addCharacteristic(gapPrivacyFlag); + + Q_Q(QLowEnergyController); + QLowEnergyService *service = addServiceHelper(gapServiceData); + if (service) + service->setParent(q); + + QLowEnergyServiceData gattServiceData; + gattServiceData.setType(QLowEnergyServiceData::ServiceTypePrimary); + gattServiceData.setUuid(QBluetoothUuid::GenericAttribute); + + QLowEnergyCharacteristicData serviceChangedChar; + serviceChangedChar.setUuid(QBluetoothUuid::ServiceChanged); + serviceChangedChar.setProperties(QLowEnergyCharacteristic::Indicate); + //arbitrary range of 2 bit handle range (1-4 + serviceChangedChar.setValue(QByteArray::fromHex("0104")); + + const QLowEnergyDescriptorData clientConfig( + QBluetoothUuid::ClientCharacteristicConfiguration, + QByteArray(2, 0)); + serviceChangedChar.addDescriptor(clientConfig); + gattServiceData.addCharacteristic(serviceChangedChar); + + service = addServiceHelper(gattServiceData); + if (service) + service->setParent(q); +} + void QLowEnergyControllerPrivate::l2cpConnected() { Q_Q(QLowEnergyController); @@ -1665,9 +1744,9 @@ void QLowEnergyControllerPrivate::readServiceValuesByOffset( { const QLowEnergyHandle charHandle = (handleData & 0xffff); const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff); - quint8 packet[READ_REQUEST_HEADER_SIZE]; - packet[0] = ATT_OP_READ_BLOB_REQUEST; + QByteArray data(READ_BLOB_REQUEST_HEADER_SIZE, Qt::Uninitialized); + data[0] = ATT_OP_READ_BLOB_REQUEST; QLowEnergyHandle handleToRead = charHandle; if (descriptorHandle) { @@ -1688,11 +1767,8 @@ void QLowEnergyControllerPrivate::readServiceValuesByOffset( } } - putBtData(handleToRead, &packet[1]); - putBtData(offset, &packet[3]); - - QByteArray data(READ_BLOB_REQUEST_HEADER_SIZE, Qt::Uninitialized); - memcpy(data.data(), packet, READ_BLOB_REQUEST_HEADER_SIZE); + putBtData(handleToRead, data.data() + 1); + putBtData(offset, data.data() + 3); Request request; request.payload = data; diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index 4b0c05cc..3f220fe6 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -147,6 +147,8 @@ public: QLowEnergyHandle handle); QLowEnergyDescriptor descriptorForHandle( QLowEnergyHandle handle); + QLowEnergyService *addServiceHelper(const QLowEnergyServiceData &service); + quint16 updateValueOfCharacteristic(QLowEnergyHandle charHandle, const QByteArray &value, @@ -416,6 +418,7 @@ private: void restartRequestTimer(); void establishL2cpClientSocket(); + void createServicesForCentralIfRequired(); private slots: void l2cpConnected(); |