summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@digia.com>2014-09-26 08:28:40 +0200
committerAlex Blasche <alexander.blasche@digia.com>2014-09-26 08:29:12 +0200
commitc1aabdba2e839ba525a4918eebfbf6fbbe96d34d (patch)
treed154275f00d6a8cbcf792f298654fb6ab12f6044
parentdc34c7aae7d4d641f4d06990141c7915542363ee (diff)
parentb7f4825cbf24c357d2f562ecdd81bc5109f4439f (diff)
Merge branch '5.4' into btle
-rw-r--r--examples/nfc/poster/poster.qml4
-rw-r--r--src/bluetooth/bluez/bluez.pri6
-rw-r--r--src/bluetooth/bluez/bluez_data_p.h156
-rw-r--r--src/bluetooth/bluez/hcimanager.cpp286
-rw-r--r--src/bluetooth/bluez/hcimanager_p.h90
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_android.cpp8
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp22
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_qnx.cpp19
-rw-r--r--src/bluetooth/qbluetoothserviceinfo_android.cpp2
-rw-r--r--src/bluetooth/qbluetoothtransferreply.cpp27
-rw-r--r--src/bluetooth/qbluetoothtransferreply.h3
-rw-r--r--src/bluetooth/qbluetoothtransferreply_bluez.cpp34
-rw-r--r--src/bluetooth/qbluetoothtransferreply_p.h4
-rw-r--r--src/bluetooth/qbluetoothtransferreply_qnx.cpp11
-rw-r--r--src/bluetooth/qbluetoothuuid.cpp2
-rw-r--r--src/bluetooth/qlowenergycharacteristic.cpp10
-rw-r--r--src/bluetooth/qlowenergycontroller.cpp4
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp466
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h19
-rw-r--r--src/bluetooth/qlowenergydescriptor.cpp2
-rw-r--r--src/bluetooth/qlowenergyservice.cpp10
-rw-r--r--src/imports/bluetooth/qdeclarativebluetoothservice.cpp6
-rw-r--r--tests/auto/qbluetoothtransfermanager/tst_qbluetoothtransfermanager.cpp8
-rw-r--r--tests/auto/qlowenergycharacteristic/tst_qlowenergycharacteristic.cpp2
-rw-r--r--tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp74
-rw-r--r--tests/auto/qlowenergydescriptor/tst_qlowenergydescriptor.cpp2
26 files changed, 1177 insertions, 100 deletions
diff --git a/examples/nfc/poster/poster.qml b/examples/nfc/poster/poster.qml
index c50c702b..e2c67dd9 100644
--- a/examples/nfc/poster/poster.qml
+++ b/examples/nfc/poster/poster.qml
@@ -50,8 +50,8 @@ Rectangle {
id: nearfield
filter: [
- NdefFilter { type: "U"; typeNameFormat: NearFiledRecord.NfcRtd; minimum: 1; maximum: 1 },
- NdefFilter { type: "T"; typeNameFormat: NearFiledRecord.NfcRtd; minimum: 1 }
+ NdefFilter { type: "U"; typeNameFormat: NdefRecord.NfcRtd; minimum: 1; maximum: 1 },
+ NdefFilter { type: "T"; typeNameFormat: NdefRecord.NfcRtd; minimum: 1 }
]
onMessageRecordsChanged: {
diff --git a/src/bluetooth/bluez/bluez.pri b/src/bluetooth/bluez/bluez.pri
index be5a02a7..46727cbc 100644
--- a/src/bluetooth/bluez/bluez.pri
+++ b/src/bluetooth/bluez/bluez.pri
@@ -17,7 +17,8 @@ HEADERS += bluez/manager_p.h \
bluez/obex_client1_bluez5_p.h \
bluez/obex_objectpush1_bluez5_p.h \
bluez/obex_transfer1_bluez5_p.h \
- bluez/bluez_data_p.h
+ bluez/bluez_data_p.h \
+ bluez/hcimanager_p.h
SOURCES += bluez/manager.cpp \
bluez/adapter.cpp \
@@ -37,4 +38,5 @@ SOURCES += bluez/manager.cpp \
bluez/profile1.cpp \
bluez/obex_client1_bluez5.cpp \
bluez/obex_objectpush1_bluez5.cpp \
- bluez/obex_transfer1_bluez5.cpp
+ bluez/obex_transfer1_bluez5.cpp \
+ bluez/hcimanager.cpp
diff --git a/src/bluetooth/bluez/bluez_data_p.h b/src/bluetooth/bluez/bluez_data_p.h
index 743f09dc..7c799977 100644
--- a/src/bluetooth/bluez/bluez_data_p.h
+++ b/src/bluetooth/bluez/bluez_data_p.h
@@ -50,10 +50,15 @@
#include <QtBluetooth/QBluetoothUuid>
#define BTPROTO_L2CAP 0
+#define BTPROTO_HCI 1
#define BTPROTO_RFCOMM 3
+#define SOL_HCI 0
#define SOL_L2CAP 6
#define SOL_RFCOMM 18
+#ifndef SOL_BLUETOOTH
+#define SOL_BLUETOOTH 274
+#endif
#define RFCOMM_LM 0x03
@@ -68,9 +73,20 @@
#define L2CAP_LM_TRUSTED 0x0008
#define L2CAP_LM_SECURE 0x0020
+#define BT_SECURITY 4
+struct bt_security {
+ uint8_t level;
+ uint8_t key_size;
+};
+#define BT_SECURITY_SDP 0
+#define BT_SECURITY_LOW 1
+#define BT_SECURITY_MEDIUM 2
+#define BT_SECURITY_HIGH 3
+
#define BDADDR_LE_PUBLIC 0x01
#define BDADDR_LE_RANDOM 0x02
+
/* Byte order conversions */
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define htobs(d) (d)
@@ -174,4 +190,144 @@ static inline void ntoh128(const quint128 *src, quint128 *dst)
#define hton128(x, y) ntoh128(x, y)
+// HCI related
+
+#define HCI_MAX_DEV 16
+
+#define HCI_MAX_EVENT_SIZE 260
+
+// HCI sockopts
+#define HCI_FILTER 2
+
+// HCI packet types
+#define HCI_EVENT_PKT 0x04
+#define HCI_VENDOR_PKT 0xff
+
+#define HCI_FLT_TYPE_BITS 31
+#define HCI_FLT_EVENT_BITS 63
+
+struct sockaddr_hci {
+ sa_family_t hci_family;
+ unsigned short hci_dev;
+ unsigned short hci_channel;
+};
+
+struct hci_dev_req {
+ uint16_t dev_id;
+ uint32_t dev_opt;
+};
+
+struct hci_dev_list_req {
+ uint16_t dev_num;
+ struct hci_dev_req dev_req[0];
+};
+
+struct hci_dev_stats {
+ uint32_t err_rx;
+ uint32_t err_tx;
+ uint32_t cmd_tx;
+ uint32_t evt_rx;
+ uint32_t acl_tx;
+ uint32_t acl_rx;
+ uint32_t sco_tx;
+ uint32_t sco_rx;
+ uint32_t byte_rx;
+ uint32_t byte_tx;
+};
+
+struct hci_dev_info {
+ uint16_t dev_id;
+ char name[8];
+
+ bdaddr_t bdaddr;
+
+ uint32_t flags;
+ uint8_t type;
+
+ uint8_t features[8];
+
+ uint32_t pkt_type;
+ uint32_t link_policy;
+ uint32_t link_mode;
+
+ uint16_t acl_mtu;
+ uint16_t acl_pkts;
+ uint16_t sco_mtu;
+ uint16_t sco_pkts;
+
+ struct hci_dev_stats stat;
+};
+
+struct hci_conn_info {
+ uint16_t handle;
+ bdaddr_t bdaddr;
+ uint8_t type;
+ uint8_t out;
+ uint16_t state;
+ uint32_t link_mode;
+};
+
+struct hci_conn_list_req {
+ uint16_t dev_id;
+ uint16_t conn_num;
+ struct hci_conn_info conn_info[0];
+};
+
+struct hci_filter {
+ uint32_t type_mask;
+ uint32_t event_mask[2];
+ uint16_t opcode;
+};
+
+static inline void hci_set_bit(int nr, void *addr)
+{
+ *((uint32_t *) addr + (nr >> 5)) |= (1 << (nr & 31));
+}
+static inline void hci_clear_bit(int nr, void *addr)
+{
+ *((uint32_t *) addr + (nr >> 5)) &= ~(1 << (nr & 31));
+}
+static inline void hci_filter_clear(struct hci_filter *f)
+{
+ memset(f, 0, sizeof(*f));
+}
+static inline void hci_filter_set_ptype(int t, struct hci_filter *f)
+{
+ hci_set_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask);
+}
+static inline void hci_filter_clear_ptype(int t, struct hci_filter *f)
+{
+ hci_clear_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask);
+}
+static inline void hci_filter_set_event(int e, struct hci_filter *f)
+{
+ hci_set_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask);
+}
+static inline void hci_filter_clear_event(int e, struct hci_filter *f)
+{
+ hci_clear_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask);
+}
+static inline void hci_filter_all_ptypes(struct hci_filter *f)
+{
+ memset((void *) &f->type_mask, 0xff, sizeof(f->type_mask));
+}
+static inline void hci_filter_all_events(struct hci_filter *f)
+{
+ memset((void *) f->event_mask, 0xff, sizeof(f->event_mask));
+}
+
+typedef struct {
+ uint8_t evt;
+ uint8_t plen;
+} __attribute__ ((packed)) hci_event_hdr;
+#define HCI_EVENT_HDR_SIZE 2
+
+#define EVT_ENCRYPT_CHANGE 0x08
+typedef struct {
+ uint8_t status;
+ uint16_t handle;
+ uint8_t encrypt;
+} __attribute__ ((packed)) evt_encrypt_change;
+#define EVT_ENCRYPT_CHANGE_SIZE 4
+
#endif // BLUEZ_DATA_P_H
diff --git a/src/bluetooth/bluez/hcimanager.cpp b/src/bluetooth/bluez/hcimanager.cpp
new file mode 100644
index 00000000..449f0825
--- /dev/null
+++ b/src/bluetooth/bluez/hcimanager.cpp
@@ -0,0 +1,286 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/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 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "hcimanager_p.h"
+
+#include "qbluetoothsocket_p.h"
+
+#include <QtCore/QLoggingCategory>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#define HCIGETCONNLIST _IOR('H', 212, int)
+#define HCIGETDEVINFO _IOR('H', 211, int)
+#define HCIGETDEVLIST _IOR('H', 210, int)
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
+
+HciManager::HciManager(const QBluetoothAddress& deviceAdapter, QObject *parent) :
+ QObject(parent), hciSocket(-1), hciDev(-1), notifier(0)
+{
+ hciSocket = ::socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI);
+ if (hciSocket < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Cannot open HCI socket";
+ return; //TODO error report
+ }
+
+ hciDev = hciForAddress(deviceAdapter);
+ if (hciDev < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Cannot find hci dev for" << deviceAdapter.toString();
+ close(hciSocket);
+ hciSocket = -1;
+ return;
+ }
+
+ struct sockaddr_hci addr;
+
+ memset(&addr, 0, sizeof(struct sockaddr_hci));
+ addr.hci_dev = hciDev;
+ addr.hci_family = AF_BLUETOOTH;
+
+ if (::bind(hciSocket, (struct sockaddr *) (&addr), sizeof(addr)) < 0) {
+ qCWarning(QT_BT_BLUEZ) << "HCI bind failed:" << strerror(errno);
+ close(hciSocket);
+ hciSocket = hciDev = -1;
+ return;
+ }
+
+ notifier = new QSocketNotifier(hciSocket, QSocketNotifier::Read, this);
+ connect(notifier, SIGNAL(activated(int)), this, SLOT(_q_readNotify()));
+
+}
+
+HciManager::~HciManager()
+{
+ if (hciSocket >= 0)
+ ::close(hciSocket);
+
+}
+
+bool HciManager::isValid() const
+{
+ if (hciSocket && hciDev >= 0)
+ return true;
+ return false;
+}
+
+int HciManager::hciForAddress(const QBluetoothAddress &deviceAdapter)
+{
+ if (hciSocket < 0)
+ return -1;
+
+ bdaddr_t adapter;
+ convertAddress(deviceAdapter.toUInt64(), adapter.b);
+
+ struct hci_dev_req *devRequest = 0;
+ struct hci_dev_list_req *devRequestList = 0;
+ struct hci_dev_info devInfo;
+ const int devListSize = sizeof(struct hci_dev_list_req)
+ + HCI_MAX_DEV * sizeof(struct hci_dev_req);
+
+ devRequestList = (hci_dev_list_req *) malloc(devListSize);
+ if (!devRequestList)
+ return -1;
+
+ QScopedPointer<hci_dev_list_req, QScopedPointerPodDeleter> p(devRequestList);
+
+ memset(p.data(), 0, devListSize);
+ p->dev_num = HCI_MAX_DEV;
+ devRequest = p->dev_req;
+
+ if (ioctl(hciSocket, HCIGETDEVLIST, devRequestList) < 0)
+ return -1;
+
+ for (int i = 0; i < devRequestList->dev_num; i++) {
+ devInfo.dev_id = (devRequest+i)->dev_id;
+ if (ioctl(hciSocket, HCIGETDEVINFO, &devInfo) < 0) {
+ continue;
+ }
+
+ int result = memcmp(&adapter, &devInfo.bdaddr, sizeof(bdaddr_t));
+ if (result == 0 || deviceAdapter.isNull()) // addresses match
+ return devRequest->dev_id;
+ }
+
+ return -1;
+}
+
+/*
+ * Returns true if \a event was successfully enabled
+ */
+bool HciManager::monitorEvent(HciManager::HciEvent event)
+{
+ if (!isValid())
+ return false;
+
+ // this event is already enabled
+ if (runningEvents.contains(event))
+ return true;
+
+ hci_filter filter;
+ socklen_t length = sizeof(hci_filter);
+ if (getsockopt(hciSocket, SOL_HCI, HCI_FILTER, &filter, &length) < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Cannot retrieve HCI filter settings";
+ return false;
+ }
+
+ hci_filter_set_ptype(HCI_EVENT_PKT, &filter);
+ hci_filter_set_event(event, &filter);
+ //hci_filter_all_events(&filter);
+
+ if (setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &filter, sizeof(hci_filter)) < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Could not set HCI socket options:" << strerror(errno);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Unsubscribe from all events
+ */
+void HciManager::stopEvents()
+{
+ if (!isValid())
+ return;
+
+ hci_filter filter;
+ hci_filter_clear(&filter);
+
+ if (setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &filter, sizeof(hci_filter)) < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Could not clear HCI socket options:" << strerror(errno);
+ return;
+ }
+
+ runningEvents.clear();
+}
+
+QBluetoothAddress HciManager::addressForConnectionHandle(quint16 handle) const
+{
+ if (!isValid())
+ return QBluetoothAddress();
+
+ hci_conn_info *info;
+ hci_conn_list_req *infoList;
+
+ const int maxNoOfConnections = 20;
+ infoList = (hci_conn_list_req *)
+ malloc(sizeof(hci_conn_list_req) + maxNoOfConnections * sizeof(hci_conn_info));
+
+ if (!infoList)
+ return QBluetoothAddress();
+
+ QScopedPointer<hci_conn_list_req, QScopedPointerPodDeleter> p(infoList);
+ p->conn_num = maxNoOfConnections;
+ p->dev_id = hciDev;
+ info = p->conn_info;
+
+ if (ioctl(hciSocket, HCIGETCONNLIST, (void *) infoList) < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Cannot retrieve connection list";
+ return QBluetoothAddress();
+ }
+
+ for (int i = 0; i < infoList->conn_num; i++) {
+ if (info[i].handle == handle) {
+ quint64 converted;
+ convertAddress(info[i].bdaddr.b, converted);
+
+ return QBluetoothAddress(converted);
+ }
+ }
+
+ return QBluetoothAddress();
+}
+
+/*!
+ * Process all incoming HCI events. Function cannot process anything else but events.
+ */
+void HciManager::_q_readNotify()
+{
+
+ unsigned char buffer[HCI_MAX_EVENT_SIZE];
+ int size;
+
+ size = ::read(hciSocket, buffer, sizeof(buffer));
+ if (size < 0) {
+ if (errno != EAGAIN && errno != EINTR)
+ qCWarning(QT_BT_BLUEZ) << "Failed reading HCI events:" << qt_error_string(errno);
+
+ return;
+ }
+
+ const unsigned char *data = buffer;
+
+ // Not interested in anything but valid HCI events
+ if ((size < HCI_EVENT_HDR_SIZE + 1) || buffer[0] != HCI_EVENT_PKT)
+ return;
+
+ hci_event_hdr *header = (hci_event_hdr *)(&buffer[1]);
+
+ size = size - HCI_EVENT_HDR_SIZE - 1;
+ data = data + HCI_EVENT_HDR_SIZE + 1;
+
+ if (header->plen != size) {
+ qCWarning(QT_BT_BLUEZ) << "Invalid HCI event packet size";
+ return;
+ }
+
+ qCDebug(QT_BT_BLUEZ) << "HCI event triggered, type:" << hex << header->evt;
+
+ switch (header->evt) {
+ case EVT_ENCRYPT_CHANGE:
+ {
+ const evt_encrypt_change *event = (evt_encrypt_change *) data;
+ qCDebug(QT_BT_BLUEZ) << "HCI Encrypt change, status:"
+ << (event->status == 0 ? "Success" : "Failed")
+ << "handle:" << hex << event->handle
+ << "encrypt:" << event->encrypt;
+
+ QBluetoothAddress remoteDevice = addressForConnectionHandle(event->handle);
+ if (!remoteDevice.isNull())
+ emit encryptionChangedEvent(remoteDevice, event->status == 0);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/bluez/hcimanager_p.h b/src/bluetooth/bluez/hcimanager_p.h
new file mode 100644
index 00000000..3a923519
--- /dev/null
+++ b/src/bluetooth/bluez/hcimanager_p.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/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 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef HCIMANAGER_P_H
+#define HCIMANAGER_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 <QObject>
+#include <QtCore/QSet>
+#include <QtCore/QSocketNotifier>
+#include <QtBluetooth/QBluetoothAddress>
+#include "bluez/bluez_data_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class HciManager : public QObject
+{
+ Q_OBJECT
+public:
+ enum HciEvent {
+ EncryptChangeEvent = EVT_ENCRYPT_CHANGE,
+ };
+
+ explicit HciManager(const QBluetoothAddress &deviceAdapter, QObject *parent = 0);
+ ~HciManager();
+
+ bool isValid() const;
+ bool monitorEvent(HciManager::HciEvent event);
+ void stopEvents();
+ QBluetoothAddress addressForConnectionHandle(quint16 handle) const;
+
+
+signals:
+ void encryptionChangedEvent(const QBluetoothAddress &address, bool wasSuccess);
+
+private slots:
+ void _q_readNotify();
+
+private:
+ int hciForAddress(const QBluetoothAddress &deviceAdapter);
+
+ int hciSocket;
+ int hciDev;
+ QSocketNotifier *notifier;
+ QSet<HciManager::HciEvent> runningEvents;
+};
+
+QT_END_NAMESPACE
+
+#endif // HCIMANAGER_P_H
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
index d97c0e92..01993f25 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
@@ -337,8 +337,12 @@ void QBluetoothServiceDiscoveryAgentPrivate::populateDiscoveredServices(const QB
QBluetoothServiceInfo serviceInfo;
serviceInfo.setDevice(remoteDevice);
- QBluetoothServiceInfo::Sequence protocolDescriptorList;
- protocolDescriptorList << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
+ QBluetoothServiceInfo::Sequence protocolDescriptorList;
+ {
+ QBluetoothServiceInfo::Sequence protocol;
+ protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
+ protocolDescriptorList.append(QVariant::fromValue(protocol));
+ }
if (customUuids.contains(i) && sppIndex > -1) {
//we have a custom uuid of service class type SPP
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp
index fb956c9e..d82a73a8 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp
@@ -448,7 +448,16 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_createdDevice(QDBusPendingCallWa
service.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
QBluetoothServiceInfo::Sequence protocolDescriptorList;
- protocolDescriptorList << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
+ {
+ QBluetoothServiceInfo::Sequence protocol;
+ protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
+ protocolDescriptorList.append(QVariant::fromValue(protocol));
+ }
+ {
+ QBluetoothServiceInfo::Sequence protocol;
+ protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Att));
+ protocolDescriptorList.append(QVariant::fromValue(protocol));
+ }
service.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
if (uuidFilter.isEmpty())
@@ -657,7 +666,16 @@ void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(cons
}
QBluetoothServiceInfo::Sequence protocolDescriptorList;
- protocolDescriptorList << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
+ {
+ QBluetoothServiceInfo::Sequence protocol;
+ protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
+ protocolDescriptorList.append(QVariant::fromValue(protocol));
+ }
+ {
+ QBluetoothServiceInfo::Sequence protocol;
+ protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Att));
+ protocolDescriptorList.append(QVariant::fromValue(protocol));
+ }
serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
//don't include the service if we already discovered it before
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_qnx.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_qnx.cpp
index 4ed62c25..69d50e29 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_qnx.cpp
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_qnx.cpp
@@ -278,8 +278,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::remoteDevicesChanged(int fd)
QBluetoothServiceInfo serviceInfo;
serviceInfo.setDevice(discoveredDevices.at(0));
- QBluetoothServiceInfo::Sequence protocolDescriptorList;
- protocolDescriptorList << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
+ QBluetoothServiceInfo::Sequence protocolDescriptorList;
+ QBluetoothServiceInfo::Sequence l2cpProtocol;
+ l2cpProtocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
+ protocolDescriptorList.append(QVariant::fromValue(l2cpProtocol));
bool ok;
QBluetoothUuid suuid(QByteArray(next_service).toUInt(&ok,16));
@@ -361,8 +363,17 @@ void QBluetoothServiceDiscoveryAgentPrivate::remoteDevicesChanged(int fd)
lowEnergyService.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
QBluetoothServiceInfo::Sequence protocolDescriptorList;
- protocolDescriptorList << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
- service.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
+ {
+ QBluetoothServiceInfo::Sequence protocol;
+ protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
+ protocolDescriptorList.append(QVariant::fromValue(protocol));
+ }
+ {
+ QBluetoothServiceInfo::Sequence protocol;
+ protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Att));
+ protocolDescriptorList.append(QVariant::fromValue(protocol));
+ }
+ lowEnergyService.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
qCDebug(QT_BT_QNX) << "Adding Low Energy service" << leUuid;
diff --git a/src/bluetooth/qbluetoothserviceinfo_android.cpp b/src/bluetooth/qbluetoothserviceinfo_android.cpp
index 5d7f0fac..64603235 100644
--- a/src/bluetooth/qbluetoothserviceinfo_android.cpp
+++ b/src/bluetooth/qbluetoothserviceinfo_android.cpp
@@ -107,7 +107,7 @@ bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress& loca
return false;
if (protocolDescriptor(QBluetoothUuid::Rfcomm).isEmpty()) {
- qCWarning(QT_BT_ANDROID) << Q_FUNC_INFO << "Only RFCOMM services can be registered on QNX";
+ qCWarning(QT_BT_ANDROID) << Q_FUNC_INFO << "Only RFCOMM services can be registered on Android";
return false;
}
diff --git a/src/bluetooth/qbluetoothtransferreply.cpp b/src/bluetooth/qbluetoothtransferreply.cpp
index 6ade4808..4ef27bf4 100644
--- a/src/bluetooth/qbluetoothtransferreply.cpp
+++ b/src/bluetooth/qbluetoothtransferreply.cpp
@@ -80,6 +80,9 @@ void QBluetoothTransferReply::abort()
\fn void QBluetoothTransferReply::finished(QBluetoothTransferReply *reply)
This signal is emitted when the transfer is complete for \a reply.
+
+ To avoid the loss of signal emissions it is recommend to immidiately connect
+ to this signal once a \c QBluetoothTransferReply instance has been created.
*/
/*!
@@ -87,6 +90,23 @@ void QBluetoothTransferReply::abort()
This signal is emitted whenever data is transferred. The \a bytesTransferred parameter contains the total
number of bytes transferred so far out of \a bytesTotal.
+
+
+ To avoid the loss of signal emissions it is recommend to immidiately connect
+ to this signal once a QBluetoothTransferReply instance has been created.
+*/
+
+/*!
+ \fn void QBluetoothTransferReply::error(QBluetoothTransferReply::TransferError errorType)
+ \since 5.4
+
+ This signal is emitted whenever an error has occurred. The \a errorType
+ parameter indicates the type of error.
+
+ To avoid the loss of signal emissions it is recommend to immidiately connect
+ to this signal once a QBluetoothTransferReply instance has been created.
+
+ \sa error(), errorString()
*/
/*!
@@ -95,7 +115,8 @@ void QBluetoothTransferReply::abort()
QBluetoothTransferReply::QBluetoothTransferReply(QObject *parent)
: QObject(parent), d_ptr(new QBluetoothTransferReplyPrivate())
{
- qRegisterMetaType<QBluetoothTransferReply*>("QBluetoothTransferReply");
+ qRegisterMetaType<QBluetoothTransferReply*>();
+ qRegisterMetaType<QBluetoothTransferReply::TransferError>();
}
/*!
@@ -165,12 +186,16 @@ void QBluetoothTransferReply::setRequest(const QBluetoothTransferRequest &reques
\fn TransferError QBluetoothTransferReply::error() const
The error code of the error that occurred.
+
+ \sa errorString()
*/
/*!
\fn QString QBluetoothTransferReply::errorString() const
String describing the error. Can be displayed to the user.
+
+ \sa error()
*/
QBluetoothTransferReplyPrivate::QBluetoothTransferReplyPrivate()
diff --git a/src/bluetooth/qbluetoothtransferreply.h b/src/bluetooth/qbluetoothtransferreply.h
index a8fda6ce..37ca8fe5 100644
--- a/src/bluetooth/qbluetoothtransferreply.h
+++ b/src/bluetooth/qbluetoothtransferreply.h
@@ -79,6 +79,7 @@ Q_SIGNALS:
//TODO Remove QBluetoothTransferReply* parameter in Qt 6
void finished(QBluetoothTransferReply *);
void transferProgress(qint64 bytesTransferred, qint64 bytesTotal);
+ void error(QBluetoothTransferReply::TransferError lastError);
protected:
explicit QBluetoothTransferReply(QObject *parent = 0);
@@ -95,4 +96,6 @@ private:
QT_END_NAMESPACE
+Q_DECLARE_METATYPE(QBluetoothTransferReply::TransferError)
+
#endif // QBLUETOOTHTRANSFERREPLY_H
diff --git a/src/bluetooth/qbluetoothtransferreply_bluez.cpp b/src/bluetooth/qbluetoothtransferreply_bluez.cpp
index be5fccd0..ae29ce37 100644
--- a/src/bluetooth/qbluetoothtransferreply_bluez.cpp
+++ b/src/bluetooth/qbluetoothtransferreply_bluez.cpp
@@ -69,8 +69,6 @@ QBluetoothTransferReplyBluez::QBluetoothTransferReplyBluez(QIODevice *input, con
setRequest(request);
setManager(parent);
- qRegisterMetaType<QBluetoothTransferReply*>("QBluetoothTransferReply*");
-
if (isBluez5()) {
m_clientBluez = new OrgBluezObexClient1Interface(QStringLiteral("org.bluez.obex"),
QStringLiteral("/org/bluez/obex"),
@@ -121,7 +119,9 @@ bool QBluetoothTransferReplyBluez::start()
m_error = QBluetoothTransferReply::IODeviceNotReadableError;
m_finished = true;
m_running = false;
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection, Q_ARG(QBluetoothTransferReply*, this));
+
+ emit QBluetoothTransferReply::error(m_error);
+ emit finished(this);
return false;
}
@@ -137,7 +137,9 @@ bool QBluetoothTransferReplyBluez::start()
m_error = QBluetoothTransferReply::FileNotFoundError;
m_finished = true;
m_running = false;
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection, Q_ARG(QBluetoothTransferReply*, this));
+
+ emit QBluetoothTransferReply::error(m_error);
+ emit finished(this);
return false;
}
if (request().address().isNull()) {
@@ -145,7 +147,9 @@ bool QBluetoothTransferReplyBluez::start()
m_error = QBluetoothTransferReply::HostNotFoundError;
m_finished = true;
m_running = false;
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection, Q_ARG(QBluetoothTransferReply*, this));
+
+ emit QBluetoothTransferReply::error(m_error);
+ emit finished(this);
return false;
}
m_size = file->size();
@@ -201,8 +205,9 @@ void QBluetoothTransferReplyBluez::sessionCreated(QDBusPendingCallWatcher *watch
m_error = QBluetoothTransferReply::HostNotFoundError;
m_finished = true;
m_running = false;
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection,
- Q_ARG(QBluetoothTransferReply*, this));
+
+ emit QBluetoothTransferReply::error(m_error);
+ emit finished(this);
watcher->deleteLater();
return;
@@ -232,8 +237,8 @@ void QBluetoothTransferReplyBluez::sessionStarted(QDBusPendingCallWatcher *watch
cleanupSession();
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection,
- Q_ARG(QBluetoothTransferReply *, this));
+ emit QBluetoothTransferReply::error(m_error);
+ emit finished(this);
watcher->deleteLater();
return;
@@ -276,6 +281,8 @@ void QBluetoothTransferReplyBluez::sessionChanged(const QString &interface,
if (s == QStringLiteral("error")) {
m_error = QBluetoothTransferReply::UnknownError;
m_errorStr = tr("Unknown Error");
+
+ emit QBluetoothTransferReply::error(m_error);
} else { // complete
// allow progress bar to complete
emit transferProgress(m_size, m_size);
@@ -283,8 +290,7 @@ void QBluetoothTransferReplyBluez::sessionChanged(const QString &interface,
cleanupSession();
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection,
- Q_ARG(QBluetoothTransferReply*, this));
+ emit finished(this);
} // ignore "active", "queued" & "suspended" status
}
qCDebug(QT_BT_BLUEZ) << "Transfer update:" << interface << changed_properties;
@@ -336,8 +342,8 @@ void QBluetoothTransferReplyBluez::sendReturned(QDBusPendingCallWatcher *watcher
m_error = QBluetoothTransferReply::UnknownError;
}
- // allow time for the developer to connect to the signal
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection, Q_ARG(QBluetoothTransferReply*, this));
+ emit QBluetoothTransferReply::error(m_error);
+ emit finished(this);
}
}
@@ -376,6 +382,7 @@ void QBluetoothTransferReplyBluez::Error(const QDBusObjectPath &in0, const QStri
m_error = QBluetoothTransferReply::UnknownError;
}
+ emit QBluetoothTransferReply::error(m_error);
emit finished(this);
}
@@ -444,6 +451,7 @@ void QBluetoothTransferReplyBluez::abort()
cleanupSession();
+ emit QBluetoothTransferReply::error(m_error);
emit finished(this);
}
}
diff --git a/src/bluetooth/qbluetoothtransferreply_p.h b/src/bluetooth/qbluetoothtransferreply_p.h
index caf3890d..8c6c4968 100644
--- a/src/bluetooth/qbluetoothtransferreply_p.h
+++ b/src/bluetooth/qbluetoothtransferreply_p.h
@@ -55,12 +55,8 @@ public:
QBluetoothTransferReplyPrivate();
QBluetoothTransferManager *m_manager;
- qint64 m_buffersize;
QBluetoothTransferRequest m_request;
-
- QBluetoothTransferReply *q_ptr;
-
};
QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothtransferreply_qnx.cpp b/src/bluetooth/qbluetoothtransferreply_qnx.cpp
index 072230b1..4c378065 100644
--- a/src/bluetooth/qbluetoothtransferreply_qnx.cpp
+++ b/src/bluetooth/qbluetoothtransferreply_qnx.cpp
@@ -106,6 +106,7 @@ bool QBluetoothTransferReplyQnx::start()
m_error = QBluetoothTransferReply::ResourceBusyError;
m_finished = true;
m_running = false;
+ emit QBluetoothTransferReply::error(m_error);
emit finished(this);
return false;
}
@@ -115,6 +116,7 @@ bool QBluetoothTransferReplyQnx::start()
m_error = QBluetoothTransferReply::IODeviceNotReadableError;
m_finished = true;
m_running = false;
+ emit QBluetoothTransferReply::error(m_error);
emit finished(this);
return false;
}
@@ -134,7 +136,8 @@ bool QBluetoothTransferReplyQnx::start()
m_error = QBluetoothTransferReply::FileNotFoundError;
m_finished = true;
m_running = false;
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection, Q_ARG(QBluetoothTransferReply*, this));
+ emit QBluetoothTransferReply::error(m_error);
+ emit finished(this);
return false;
}
if (request().address().isNull()) {
@@ -142,7 +145,8 @@ bool QBluetoothTransferReplyQnx::start()
m_error = QBluetoothTransferReply::HostNotFoundError;
m_finished = true;
m_running = false;
- QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection, Q_ARG(QBluetoothTransferReply*, this));
+ emit QBluetoothTransferReply::error(m_error);
+ emit finished(this);
return false;
}
startOPP(file->fileName());
@@ -192,6 +196,8 @@ void QBluetoothTransferReplyQnx::controlReply(ppsResult result)
if (!result.errorMsg.isEmpty()) {
m_errorStr = result.errorMsg;
m_error = QBluetoothTransferReply::UnknownError;
+ emit QBluetoothTransferReply::error(m_error);
+ emit finished(this);
}
}
@@ -214,6 +220,7 @@ void QBluetoothTransferReplyQnx::controlEvent(ppsResult result)
// } else {
m_errorStr = result.errorMsg;
m_error = QBluetoothTransferReply::UnknownError;
+ emit QBluetoothTransferReply::error(m_error);
// }
emit finished(this);
} else if (result.msg == QStringLiteral("opp_update")) {
diff --git a/src/bluetooth/qbluetoothuuid.cpp b/src/bluetooth/qbluetoothuuid.cpp
index 15d238e1..72bf3404 100644
--- a/src/bluetooth/qbluetoothuuid.cpp
+++ b/src/bluetooth/qbluetoothuuid.cpp
@@ -375,6 +375,8 @@ Q_GLOBAL_STATIC_WITH_ARGS(QUuid, baseUuid, ("{00000000-0000-1000-8000-00805F9B34
will be implicitly converted into a QBluetoothUuid when necessary.
\value CharacteristicExtendedProperties Descriptor defines additional Characteristic Properties.
+ The existence of this descriptor is indicated by the
+ \l QLowEnergyCharacteristic::ExtendedProperty flag.
\value CharacteristicUserDescription Descriptor provides a textual user description for a characteristic value.
\value ClientCharacteristicConfiguration Descriptor defines how the characteristic may be configured by a specific client.
\value ServerCharacteristicConfiguration Descriptor defines how the characteristic descriptor is associated with may be
diff --git a/src/bluetooth/qlowenergycharacteristic.cpp b/src/bluetooth/qlowenergycharacteristic.cpp
index 8d63419b..94842f9c 100644
--- a/src/bluetooth/qlowenergycharacteristic.cpp
+++ b/src/bluetooth/qlowenergycharacteristic.cpp
@@ -80,7 +80,7 @@ QT_BEGIN_NAMESPACE
\value Notify Permits notification of characteristic values.
\value Indicate Permits indications of characteristic values.
\value WriteSigned Permits signed writes of the GATT characteristic values.
- \value ExtendedProperty Additional characteristic properties are defined in the characteristic
+ \value ExtendedProperty Additional characteristic properties are defined in the characteristic's
extended properties descriptor.
\sa properties()
@@ -207,7 +207,8 @@ QByteArray QLowEnergyCharacteristic::value() const
/*!
Returns the handle of the characteristic's value attribute;
- or \c 0 if the handle cannot be accessed on the platform.
+ or \c 0 if the handle cannot be accessed on the platform or
+ if the characteristic is invalid.
*/
QLowEnergyHandle QLowEnergyCharacteristic::handle() const
{
@@ -305,7 +306,10 @@ bool QLowEnergyCharacteristic::isValid() const
\internal
Returns the handle of the characteristic or
- \c 0 if the handle cannot be accessed on the platform.
+ \c 0 if the handle cannot be accessed on the platform or if the
+ characteristic is invalid.
+
+ \sa isValid()
*/
QLowEnergyHandle QLowEnergyCharacteristic::attributeHandle() const
{
diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp
index ce009f00..bdbb2e48 100644
--- a/src/bluetooth/qlowenergycontroller.cpp
+++ b/src/bluetooth/qlowenergycontroller.cpp
@@ -297,7 +297,7 @@ QLowEnergyCharacteristic QLowEnergyControllerPrivate::characteristicForHandle(
}
/*!
- Returns a valid descriptor if \a handle blongs to a descriptor;
+ Returns a valid descriptor if \a handle belongs to a descriptor;
otherwise an invalid one.
*/
QLowEnergyDescriptor QLowEnergyControllerPrivate::descriptorForHandle(
@@ -451,7 +451,7 @@ QLowEnergyController::ControllerState QLowEnergyController::state() const
Returns the type of \l remoteAddress(). By default, this value is initialized
to \l PublicAddress.
- \sa setRemoteAddressType
+ \sa setRemoteAddressType()
*/
QLowEnergyController::RemoteAddressType QLowEnergyController::remoteAddressType() const
{
diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp
index 79d5add6..f217f0a2 100644
--- a/src/bluetooth/qlowenergycontroller_bluez.cpp
+++ b/src/bluetooth/qlowenergycontroller_bluez.cpp
@@ -35,6 +35,7 @@
#include "qlowenergycontroller_p.h"
#include "qbluetoothsocket_p.h"
#include "bluez/bluez_data_p.h"
+#include "bluez/hcimanager_p.h"
#include <QtCore/QLoggingCategory>
#include <QtBluetooth/QBluetoothSocket>
@@ -66,19 +67,25 @@
#define ATT_OP_READ_BY_GROUP_RESPONSE 0x11
#define ATT_OP_WRITE_REQUEST 0x12 //write characteristic with response
#define ATT_OP_WRITE_RESPONSE 0x13
+#define ATT_OP_PREPARE_WRITE_REQUEST 0x16 //write values longer than MTU-3 -> queueing
+#define ATT_OP_PREPARE_WRITE_RESPONSE 0x17
+#define ATT_OP_EXECUTE_WRITE_REQUEST 0x18 //write values longer than MTU-3 -> execute queue
+#define ATT_OP_EXECUTE_WRITE_RESPONSE 0x19
#define ATT_OP_HANDLE_VAL_NOTIFICATION 0x1b //informs about value change
#define ATT_OP_HANDLE_VAL_INDICATION 0x1d //informs about value change -> requires reply
#define ATT_OP_HANDLE_VAL_CONFIRMATION 0x1e //answer for ATT_OP_HANDLE_VAL_INDICATION
#define ATT_OP_WRITE_COMMAND 0x52 //write characteristic without response
//GATT command sizes in bytes
-#define FIND_INFO_REQUEST_SIZE 5
-#define GRP_TYPE_REQ_SIZE 7
-#define READ_BY_TYPE_REQ_SIZE 7
-#define READ_REQUEST_SIZE 3
-#define READ_BLOB_REQUEST_SIZE 5
-#define WRITE_REQUEST_SIZE 3 // same size for WRITE_COMMAND
-#define MTU_EXCHANGE_SIZE 3
+#define FIND_INFO_REQUEST_HEADER_SIZE 5
+#define GRP_TYPE_REQ_HEADER_SIZE 7
+#define READ_BY_TYPE_REQ_HEADER_SIZE 7
+#define READ_REQUEST_HEADER_SIZE 3
+#define READ_BLOB_REQUEST_HEADER_SIZE 5
+#define WRITE_REQUEST_HEADER_SIZE 3 // same size for WRITE_COMMAND header
+#define PREPARE_WRITE_HEADER_SIZE 5
+#define EXECUTE_WRITE_HEADER_SIZE 2
+#define MTU_EXCHANGE_HEADER_SIZE 3
// GATT error codes
#define ATT_ERROR_INVALID_HANDLE 0x01
@@ -184,9 +191,20 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
state(QLowEnergyController::UnconnectedState),
error(QLowEnergyController::NoError),
l2cpSocket(0), requestPending(false),
- mtuSize(ATT_DEFAULT_LE_MTU)
+ mtuSize(ATT_DEFAULT_LE_MTU),
+ securityLevelValue(-1),
+ encryptionChangePending(false),
+ hciManager(0)
{
qRegisterMetaType<QList<QLowEnergyHandle> >();
+
+ hciManager = new HciManager(localAdapter, this);
+ if (!hciManager->isValid())
+ return;
+
+ hciManager->monitorEvent(HciManager::EncryptChangeEvent);
+ connect(hciManager, SIGNAL(encryptionChangedEvent(QBluetoothAddress,bool)),
+ this, SLOT(encryptionChangedEvent(QBluetoothAddress,bool)));
}
QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate()
@@ -240,6 +258,7 @@ void QLowEnergyControllerPrivate::l2cpConnected()
{
Q_Q(QLowEnergyController);
+ securityLevelValue = securityLevel();
exchangeMTU();
setState(QLowEnergyController::ConnectedState);
@@ -256,6 +275,7 @@ void QLowEnergyControllerPrivate::l2cpDisconnected()
{
Q_Q(QLowEnergyController);
+ securityLevelValue = -1;
setState(QLowEnergyController::UnconnectedState);
emit q->disconnected();
}
@@ -333,6 +353,62 @@ void QLowEnergyControllerPrivate::l2cpReadyRead()
sendNextPendingRequest();
}
+/*!
+ * Called when the request for socket encryption has been
+ * processed by the kernel. Such requests take time as the kernel
+ * has to renegotiate the link parameters with the remote device.
+ *
+ * Therefore any such request delays the pending ATT commands until this
+ * callback is called. The first pending request in the queue is the request
+ * that triggered the encryption request.
+ */
+void QLowEnergyControllerPrivate::encryptionChangedEvent(
+ const QBluetoothAddress &address, bool wasSuccess)
+{
+ if (remoteDevice != address)
+ return;
+
+ Q_ASSERT(encryptionChangePending);
+ securityLevelValue = securityLevel();
+
+ // On success continue to process ATT command queue
+ if (!wasSuccess) {
+ // We could not increase the security of the link
+ // The next request was requeued due to security error
+ // skip it to avoid endless loop of security negotiations
+ Q_ASSERT(!openRequests.isEmpty());
+ Request failedRequest = openRequests.takeFirst();
+
+ if (failedRequest.command == ATT_OP_WRITE_REQUEST) {
+ // Failing write requests trigger some sort of response
+ uint ref = failedRequest.reference.toUInt();
+ const QLowEnergyHandle charHandle = (ref & 0xffff);
+ const QLowEnergyHandle descriptorHandle = ((ref >> 16) & 0xffff);
+
+ QSharedPointer<QLowEnergyServicePrivate> service
+ = serviceForHandle(charHandle);
+ if (!service.isNull() && service->characteristicList.contains(charHandle)) {
+ if (!descriptorHandle)
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ else
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ }
+ } else if (failedRequest.command == ATT_OP_PREPARE_WRITE_REQUEST) {
+ uint handleData = failedRequest.reference.toUInt();
+ const QLowEnergyHandle attrHandle = (handleData & 0xffff);
+ const QByteArray newValue = failedRequest.reference2.toByteArray();
+
+ // Prepare command failed, cancel pending prepare queue on
+ // the device. The appropriate (Descriptor|Characteristic)WriteError
+ // is emitted too once the execute write request comes through
+ sendExecuteWriteRequest(attrHandle, newValue, true);
+ }
+ }
+
+ encryptionChangePending = false;
+ sendNextPendingRequest();
+}
+
void QLowEnergyControllerPrivate::sendCommand(const QByteArray &packet)
{
qint64 result = l2cpSocket->write(packet.constData(),
@@ -347,7 +423,7 @@ void QLowEnergyControllerPrivate::sendCommand(const QByteArray &packet)
void QLowEnergyControllerPrivate::sendNextPendingRequest()
{
- if (openRequests.isEmpty() || requestPending)
+ if (openRequests.isEmpty() || requestPending || encryptionChangePending)
return;
const Request &request = openRequests.head();
@@ -593,8 +669,16 @@ void QLowEnergyControllerPrivate::processReply(
const QLowEnergyHandle charHandle = (handleData & 0xffff);
const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff);
- // we ignore error response
- if (!isErrorResponse) {
+ if (isErrorResponse) {
+ Q_ASSERT(!encryptionChangePending);
+ encryptionChangePending = increaseEncryptLevelfRequired(response.constData()[4]);
+ if (encryptionChangePending) {
+ // Just requested a security level change.
+ // Retry the same command again once the change has happened
+ openRequests.prepend(request);
+ break;
+ }
+ } else {
if (!descriptorHandle)
updateValueOfCharacteristic(charHandle, response.mid(1), NEW_VALUE);
else
@@ -631,7 +715,12 @@ void QLowEnergyControllerPrivate::processReply(
const QLowEnergyHandle charHandle = (handleData & 0xffff);
const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff);
- //ignore errors
+ /*
+ * READ_BLOB does not require encryption setup code. BLOB commands
+ * are only issued after read request if the read request is too long
+ * for single MTU. The preceding read request would have triggered
+ * the setup of the encryption already.
+ */
if (!isErrorResponse) {
quint16 length = 0;
if (!descriptorHandle)
@@ -784,6 +873,13 @@ void QLowEnergyControllerPrivate::processReply(
break;
if (isErrorResponse) {
+ Q_ASSERT(!encryptionChangePending);
+ encryptionChangePending = increaseEncryptLevelfRequired(response.constData()[4]);
+ if (encryptionChangePending) {
+ openRequests.prepend(request);
+ break;
+ }
+
if (!descriptorHandle)
service->setError(QLowEnergyService::CharacteristicWriteError);
else
@@ -793,16 +889,81 @@ void QLowEnergyControllerPrivate::processReply(
const QByteArray newValue = request.reference2.toByteArray();
if (!descriptorHandle) {
- service->characteristicList[charHandle].value = newValue;
+ updateValueOfCharacteristic(charHandle, newValue, NEW_VALUE);
QLowEnergyCharacteristic ch(service, charHandle);
emit service->characteristicWritten(ch, newValue);
} else {
- service->characteristicList[charHandle].descriptorList[descriptorHandle].value = newValue;
+ updateValueOfDescriptor(charHandle, descriptorHandle, newValue, NEW_VALUE);
QLowEnergyDescriptor descriptor(service, charHandle, descriptorHandle);
emit service->descriptorWritten(descriptor, newValue);
}
}
break;
+ case ATT_OP_PREPARE_WRITE_REQUEST: //error case
+ case ATT_OP_PREPARE_WRITE_RESPONSE:
+ {
+ //Prepare write command response
+ Q_ASSERT(request.command == ATT_OP_PREPARE_WRITE_REQUEST);
+
+ uint handleData = request.reference.toUInt();
+ const QLowEnergyHandle attrHandle = (handleData & 0xffff);
+ const QByteArray newValue = request.reference2.toByteArray();
+ const int writtenPayload = ((handleData >> 16) & 0xffff);
+
+ if (isErrorResponse) {
+ Q_ASSERT(!encryptionChangePending);
+ encryptionChangePending = increaseEncryptLevelfRequired(response.constData()[4]);
+ if (encryptionChangePending) {
+ openRequests.prepend(request);
+ break;
+ }
+ //emits error on cancellation and aborts existing prepare reuqests
+ sendExecuteWriteRequest(attrHandle, newValue, true);
+ } else {
+ if (writtenPayload < newValue.size()) {
+ sendNextPrepareWriteRequest(attrHandle, newValue, writtenPayload);
+ } else {
+ sendExecuteWriteRequest(attrHandle, newValue, false);
+ }
+ }
+ }
+ break;
+ case ATT_OP_EXECUTE_WRITE_REQUEST: //error case
+ case ATT_OP_EXECUTE_WRITE_RESPONSE:
+ {
+ // right now used in connection with long characteristic/descriptor value writes
+ // not catering for reliable writes
+ Q_ASSERT(request.command == ATT_OP_EXECUTE_WRITE_REQUEST);
+
+ uint handleData = request.reference.toUInt();
+ const QLowEnergyHandle attrHandle = handleData & 0xffff;
+ bool wasCancellation = !((handleData >> 16) & 0xffff);
+ const QByteArray newValue = request.reference2.toByteArray();
+
+ // is it a descriptor or characteristic?
+ const QLowEnergyDescriptor descriptor = descriptorForHandle(attrHandle);
+ QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(attrHandle);
+ Q_ASSERT(!service.isNull());
+
+ if (isErrorResponse || wasCancellation) {
+ // charHandle == 0 -> cancellation
+ if (descriptor.isValid())
+ service->setError(QLowEnergyService::DescriptorWriteError);
+ else
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+ } else {
+ if (descriptor.isValid()) {
+ updateValueOfDescriptor(descriptor.characteristicHandle(),
+ attrHandle, newValue, NEW_VALUE);
+ emit service->descriptorWritten(descriptor, newValue);
+ } else {
+ updateValueOfCharacteristic(attrHandle, newValue, NEW_VALUE);
+ QLowEnergyCharacteristic ch(service, attrHandle);
+ emit service->characteristicWritten(ch, newValue);
+ }
+ }
+ }
+ break;
default:
qCDebug(QT_BT_BLUEZ) << "Unknown packet: " << response.toHex();
break;
@@ -818,15 +979,15 @@ void QLowEnergyControllerPrivate::sendReadByGroupRequest(
QLowEnergyHandle start, QLowEnergyHandle end, quint16 type)
{
//call for primary and secondary services
- quint8 packet[GRP_TYPE_REQ_SIZE];
+ quint8 packet[GRP_TYPE_REQ_HEADER_SIZE];
packet[0] = ATT_OP_READ_BY_GROUP_REQUEST;
bt_put_unaligned(htobs(start), (quint16 *) &packet[1]);
bt_put_unaligned(htobs(end), (quint16 *) &packet[3]);
bt_put_unaligned(htobs(type), (quint16 *) &packet[5]);
- QByteArray data(GRP_TYPE_REQ_SIZE, Qt::Uninitialized);
- memcpy(data.data(), packet, GRP_TYPE_REQ_SIZE);
+ QByteArray data(GRP_TYPE_REQ_HEADER_SIZE, Qt::Uninitialized);
+ memcpy(data.data(), packet, GRP_TYPE_REQ_HEADER_SIZE);
qCDebug(QT_BT_BLUEZ) << "Sending read_by_group_type request, startHandle:" << hex
<< start << "endHandle:" << end << type;
@@ -856,15 +1017,15 @@ void QLowEnergyControllerPrivate::sendReadByTypeRequest(
QSharedPointer<QLowEnergyServicePrivate> serviceData,
QLowEnergyHandle nextHandle, quint16 attributeType)
{
- quint8 packet[READ_BY_TYPE_REQ_SIZE];
+ quint8 packet[READ_BY_TYPE_REQ_HEADER_SIZE];
packet[0] = ATT_OP_READ_BY_TYPE_REQUEST;
bt_put_unaligned(htobs(nextHandle), (quint16 *) &packet[1]);
bt_put_unaligned(htobs(serviceData->endHandle), (quint16 *) &packet[3]);
bt_put_unaligned(htobs(attributeType), (quint16 *) &packet[5]);
- QByteArray data(READ_BY_TYPE_REQ_SIZE, Qt::Uninitialized);
- memcpy(data.data(), packet, READ_BY_TYPE_REQ_SIZE);
+ QByteArray data(READ_BY_TYPE_REQ_HEADER_SIZE, Qt::Uninitialized);
+ memcpy(data.data(), packet, READ_BY_TYPE_REQ_HEADER_SIZE);
qCDebug(QT_BT_BLUEZ) << "Sending read_by_type request, startHandle:" << hex
<< nextHandle << "endHandle:" << serviceData->endHandle
<< "type:" << attributeType << "packet:" << data.toHex();
@@ -890,7 +1051,7 @@ void QLowEnergyControllerPrivate::sendReadByTypeRequest(
void QLowEnergyControllerPrivate::readServiceValues(
const QBluetoothUuid &serviceUuid, bool readCharacteristics)
{
- quint8 packet[READ_REQUEST_SIZE];
+ quint8 packet[READ_REQUEST_HEADER_SIZE];
if (QT_BT_BLUEZ().isDebugEnabled()) {
if (readCharacteristics)
qCDebug(QT_BT_BLUEZ) << "Reading characteristic values for"
@@ -954,8 +1115,8 @@ void QLowEnergyControllerPrivate::readServiceValues(
packet[0] = ATT_OP_READ_REQUEST;
bt_put_unaligned(htobs(pair.first), (quint16 *) &packet[1]);
- QByteArray data(READ_REQUEST_SIZE, Qt::Uninitialized);
- memcpy(data.data(), packet, READ_REQUEST_SIZE);
+ QByteArray data(READ_REQUEST_HEADER_SIZE, Qt::Uninitialized);
+ memcpy(data.data(), packet, READ_REQUEST_HEADER_SIZE);
Request request;
request.payload = data;
@@ -984,7 +1145,7 @@ void QLowEnergyControllerPrivate::readServiceValuesByOffset(
{
const QLowEnergyHandle charHandle = (handleData & 0xffff);
const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff);
- quint8 packet[READ_REQUEST_SIZE];
+ quint8 packet[READ_REQUEST_HEADER_SIZE];
packet[0] = ATT_OP_READ_BLOB_REQUEST;
@@ -1010,8 +1171,8 @@ void QLowEnergyControllerPrivate::readServiceValuesByOffset(
bt_put_unaligned(htobs(handleToRead), (quint16 *) &packet[1]);
bt_put_unaligned(htobs(offset), (quint16 *) &packet[3]);
- QByteArray data(READ_BLOB_REQUEST_SIZE, Qt::Uninitialized);
- memcpy(data.data(), packet, READ_BLOB_REQUEST_SIZE);
+ QByteArray data(READ_BLOB_REQUEST_HEADER_SIZE, Qt::Uninitialized);
+ memcpy(data.data(), packet, READ_BLOB_REQUEST_HEADER_SIZE);
Request request;
request.payload = data;
@@ -1068,12 +1229,12 @@ void QLowEnergyControllerPrivate::exchangeMTU()
{
qCDebug(QT_BT_BLUEZ) << "Exchanging MTU";
- quint8 packet[MTU_EXCHANGE_SIZE];
+ quint8 packet[MTU_EXCHANGE_HEADER_SIZE];
packet[0] = ATT_OP_EXCHANGE_MTU_REQUEST;
bt_put_unaligned(htobs(ATT_MAX_LE_MTU), (quint16 *) &packet[1]);
- QByteArray data(MTU_EXCHANGE_SIZE, Qt::Uninitialized);
- memcpy(data.data(), packet, MTU_EXCHANGE_SIZE);
+ QByteArray data(MTU_EXCHANGE_HEADER_SIZE, Qt::Uninitialized);
+ memcpy(data.data(), packet, MTU_EXCHANGE_HEADER_SIZE);
Request request;
request.payload = data;
@@ -1083,6 +1244,90 @@ void QLowEnergyControllerPrivate::exchangeMTU()
sendNextPendingRequest();
}
+int QLowEnergyControllerPrivate::securityLevel() const
+{
+ int socket = l2cpSocket->socketDescriptor();
+ if (socket < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Invalid l2cp socket, aborting getting of sec level";
+ return -1;
+ }
+
+ struct bt_security secData;
+ socklen_t length = sizeof(secData);
+ memset(&secData, 0, length);
+
+ if (getsockopt(socket, SOL_BLUETOOTH, BT_SECURITY, &secData, &length) == 0) {
+ qCDebug(QT_BT_BLUEZ) << "Current l2cp sec level:" << secData.level;
+ return secData.level;
+ }
+
+ if (errno != ENOPROTOOPT) //older kernel, fall back to L2CAP_LM option
+ return -1;
+
+ // cater for older kernels
+ int optval;
+ length = sizeof(optval);
+ if (getsockopt(socket, SOL_L2CAP, L2CAP_LM, &optval, &length) == 0) {
+ int level = BT_SECURITY_SDP;
+ if (optval & L2CAP_LM_AUTH)
+ level = BT_SECURITY_LOW;
+ if (optval & L2CAP_LM_ENCRYPT)
+ level = BT_SECURITY_MEDIUM;
+ if (optval & L2CAP_LM_SECURE)
+ level = BT_SECURITY_HIGH;
+
+ qDebug() << "Current l2cp sec level (old):" << level;
+ return level;
+ }
+
+ return -1;
+}
+
+bool QLowEnergyControllerPrivate::setSecurityLevel(int level)
+{
+ if (level > BT_SECURITY_HIGH || level < BT_SECURITY_LOW)
+ return false;
+
+ int socket = l2cpSocket->socketDescriptor();
+ if (socket < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Invalid l2cp socket, aborting setting of sec level";
+ return false;
+ }
+
+ struct bt_security secData;
+ socklen_t length = sizeof(secData);
+ memset(&secData, 0, length);
+ secData.level = level;
+
+ if (setsockopt(socket, SOL_BLUETOOTH, BT_SECURITY, &secData, length) == 0) {
+ qCDebug(QT_BT_BLUEZ) << "Setting new l2cp sec level:" << secData.level;
+ return true;
+ }
+
+ if (errno != ENOPROTOOPT) //older kernel
+ return false;
+
+ int optval = 0;
+ switch (level) { // fall through intendeds
+ case BT_SECURITY_HIGH:
+ optval |= L2CAP_LM_SECURE;
+ case BT_SECURITY_MEDIUM:
+ optval |= L2CAP_LM_ENCRYPT;
+ case BT_SECURITY_LOW:
+ optval |= L2CAP_LM_AUTH;
+ break;
+ default:
+ return false;
+ }
+
+ if (setsockopt(socket, SOL_L2CAP, L2CAP_LM, &optval, sizeof(optval)) == 0) {
+ qDebug(QT_BT_BLUEZ) << "Old l2cp sec level:" << optval;
+ return true;
+ }
+
+ return false;
+}
+
void QLowEnergyControllerPrivate::discoverNextDescriptor(
QSharedPointer<QLowEnergyServicePrivate> serviceData,
const QList<QLowEnergyHandle> pendingCharHandles,
@@ -1094,7 +1339,7 @@ void QLowEnergyControllerPrivate::discoverNextDescriptor(
qCDebug(QT_BT_BLUEZ) << "Sending find_info request" << hex
<< pendingCharHandles << startingHandle;
- quint8 packet[FIND_INFO_REQUEST_SIZE];
+ quint8 packet[FIND_INFO_REQUEST_HEADER_SIZE];
packet[0] = ATT_OP_FIND_INFORMATION_REQUEST;
const QLowEnergyHandle charStartHandle = startingHandle;
@@ -1107,8 +1352,8 @@ void QLowEnergyControllerPrivate::discoverNextDescriptor(
bt_put_unaligned(htobs(charStartHandle), (quint16 *) &packet[1]);
bt_put_unaligned(htobs(charEndHandle), (quint16 *) &packet[3]);
- QByteArray data(FIND_INFO_REQUEST_SIZE, Qt::Uninitialized);
- memcpy(data.data(), packet, FIND_INFO_REQUEST_SIZE);
+ QByteArray data(FIND_INFO_REQUEST_HEADER_SIZE, Qt::Uninitialized);
+ memcpy(data.data(), packet, FIND_INFO_REQUEST_HEADER_SIZE);
Request request;
request.payload = data;
@@ -1120,6 +1365,93 @@ void QLowEnergyControllerPrivate::discoverNextDescriptor(
sendNextPendingRequest();
}
+void QLowEnergyControllerPrivate::sendNextPrepareWriteRequest(
+ const QLowEnergyHandle handle, const QByteArray &newValue,
+ quint16 offset)
+{
+ // is it a descriptor or characteristic?
+ QLowEnergyHandle targetHandle = 0;
+ const QLowEnergyDescriptor descriptor = descriptorForHandle(handle);
+ if (descriptor.isValid())
+ targetHandle = descriptor.handle();
+ else
+ targetHandle = characteristicForHandle(handle).handle();
+
+ if (!targetHandle) {
+ qCWarning(QT_BT_BLUEZ) << "sendNextPrepareWriteRequest cancelled due to invalid handle"
+ << handle;
+ return;
+ }
+
+ quint8 packet[PREPARE_WRITE_HEADER_SIZE];
+ packet[0] = ATT_OP_PREPARE_WRITE_REQUEST;
+ bt_put_unaligned(htobs(targetHandle), (quint16 *) &packet[1]); // attribute handle
+ bt_put_unaligned(htobs(offset), (quint16 *) &packet[3]); // offset into newValue
+
+ qCDebug(QT_BT_BLUEZ) << "Writing long characteristic (prepare):"
+ << hex << handle;
+
+
+ const int maxAvailablePayload = mtuSize - PREPARE_WRITE_HEADER_SIZE;
+ const int requiredPayload = qMin(newValue.size() - offset, maxAvailablePayload);
+ const int dataSize = PREPARE_WRITE_HEADER_SIZE + requiredPayload;
+
+ Q_ASSERT((offset + requiredPayload) <= newValue.size());
+ Q_ASSERT(dataSize <= mtuSize);
+
+ QByteArray data(dataSize, Qt::Uninitialized);
+ memcpy(data.data(), packet, PREPARE_WRITE_HEADER_SIZE);
+ memcpy(&(data.data()[PREPARE_WRITE_HEADER_SIZE]), &(newValue.constData()[offset]),
+ requiredPayload);
+
+ Request request;
+ request.payload = data;
+ request.command = ATT_OP_PREPARE_WRITE_REQUEST;
+ request.reference = (handle | ((offset + requiredPayload) << 16));
+ request.reference2 = newValue;
+ openRequests.enqueue(request);
+}
+
+/*!
+ Sends an "Execute Write Request" for a long characteristic or descriptor write.
+ This cannot be used for executes in relation to reliable write requests.
+
+ A cancellation removes all pending prepare write request on the GATT server.
+ Otherwise this function sends an execute request for all pending prepare
+ write requests.
+ */
+void QLowEnergyControllerPrivate::sendExecuteWriteRequest(
+ const QLowEnergyHandle attrHandle, const QByteArray &newValue,
+ bool isCancelation)
+{
+ quint8 packet[EXECUTE_WRITE_HEADER_SIZE];
+ packet[0] = ATT_OP_EXECUTE_WRITE_REQUEST;
+ if (isCancelation)
+ packet[1] = 0x00; // cancel pending write prepare requests
+ else
+ packet[1] = 0x01; // execute pending write prepare requests
+
+ QByteArray data(EXECUTE_WRITE_HEADER_SIZE, Qt::Uninitialized);
+ memcpy(data.data(), packet, EXECUTE_WRITE_HEADER_SIZE);
+
+ qCDebug(QT_BT_BLUEZ) << "Sending Execute Write Request for long characteristic value"
+ << hex << attrHandle;
+
+ Request request;
+ request.payload = data;
+ request.command = ATT_OP_EXECUTE_WRITE_REQUEST;
+ request.reference = (attrHandle | ((isCancelation ? 0x00 : 0x01) << 16));
+ request.reference2 = newValue;
+ openRequests.prepend(request);
+}
+
+
+/*!
+ Writes long (prepare write request), short (write request)
+ and writeWithoutResponse characteristic values.
+
+ TODO Reliable/prepare write across multiple characteristics is not supported
+ */
void QLowEnergyControllerPrivate::writeCharacteristic(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
@@ -1132,23 +1464,31 @@ void QLowEnergyControllerPrivate::writeCharacteristic(
return;
const QLowEnergyHandle valueHandle = service->characteristicList[charHandle].valueHandle;
- // sizeof(command) + sizeof(handle) + sizeof(newValue)
- const int size = 1 + 2 + newValue.size();
+ const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
- quint8 packet[WRITE_REQUEST_SIZE];
- if (writeWithResponse)
- packet[0] = ATT_OP_WRITE_REQUEST;
- else
+ quint8 packet[WRITE_REQUEST_HEADER_SIZE];
+ if (writeWithResponse) {
+ if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
+ sendNextPrepareWriteRequest(charHandle, newValue, 0);
+ sendNextPendingRequest();
+ return;
+ } else {
+ // write value fits into single package
+ packet[0] = ATT_OP_WRITE_REQUEST;
+ }
+ } else {
+ // write without response
packet[0] = ATT_OP_WRITE_COMMAND;
+ }
bt_put_unaligned(htobs(valueHandle), (quint16 *) &packet[1]);
QByteArray data(size, Qt::Uninitialized);
- memcpy(data.data(), packet, WRITE_REQUEST_SIZE);
- memcpy(&(data.data()[WRITE_REQUEST_SIZE]), newValue.constData(), newValue.size());
+ memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
+ memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
qCDebug(QT_BT_BLUEZ) << "Writing characteristic" << hex << charHandle
- << "(size:" << size << "response:" << writeWithResponse << ")";
+ << "(size:" << size << "with response:" << writeWithResponse << ")";
// Advantage of write without response is the quick turnaround.
// It can be send at any time and does not produce responses.
@@ -1176,16 +1516,20 @@ void QLowEnergyControllerPrivate::writeDescriptor(
{
Q_ASSERT(!service.isNull());
- // sizeof(command) + sizeof(handle) + sizeof(newValue)
- const int size = 1 + 2 + newValue.size();
+ if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
+ sendNextPrepareWriteRequest(descriptorHandle, newValue, 0);
+ sendNextPendingRequest();
+ return;
+ }
- quint8 packet[WRITE_REQUEST_SIZE];
+ quint8 packet[WRITE_REQUEST_HEADER_SIZE];
packet[0] = ATT_OP_WRITE_REQUEST;
bt_put_unaligned(htobs(descriptorHandle), (quint16 *) &packet[1]);
+ const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
QByteArray data(size, Qt::Uninitialized);
- memcpy(data.data(), packet, WRITE_REQUEST_SIZE);
- memcpy(&(data.data()[WRITE_REQUEST_SIZE]), newValue.constData(), newValue.size());
+ memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
+ memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
qCDebug(QT_BT_BLUEZ) << "Writing descriptor" << hex << descriptorHandle
<< "(size:" << size << ")";
@@ -1200,4 +1544,34 @@ void QLowEnergyControllerPrivate::writeDescriptor(
sendNextPendingRequest();
}
+/*!
+ * Returns true if the encryption change was successfully requested.
+ * The request is triggered if we got a related ATT error.
+ */
+bool QLowEnergyControllerPrivate::increaseEncryptLevelfRequired(quint8 errorCode)
+{
+ if (securityLevelValue == BT_SECURITY_HIGH)
+ return false;
+
+ switch (errorCode) {
+ case ATT_ERROR_INSUF_AUTHORIZATION:
+ case ATT_ERROR_INSUF_ENCRYPTION:
+ case ATT_ERROR_INSUF_AUTHENTICATION:
+ if (!hciManager->isValid())
+ return false;
+ if (!hciManager->monitorEvent(HciManager::EncryptChangeEvent))
+ return false;
+ if (securityLevelValue != BT_SECURITY_HIGH) {
+ qCDebug(QT_BT_BLUEZ) << "Requesting encrypted link";
+ if (setSecurityLevel(BT_SECURITY_HIGH))
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h
index d9f75625..ee206035 100644
--- a/src/bluetooth/qlowenergycontroller_p.h
+++ b/src/bluetooth/qlowenergycontroller_p.h
@@ -46,6 +46,10 @@
QT_BEGIN_NAMESPACE
+#if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE)
+class HciManager;
+#endif
+
typedef QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate> > ServiceDataMap;
class QLowEnergyControllerPrivate : public QObject
@@ -122,6 +126,10 @@ private:
QQueue<Request> openRequests;
bool requestPending;
quint16 mtuSize;
+ int securityLevelValue;
+ bool encryptionChangePending;
+
+ HciManager *hciManager;
void sendCommand(const QByteArray &packet);
void sendNextPendingRequest();
@@ -143,16 +151,25 @@ private:
QLowEnergyHandle startingHandle);
void processUnsolicitedReply(const QByteArray &msg);
void exchangeMTU();
-
+ bool setSecurityLevel(int level);
+ int securityLevel() const;
+ void sendExecuteWriteRequest(const QLowEnergyHandle attrHandle,
+ const QByteArray &newValue,
+ bool isCancelation);
+ void sendNextPrepareWriteRequest(const QLowEnergyHandle handle,
+ const QByteArray &newValue, quint16 offset);
+ bool increaseEncryptLevelfRequired(quint8 errorCode);
private slots:
void l2cpConnected();
void l2cpDisconnected();
void l2cpErrorChanged(QBluetoothSocket::SocketError);
void l2cpReadyRead();
+ void encryptionChangedEvent(const QBluetoothAddress&, bool);
#endif
private:
QLowEnergyController *q_ptr;
+
};
QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergydescriptor.cpp b/src/bluetooth/qlowenergydescriptor.cpp
index 270a8a68..c21f41cc 100644
--- a/src/bluetooth/qlowenergydescriptor.cpp
+++ b/src/bluetooth/qlowenergydescriptor.cpp
@@ -218,7 +218,7 @@ QBluetoothUuid QLowEnergyDescriptor::uuid() const
/*!
Returns the handle of the descriptor or \c 0 if the handle
- cannot be accessed on the platform.
+ cannot be accessed on the platform or the descriptor is invalid.
*/
QLowEnergyHandle QLowEnergyDescriptor::handle() const
{
diff --git a/src/bluetooth/qlowenergyservice.cpp b/src/bluetooth/qlowenergyservice.cpp
index 9136f83b..f8b430e0 100644
--- a/src/bluetooth/qlowenergyservice.cpp
+++ b/src/bluetooth/qlowenergyservice.cpp
@@ -100,6 +100,8 @@ QT_BEGIN_NAMESPACE
signal is emitted. A failure to write triggers the \l CharacteristicWriteError.
Writing a descriptor follows the same pattern.
+ \note Currently, it is not possible to send signed write or reliable write requests.
+
\target notifications
In some cases the peripheral generates value updates which
@@ -488,6 +490,9 @@ bool QLowEnergyService::contains(const QLowEnergyCharacteristic &characteristic)
\l QLowEnergyCharacteristic::Write and \l QLowEnergyCharacteristic::WriteNoResponse
properties.
+ \note Currently, it is not possible to use signed or reliable writes as defined by the
+ Bluetooth specification.
+
A characteristic can only be written if this service is in the \l ServiceDiscovered state,
belongs to the service and is writable.
@@ -498,7 +503,6 @@ void QLowEnergyService::writeCharacteristic(
const QByteArray &newValue, QLowEnergyService::WriteMode mode)
{
//TODO check behavior when writing to WriteSigned characteristic
- //TODO add support for write long characteristic value (newValue.size() > MTU - 3)
Q_D(QLowEnergyService);
// not a characteristic of this service
@@ -563,15 +567,11 @@ bool QLowEnergyService::contains(const QLowEnergyDescriptor &descriptor) const
void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor,
const QByteArray &newValue)
{
- //TODO not all descriptors are writable (how to deal with write errors)
Q_D(QLowEnergyService);
if (!contains(descriptor))
return;
- if (descriptor.value() == newValue)
- return;
-
if (state() != ServiceDiscovered || !d->controller) {
d->setError(QLowEnergyService::OperationError);
return;
diff --git a/src/imports/bluetooth/qdeclarativebluetoothservice.cpp b/src/imports/bluetooth/qdeclarativebluetoothservice.cpp
index 749dd65d..59ee2510 100644
--- a/src/imports/bluetooth/qdeclarativebluetoothservice.cpp
+++ b/src/imports/bluetooth/qdeclarativebluetoothservice.cpp
@@ -310,6 +310,12 @@ void QDeclarativeBluetoothService::setRegistered(bool registered)
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap))
<< QVariant::fromValue(quint16(d->m_server->serverPort()));
} else if (d->m_protocol == RfcommProtocol) {
+ //rfcomm implies l2cp protocol
+ {
+ QBluetoothServiceInfo::Sequence l2cpProtocol;
+ l2cpProtocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
+ protocolDescriptorList.append(QVariant::fromValue(l2cpProtocol));
+ }
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm))
<< QVariant::fromValue(quint8(d->m_server->serverPort()));
}
diff --git a/tests/auto/qbluetoothtransfermanager/tst_qbluetoothtransfermanager.cpp b/tests/auto/qbluetoothtransfermanager/tst_qbluetoothtransfermanager.cpp
index 89c70854..b2c55da5 100644
--- a/tests/auto/qbluetoothtransfermanager/tst_qbluetoothtransfermanager.cpp
+++ b/tests/auto/qbluetoothtransfermanager/tst_qbluetoothtransfermanager.cpp
@@ -235,6 +235,7 @@ void tst_QBluetoothTransferManager::tst_sendFile()
QBluetoothTransferReply* reply = manager.put(request, &f);
QSignalSpy finishedSpy(reply, SIGNAL(finished(QBluetoothTransferReply*)));
QSignalSpy progressSpy(reply, SIGNAL(transferProgress(qint64,qint64)));
+ QSignalSpy errorSpy(reply, SIGNAL(error(QBluetoothTransferReply::TransferError)));
QCOMPARE(reply->request(), request);
QVERIFY(reply->manager() == &manager);
@@ -253,6 +254,7 @@ void tst_QBluetoothTransferManager::tst_sendFile()
QVERIFY(progressSpy.count()>0);
QCOMPARE(reply->error(), QBluetoothTransferReply::NoError);
QCOMPARE(reply->errorString(), QString());
+ QVERIFY(errorSpy.isEmpty());
} else {
QVERIFY(progressSpy.count() == 0);
if (isInvalidFile)
@@ -260,6 +262,7 @@ void tst_QBluetoothTransferManager::tst_sendFile()
else
QVERIFY(reply->error() != QBluetoothTransferReply::NoError);
QVERIFY(!reply->errorString().isEmpty());
+ QCOMPARE(errorSpy.count(), 1);
}
QVERIFY(reply->isFinished());
@@ -274,7 +277,7 @@ void tst_QBluetoothTransferManager::tst_sendBuffer_data()
QTest::addColumn<QByteArray>("data");
QTest::newRow("Push to remote test device") << remoteAddress << true <<
- QByteArray("This is a very long byte arry which we are going to access via a QBuffer"); ;
+ QByteArray("This is a very long byte array which we are going to access via a QBuffer"); ;
QTest::newRow("Push to invalid address") << QBluetoothAddress() << false << QByteArray("test");
QTest::newRow("Push to non-existend device") << QBluetoothAddress("11:22:33:44:55:66") << false << QByteArray("test");
}
@@ -310,6 +313,7 @@ void tst_QBluetoothTransferManager::tst_sendBuffer()
QBluetoothTransferReply* reply = manager.put(request, &buffer);
QSignalSpy finishedSpy(reply, SIGNAL(finished(QBluetoothTransferReply*)));
QSignalSpy progressSpy(reply, SIGNAL(transferProgress(qint64,qint64)));
+ QSignalSpy errorSpy(reply, SIGNAL(error(QBluetoothTransferReply::TransferError)));
QCOMPARE(reply->request(), request);
QVERIFY(reply->manager() == &manager);
@@ -326,12 +330,14 @@ void tst_QBluetoothTransferManager::tst_sendBuffer()
QVERIFY(finishedSpy.count()>0);
if (expectSuccess) {
QVERIFY(progressSpy.count()>0);
+ QVERIFY(errorSpy.isEmpty());
QCOMPARE(reply->error(), QBluetoothTransferReply::NoError);
QCOMPARE(reply->errorString(), QString());
} else {
QVERIFY(progressSpy.count() == 0);
QVERIFY(reply->error() != QBluetoothTransferReply::NoError);
QVERIFY(!reply->errorString().isEmpty());
+ QCOMPARE(errorSpy.count(), 1);
}
QVERIFY(reply->isFinished());
diff --git a/tests/auto/qlowenergycharacteristic/tst_qlowenergycharacteristic.cpp b/tests/auto/qlowenergycharacteristic/tst_qlowenergycharacteristic.cpp
index 23189f51..e65b573c 100644
--- a/tests/auto/qlowenergycharacteristic/tst_qlowenergycharacteristic.cpp
+++ b/tests/auto/qlowenergycharacteristic/tst_qlowenergycharacteristic.cpp
@@ -116,7 +116,7 @@ void tst_QLowEnergyCharacteristic::initTestCase()
qDebug() << "Connecting to" << remoteDevice;
controller->connectToDevice();
QTRY_IMPL(controller->state() != QLowEnergyController::ConnectingState,
- 10000);
+ 20000);
if (controller->state() != QLowEnergyController::ConnectedState) {
// any error and we skip
delete controller;
diff --git a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
index 84223c91..4af96f3c 100644
--- a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
+++ b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
@@ -67,10 +67,9 @@ private slots:
void tst_concurrentDiscovery();
void tst_defaultBehavior();
void tst_writeCharacteristic();
+ void tst_writeCharacteristicNoResponse();
void tst_writeDescriptor();
- void tst_writeDescriptorNoResponse();
-
-
+ void tst_encryption();
private:
void verifyServiceProperties(const QLowEnergyService *info);
@@ -113,6 +112,7 @@ void tst_QLowEnergyController::initTestCase()
return;
}
+
devAgent = new QBluetoothDeviceDiscoveryAgent(this);
QSignalSpy finishedSpy(devAgent, SIGNAL(finished()));
@@ -1844,10 +1844,74 @@ void tst_QLowEnergyController::tst_writeDescriptor()
}
/*
+ * Tests encrypted read/write.
+ * This test is semi manual as the test device environment is very specific.
+ * Adjust the various uuids and addresses at the top to cater for the current
+ * situation. By default this test is skipped.
+ */
+void tst_QLowEnergyController::tst_encryption()
+{
+ QSKIP("Skipping encryption");
+
+ //Adjust the uuids and device address as see fit to match
+ //values that match the current test environment
+ //The target characteristic must be readble and writable
+ //under encryption to test dynamic switching of security level
+ QBluetoothAddress encryptedDevice(QString("00:02:5B:00:15:10"));
+ QBluetoothUuid serviceUuid(QBluetoothUuid::GenericAccess);
+ QBluetoothUuid characterristicUuid(QBluetoothUuid::DeviceName);
+
+ QLowEnergyController control(encryptedDevice);
+ QCOMPARE(control.error(), QLowEnergyController::NoError);
+
+ control.connectToDevice();
+ {
+ QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState,
+ 30000);
+ }
+
+ if (control.state() == QLowEnergyController::ConnectingState
+ || control.error() != QLowEnergyController::NoError) {
+ // default BTLE backend forever hangs in ConnectingState
+ QSKIP("Cannot connect to remote device");
+ }
+
+ QCOMPARE(control.state(), QLowEnergyController::ConnectedState);
+ QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished()));
+ QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState)));
+ control.discoverServices();
+ QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 10000);
+ QCOMPARE(stateSpy.count(), 2);
+ QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(),
+ QLowEnergyController::DiscoveringState);
+ QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(),
+ QLowEnergyController::DiscoveredState);
+
+ QList<QBluetoothUuid> uuids = control.services();
+ QVERIFY(uuids.contains(serviceUuid));
+
+ QLowEnergyService *service = control.createServiceObject(serviceUuid, this);
+ QVERIFY(service);
+ service->discoverDetails();
+ QTRY_VERIFY_WITH_TIMEOUT(
+ service->state() == QLowEnergyService::ServiceDiscovered, 30000);
+
+ QLowEnergyCharacteristic characteristic = service->characteristic(
+ characterristicUuid);
+
+ QVERIFY(characteristic.isValid());
+ qDebug() << "Encrypted char value:" << characteristic.value().toHex() << characteristic.value();
+ QVERIFY(!characteristic.value().isEmpty());
+
+ delete service;
+ control.disconnectFromDevice();
+}
+
+/*
Tests write without responses. We utilize the Over-The-Air image update
service of the SensorTag.
*/
-void tst_QLowEnergyController::tst_writeDescriptorNoResponse()
+void tst_QLowEnergyController::tst_writeCharacteristicNoResponse()
{
QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices();
if (localAdapters.isEmpty() || remoteDevice.isNull())
@@ -1932,8 +1996,6 @@ void tst_QLowEnergyController::tst_writeDescriptorNoResponse()
}
// 4. Trigger image identity announcement (using traditional write)
-
- QByteArray imageAValue, imageBValue;
QList<QVariant> entry;
bool foundOneImage = false;
diff --git a/tests/auto/qlowenergydescriptor/tst_qlowenergydescriptor.cpp b/tests/auto/qlowenergydescriptor/tst_qlowenergydescriptor.cpp
index e276b2ea..ff958cc6 100644
--- a/tests/auto/qlowenergydescriptor/tst_qlowenergydescriptor.cpp
+++ b/tests/auto/qlowenergydescriptor/tst_qlowenergydescriptor.cpp
@@ -119,7 +119,7 @@ void tst_QLowEnergyDescriptor::initTestCase()
qDebug() << "Connecting to" << remoteDevice;
controller->connectToDevice();
QTRY_IMPL(controller->state() != QLowEnergyController::ConnectingState,
- 10000);
+ 20000);
if (controller->state() != QLowEnergyController::ConnectedState) {
// any error and we skip
delete controller;