diff options
author | Alex Blasche <alexander.blasche@digia.com> | 2014-09-26 08:28:40 +0200 |
---|---|---|
committer | Alex Blasche <alexander.blasche@digia.com> | 2014-09-26 08:29:12 +0200 |
commit | c1aabdba2e839ba525a4918eebfbf6fbbe96d34d (patch) | |
tree | d154275f00d6a8cbcf792f298654fb6ab12f6044 | |
parent | dc34c7aae7d4d641f4d06990141c7915542363ee (diff) | |
parent | b7f4825cbf24c357d2f562ecdd81bc5109f4439f (diff) |
Merge branch '5.4' into btle
Change-Id: Ic5f6b510e43afbebc9bb76aa7bcd68a27d4a4a9b
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; |