diff options
Diffstat (limited to 'src/bluetooth/qlowenergycontroller_bluez.cpp')
-rw-r--r-- | src/bluetooth/qlowenergycontroller_bluez.cpp | 1630 |
1 files changed, 1494 insertions, 136 deletions
diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index 436b3a43..729847f9 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -1,57 +1,75 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd. -** Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> -** Contact: http://www.qt.io/licensing/ +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Javier S. Pedro <maemo@javispedro.com> +** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. ** -** $QT_BEGIN_LICENSE:LGPL21$ +** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms -** and conditions see http://www.qt.io/terms-conditions. For further -** information use the contact form at http://www.qt.io/contact-us. +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 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. +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** -** As a special exception, The Qt Company gives you certain additional -** rights. These rights are described in The Qt Company LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ +#include "lecmacverifier_p.h" #include "qlowenergycontroller_p.h" #include "qbluetoothsocket_p.h" +#include "qleadvertiser_p.h" #include "bluez/bluez_data_p.h" #include "bluez/hcimanager_p.h" +#include <QtCore/QFileInfo> #include <QtCore/QLoggingCategory> +#include <QtCore/QSettings> +#include <QtBluetooth/QBluetoothLocalDevice> #include <QtBluetooth/QBluetoothSocket> +#include <QtBluetooth/QLowEnergyCharacteristicData> +#include <QtBluetooth/QLowEnergyDescriptorData> #include <QtBluetooth/QLowEnergyService> +#include <QtBluetooth/QLowEnergyServiceData> +#include <algorithm> +#include <climits> +#include <cstring> #include <errno.h> - -#define ATTRIBUTE_CHANNEL_ID 4 +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> #define ATT_DEFAULT_LE_MTU 23 #define ATT_MAX_LE_MTU 0x200 -#define GATT_PRIMARY_SERVICE 0x2800 -#define GATT_SECONDARY_SERVICE 0x2801 -#define GATT_INCLUDED_SERVICE 0x2802 -#define GATT_CHARACTERISTIC 0x2803 +#define GATT_PRIMARY_SERVICE quint16(0x2800) +#define GATT_SECONDARY_SERVICE quint16(0x2801) +#define GATT_INCLUDED_SERVICE quint16(0x2802) +#define GATT_CHARACTERISTIC quint16(0x2803) // GATT commands #define ATT_OP_ERROR_RESPONSE 0x1 @@ -59,12 +77,16 @@ #define ATT_OP_EXCHANGE_MTU_RESPONSE 0x3 //receive server MTU #define ATT_OP_FIND_INFORMATION_REQUEST 0x4 //discover individual attribute info #define ATT_OP_FIND_INFORMATION_RESPONSE 0x5 +#define ATT_OP_FIND_BY_TYPE_VALUE_REQUEST 0x6 +#define ATT_OP_FIND_BY_TYPE_VALUE_RESPONSE 0x7 #define ATT_OP_READ_BY_TYPE_REQUEST 0x8 //discover characteristics #define ATT_OP_READ_BY_TYPE_RESPONSE 0x9 #define ATT_OP_READ_REQUEST 0xA //read characteristic & descriptor values #define ATT_OP_READ_RESPONSE 0xB #define ATT_OP_READ_BLOB_REQUEST 0xC //read values longer than MTU-1 #define ATT_OP_READ_BLOB_RESPONSE 0xD +#define ATT_OP_READ_MULTIPLE_REQUEST 0xE +#define ATT_OP_READ_MULTIPLE_RESPONSE 0xF #define ATT_OP_READ_BY_GROUP_REQUEST 0x10 //discover services #define ATT_OP_READ_BY_GROUP_RESPONSE 0x11 #define ATT_OP_WRITE_REQUEST 0x12 //write characteristic with response @@ -77,6 +99,7 @@ #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 +#define ATT_OP_SIGNED_WRITE_COMMAND 0xD2 //GATT command sizes in bytes #define ERROR_RESPONSE_HEADER_SIZE 5 @@ -118,6 +141,10 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) +using namespace QBluetooth; + +const int maxPrepareQueueSize = 1024; + static inline QBluetoothUuid convert_uuid128(const quint128 *p) { quint128 dst_hostOrder, dst_bigEndian; @@ -195,24 +222,49 @@ static void dumpErrorInformation(const QByteArray &response) << "handle:" << handle; } +static int getUuidSize(const QBluetoothUuid &uuid) +{ + return uuid.minimumSize() == 2 ? 2 : 16; +} + +template<typename T> static void putDataAndIncrement(const T &src, char *&dst) +{ + putBtData(src, dst); + dst += sizeof(T); +} +template<> void putDataAndIncrement(const QBluetoothUuid &uuid, char *&dst) +{ + const int uuidSize = getUuidSize(uuid); + if (uuidSize == 2) + putBtData(uuid.toUInt16(), dst); + else + putBtData(uuid.toUInt128(), dst); + dst += uuidSize; +} +template<> void putDataAndIncrement(const QByteArray &value, char *&dst) +{ + using namespace std; + memcpy(dst, value.constData(), value.count()); + dst += value.count(); +} + QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() : QObject(), state(QLowEnergyController::UnconnectedState), error(QLowEnergyController::NoError), + lastLocalHandle(0), l2cpSocket(0), requestPending(false), mtuSize(ATT_DEFAULT_LE_MTU), securityLevelValue(-1), encryptionChangePending(false), - hciManager(0) + hciManager(0), + advertiser(0), + serverSocketNotifier(0) { registerQLowEnergyControllerMetaType(); qRegisterMetaType<QList<QLowEnergyHandle> >(); } -QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() -{ -} - void QLowEnergyControllerPrivate::init() { hciManager = new HciManager(localAdapter, this); @@ -222,6 +274,135 @@ void QLowEnergyControllerPrivate::init() hciManager->monitorEvent(HciManager::EncryptChangeEvent); connect(hciManager, SIGNAL(encryptionChangedEvent(QBluetoothAddress,bool)), this, SLOT(encryptionChangedEvent(QBluetoothAddress,bool))); + hciManager->monitorEvent(HciManager::LeMetaEvent); + hciManager->monitorAclPackets(); + connect(hciManager, &HciManager::connectionComplete, [this](quint16 handle) { + connectionHandle = handle; + qCDebug(QT_BT_BLUEZ) << "received connection complete event, handle:" << handle; + }); + connect(hciManager, &HciManager::connectionUpdate, + [this](quint16 handle, const QLowEnergyConnectionParameters ¶ms) { + if (handle == connectionHandle) + emit q_ptr->connectionUpdated(params); + } + ); + connect(hciManager, &HciManager::signatureResolvingKeyReceived, + [this](quint16 handle, const quint128 &csrk) { + if (handle == connectionHandle) { + qCDebug(QT_BT_BLUEZ) << "received new signature resolving key" + << QByteArray(reinterpret_cast<const char *>(csrk.data), + sizeof csrk).toHex(); + signingData.insert(remoteDevice.toUInt64(), SigningData(csrk)); + } + } + ); +} + +QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() +{ + closeServerSocket(); + delete cmacVerifier; +} + +class ServerSocket +{ +public: + bool listen(const QBluetoothAddress &localAdapter) + { + m_socket = ::socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (m_socket == -1) { + qCWarning(QT_BT_BLUEZ) << "socket creation failed:" << qt_error_string(errno); + return false; + } + sockaddr_l2 addr; + + // memset should be in std namespace for C++ compilers, but we also need to support + // broken ones that put it in the global one. + using namespace std; + memset(&addr, 0, sizeof addr); + + addr.l2_family = AF_BLUETOOTH; + addr.l2_cid = htobs(ATTRIBUTE_CHANNEL_ID); + addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; + convertAddress(localAdapter.toUInt64(), addr.l2_bdaddr.b); + if (::bind(m_socket, reinterpret_cast<sockaddr *>(&addr), sizeof addr) == -1) { + qCWarning(QT_BT_BLUEZ) << "bind() failed:" << qt_error_string(errno); + return false; + } + if (::listen(m_socket, 1)) { + qCWarning(QT_BT_BLUEZ) << "listen() failed:" << qt_error_string(errno); + return false; + } + return true; + } + + ~ServerSocket() + { + if (m_socket != -1) + close(m_socket); + } + + int takeSocket() + { + const int socket = m_socket; + m_socket = -1; + return socket; + } + +private: + int m_socket = -1; +}; + + +void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData) +{ + qCDebug(QT_BT_BLUEZ) << "Starting to advertise"; + if (!advertiser) { + advertiser = new QLeAdvertiserBluez(params, advertisingData, scanResponseData, *hciManager, + this); + connect(advertiser, &QLeAdvertiser::errorOccurred, this, + &QLowEnergyControllerPrivate::handleAdvertisingError); + } + setState(QLowEnergyController::AdvertisingState); + advertiser->startAdvertising(); + if (params.mode() == QLowEnergyAdvertisingParameters::AdvNonConnInd + || params.mode() == QLowEnergyAdvertisingParameters::AdvScanInd) { + qCDebug(QT_BT_BLUEZ) << "Non-connectable advertising requested, " + "not listening for connections."; + return; + } + + ServerSocket serverSocket; + if (!serverSocket.listen(localAdapter)) { + setError(QLowEnergyController::AdvertisingError); + setState(QLowEnergyController::UnconnectedState); + return; + } + + const int socketFd = serverSocket.takeSocket(); + serverSocketNotifier = new QSocketNotifier(socketFd, QSocketNotifier::Read, this); + connect(serverSocketNotifier, &QSocketNotifier::activated, this, + &QLowEnergyControllerPrivate::handleConnectionRequest); +} + +void QLowEnergyControllerPrivate::stopAdvertising() +{ + setState(QLowEnergyController::UnconnectedState); + advertiser->stopAdvertising(); +} + +void QLowEnergyControllerPrivate::requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms) +{ + // The spec says that the connection update command can be used by both slave and master + // devices, but BlueZ allows it only for master devices. So for slave devices, we have to use a + // connection parameter update request, which we need to wrap in an ACL command, as BlueZ + // does not allow user-space sockets for the signaling channel. + if (role == QLowEnergyController::CentralRole) + hciManager->sendConnectionUpdateCommand(connectionHandle, params); + else + hciManager->sendConnectionParameterUpdateRequest(connectionHandle, params); } void QLowEnergyControllerPrivate::connectToDevice() @@ -299,7 +480,10 @@ void QLowEnergyControllerPrivate::l2cpDisconnected() { Q_Q(QLowEnergyController); - securityLevelValue = -1; + if (role == QLowEnergyController::PeripheralRole) + storeClientConfigurations(); + invalidateServices(); + resetController(); setState(QLowEnergyController::UnconnectedState); emit q->disconnected(); } @@ -336,23 +520,29 @@ void QLowEnergyControllerPrivate::l2cpErrorChanged(QBluetoothSocket::SocketError void QLowEnergyControllerPrivate::resetController() { openRequests.clear(); + openPrepareWriteRequests.clear(); + scheduledIndications.clear(); + indicationInFlight = false; requestPending = false; encryptionChangePending = false; + receivedMtuExchangeRequest = false; securityLevelValue = -1; + connectionHandle = 0; } void QLowEnergyControllerPrivate::l2cpReadyRead() { - const QByteArray reply = l2cpSocket->readAll(); - qCDebug(QT_BT_BLUEZ) << "Received size:" << reply.size() << "data:" << reply.toHex(); - if (reply.isEmpty()) + const QByteArray incomingPacket = l2cpSocket->readAll(); + qCDebug(QT_BT_BLUEZ) << "Received size:" << incomingPacket.size() << "data:" + << incomingPacket.toHex(); + if (incomingPacket.isEmpty()) return; - const quint8 command = reply.constData()[0]; + const quint8 command = incomingPacket.constData()[0]; switch (command) { case ATT_OP_HANDLE_VAL_NOTIFICATION: { - processUnsolicitedReply(reply); + processUnsolicitedReply(incomingPacket); return; } case ATT_OP_HANDLE_VAL_INDICATION: @@ -360,31 +550,55 @@ void QLowEnergyControllerPrivate::l2cpReadyRead() //send confirmation QByteArray packet; packet.append(static_cast<char>(ATT_OP_HANDLE_VAL_CONFIRMATION)); - sendCommand(packet); + sendPacket(packet); - processUnsolicitedReply(reply); + processUnsolicitedReply(incomingPacket); return; } + case ATT_OP_EXCHANGE_MTU_REQUEST: - case ATT_OP_READ_BY_GROUP_REQUEST: + handleExchangeMtuRequest(incomingPacket); + return; + case ATT_OP_FIND_INFORMATION_REQUEST: + handleFindInformationRequest(incomingPacket); + return; + case ATT_OP_FIND_BY_TYPE_VALUE_REQUEST: + handleFindByTypeValueRequest(incomingPacket); + return; case ATT_OP_READ_BY_TYPE_REQUEST: + handleReadByTypeRequest(incomingPacket); + return; case ATT_OP_READ_REQUEST: - case ATT_OP_FIND_INFORMATION_REQUEST: + handleReadRequest(incomingPacket); + return; + case ATT_OP_READ_BLOB_REQUEST: + handleReadBlobRequest(incomingPacket); + return; + case ATT_OP_READ_MULTIPLE_REQUEST: + handleReadMultipleRequest(incomingPacket); + return; + case ATT_OP_READ_BY_GROUP_REQUEST: + handleReadByGroupTypeRequest(incomingPacket); + return; case ATT_OP_WRITE_REQUEST: - { - qCDebug(QT_BT_BLUEZ) << "Server request" << hex << command; - - //send not supported - QByteArray packet(ERROR_RESPONSE_HEADER_SIZE, Qt::Uninitialized); - packet[0] = ATT_OP_ERROR_RESPONSE; - packet[1] = command; - bt_put_unaligned(htobs(0), (quint16 *)(packet.data() + 2)); - packet[4] = ATT_ERROR_REQUEST_NOT_SUPPORTED; - - sendCommand(packet); - + case ATT_OP_WRITE_COMMAND: + case ATT_OP_SIGNED_WRITE_COMMAND: + handleWriteRequestOrCommand(incomingPacket); + return; + case ATT_OP_PREPARE_WRITE_REQUEST: + handlePrepareWriteRequest(incomingPacket); + return; + case ATT_OP_EXECUTE_WRITE_REQUEST: + handleExecuteWriteRequest(incomingPacket); + return; + case ATT_OP_HANDLE_VAL_CONFIRMATION: + if (indicationInFlight) { + indicationInFlight = false; + sendNextIndication(); + } else { + qCWarning(QT_BT_BLUEZ) << "received unexpected handle value confirmation"; + } return; - } default: //only solicited replies finish pending requests requestPending = false; @@ -398,7 +612,7 @@ void QLowEnergyControllerPrivate::l2cpReadyRead() } const Request request = openRequests.dequeue(); - processReply(request, reply); + processReply(request, incomingPacket); sendNextPendingRequest(); } @@ -461,12 +675,12 @@ void QLowEnergyControllerPrivate::encryptionChangedEvent( sendNextPendingRequest(); } -void QLowEnergyControllerPrivate::sendCommand(const QByteArray &packet) +void QLowEnergyControllerPrivate::sendPacket(const QByteArray &packet) { qint64 result = l2cpSocket->write(packet.constData(), packet.size()); if (result == -1) { - qCDebug(QT_BT_BLUEZ) << "Cannot write L2CP command:" << hex + qCDebug(QT_BT_BLUEZ) << "Cannot write L2CP packet:" << hex << packet.toHex() << l2cpSocket->errorString(); setError(QLowEnergyController::NetworkError); @@ -483,7 +697,7 @@ void QLowEnergyControllerPrivate::sendNextPendingRequest() // << request.payload.toHex(); requestPending = true; - sendCommand(request.payload); + sendPacket(request.payload); } QLowEnergyHandle parseReadByTypeCharDiscovery( @@ -1087,9 +1301,9 @@ void QLowEnergyControllerPrivate::sendReadByGroupRequest( 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]); + putBtData(start, &packet[1]); + putBtData(end, &packet[3]); + putBtData(type, &packet[5]); QByteArray data(GRP_TYPE_REQ_HEADER_SIZE, Qt::Uninitialized); memcpy(data.data(), packet, GRP_TYPE_REQ_HEADER_SIZE); @@ -1125,9 +1339,9 @@ void QLowEnergyControllerPrivate::sendReadByTypeRequest( 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]); + putBtData(nextHandle, &packet[1]); + putBtData(serviceData->endHandle, &packet[3]); + putBtData(attributeType, &packet[5]); QByteArray data(READ_BY_TYPE_REQ_HEADER_SIZE, Qt::Uninitialized); memcpy(data.data(), packet, READ_BY_TYPE_REQ_HEADER_SIZE); @@ -1221,7 +1435,7 @@ void QLowEnergyControllerPrivate::readServiceValues( for (int i = 0; i < targetHandles.count(); i++) { pair = targetHandles.at(i); packet[0] = ATT_OP_READ_REQUEST; - bt_put_unaligned(htobs(pair.first), (quint16 *) &packet[1]); + putBtData(pair.first, &packet[1]); QByteArray data(READ_REQUEST_HEADER_SIZE, Qt::Uninitialized); memcpy(data.data(), packet, READ_REQUEST_HEADER_SIZE); @@ -1276,8 +1490,8 @@ void QLowEnergyControllerPrivate::readServiceValuesByOffset( } } - bt_put_unaligned(htobs(handleToRead), (quint16 *) &packet[1]); - bt_put_unaligned(htobs(offset), (quint16 *) &packet[3]); + putBtData(handleToRead, &packet[1]); + putBtData(offset, &packet[3]); QByteArray data(READ_BLOB_REQUEST_HEADER_SIZE, Qt::Uninitialized); memcpy(data.data(), packet, READ_BLOB_REQUEST_HEADER_SIZE); @@ -1340,7 +1554,7 @@ void QLowEnergyControllerPrivate::exchangeMTU() quint8 packet[MTU_EXCHANGE_HEADER_SIZE]; packet[0] = ATT_OP_EXCHANGE_MTU_REQUEST; - bt_put_unaligned(htobs(ATT_MAX_LE_MTU), (quint16 *) &packet[1]); + putBtData(quint16(ATT_MAX_LE_MTU), &packet[1]); QByteArray data(MTU_EXCHANGE_HEADER_SIZE, Qt::Uninitialized); memcpy(data.data(), packet, MTU_EXCHANGE_HEADER_SIZE); @@ -1458,8 +1672,8 @@ void QLowEnergyControllerPrivate::discoverNextDescriptor( else charEndHandle = pendingCharHandles[1] - 1; - bt_put_unaligned(htobs(charStartHandle), (quint16 *) &packet[1]); - bt_put_unaligned(htobs(charEndHandle), (quint16 *) &packet[3]); + putBtData(charStartHandle, &packet[1]); + putBtData(charEndHandle, &packet[3]); QByteArray data(FIND_INFO_REQUEST_HEADER_SIZE, Qt::Uninitialized); memcpy(data.data(), packet, FIND_INFO_REQUEST_HEADER_SIZE); @@ -1494,8 +1708,8 @@ void QLowEnergyControllerPrivate::sendNextPrepareWriteRequest( 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 + putBtData(targetHandle, &packet[1]); // attribute handle + putBtData(offset, &packet[3]); // offset into newValue qCDebug(QT_BT_BLUEZ) << "Writing long characteristic (prepare):" << hex << handle; @@ -1572,49 +1786,13 @@ void QLowEnergyControllerPrivate::writeCharacteristic( if (!service->characteristicList.contains(charHandle)) return; - const QLowEnergyHandle valueHandle = service->characteristicList[charHandle].valueHandle; - const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size(); - - 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; - } + QLowEnergyServicePrivate::CharData &charData = service->characteristicList[charHandle]; + if (role == QLowEnergyController::PeripheralRole) { + writeCharacteristicForPeripheral(charData, newValue); } else { - // write without response - packet[0] = ATT_OP_WRITE_COMMAND; + writeCharacteristicForCentral(charHandle, charData.valueHandle, newValue, + writeWithResponse); } - - bt_put_unaligned(htobs(valueHandle), (quint16 *) &packet[1]); - - QByteArray data(size, Qt::Uninitialized); - 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 << "with response:" << writeWithResponse << ")"; - - // Advantage of write without response is the quick turnaround. - // It can be send at any time and does not produce responses. - // Therefore we will not put them into the openRequest queue at all. - if (!writeWithResponse) { - sendCommand(data); - return; - } - - Request request; - request.payload = data; - request.command = ATT_OP_WRITE_REQUEST; - request.reference = charHandle; - request.reference2 = newValue; - openRequests.enqueue(request); - - sendNextPendingRequest(); } void QLowEnergyControllerPrivate::writeDescriptor( @@ -1625,32 +1803,10 @@ void QLowEnergyControllerPrivate::writeDescriptor( { Q_ASSERT(!service.isNull()); - if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) { - sendNextPrepareWriteRequest(descriptorHandle, newValue, 0); - sendNextPendingRequest(); - return; - } - - 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_HEADER_SIZE); - memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size()); - - qCDebug(QT_BT_BLUEZ) << "Writing descriptor" << hex << descriptorHandle - << "(size:" << size << ")"; - - Request request; - request.payload = data; - request.command = ATT_OP_WRITE_REQUEST; - request.reference = (charHandle | (descriptorHandle << 16)); - request.reference2 = newValue; - openRequests.enqueue(request); - - sendNextPendingRequest(); + if (role == QLowEnergyController::PeripheralRole) + writeDescriptorForPeripheral(service, charHandle, descriptorHandle, newValue); + else + writeDescriptorForCentral(charHandle, descriptorHandle, newValue); } /*! @@ -1677,7 +1833,7 @@ void QLowEnergyControllerPrivate::readCharacteristic( quint8 packet[READ_REQUEST_HEADER_SIZE]; packet[0] = ATT_OP_READ_REQUEST; - bt_put_unaligned(htobs(charDetails.valueHandle), (quint16 *) &packet[1]); + putBtData(charDetails.valueHandle, &packet[1]); QByteArray data(READ_REQUEST_HEADER_SIZE, Qt::Uninitialized); memcpy(data.data(), packet, READ_REQUEST_HEADER_SIZE); @@ -1712,7 +1868,7 @@ void QLowEnergyControllerPrivate::readDescriptor( quint8 packet[READ_REQUEST_HEADER_SIZE]; packet[0] = ATT_OP_READ_REQUEST; - bt_put_unaligned(htobs(descriptorHandle), (quint16 *) &packet[1]); + putBtData(descriptorHandle, &packet[1]); QByteArray data(READ_REQUEST_HEADER_SIZE, Qt::Uninitialized); memcpy(data.data(), packet, READ_REQUEST_HEADER_SIZE); @@ -1761,4 +1917,1206 @@ bool QLowEnergyControllerPrivate::increaseEncryptLevelfRequired(quint8 errorCode return false; } +void QLowEnergyControllerPrivate::handleAdvertisingError() +{ + qCWarning(QT_BT_BLUEZ) << "received advertising error"; + setError(QLowEnergyController::AdvertisingError); + setState(QLowEnergyController::UnconnectedState); +} + +bool QLowEnergyControllerPrivate::checkPacketSize(const QByteArray &packet, int minSize, + int maxSize) +{ + if (maxSize == -1) + maxSize = minSize; + if (Q_LIKELY(packet.count() >= minSize && packet.count() <= maxSize)) + return true; + qCWarning(QT_BT_BLUEZ) << "client request of type" << packet.at(0) + << "has unexpected packet size" << packet.count(); + sendErrorResponse(packet.at(0), 0, ATT_ERROR_INVALID_PDU); + return false; +} + +bool QLowEnergyControllerPrivate::checkHandle(const QByteArray &packet, QLowEnergyHandle handle) +{ + if (handle != 0 && handle <= lastLocalHandle) + return true; + sendErrorResponse(packet.at(0), handle, ATT_ERROR_INVALID_HANDLE); + return false; +} + +bool QLowEnergyControllerPrivate::checkHandlePair(quint8 request, QLowEnergyHandle startingHandle, + QLowEnergyHandle endingHandle) +{ + if (startingHandle == 0 || startingHandle > endingHandle) { + qCDebug(QT_BT_BLUEZ) << "handle range invalid"; + sendErrorResponse(request, startingHandle, ATT_ERROR_INVALID_HANDLE); + return false; + } + return true; +} + +void QLowEnergyControllerPrivate::handleExchangeMtuRequest(const QByteArray &packet) +{ + // Spec v4.2, Vol 3, Part F, 3.4.2 + + if (!checkPacketSize(packet, 3)) + return; + if (receivedMtuExchangeRequest) { // Client must only send this once per connection. + qCDebug(QT_BT_BLUEZ) << "Client sent extraneous MTU exchange packet"; + sendErrorResponse(packet.at(0), 0, ATT_ERROR_REQUEST_NOT_SUPPORTED); + return; + } + receivedMtuExchangeRequest = true; + + // Send reply. + QByteArray reply(MTU_EXCHANGE_HEADER_SIZE, Qt::Uninitialized); + reply[0] = ATT_OP_EXCHANGE_MTU_RESPONSE; + putBtData(static_cast<quint16>(ATT_MAX_LE_MTU), reply.data() + 1); + sendPacket(reply); + + // Apply requested MTU. + const quint16 clientRxMtu = bt_get_le16(packet.constData() + 1); + mtuSize = qMax<quint16>(ATT_DEFAULT_LE_MTU, qMin<quint16>(clientRxMtu, ATT_MAX_LE_MTU)); + qCDebug(QT_BT_BLUEZ) << "MTU request from client:" << clientRxMtu + << "effective client RX MTU:" << mtuSize; + qCDebug(QT_BT_BLUEZ) << "Sending server RX MTU" << ATT_MAX_LE_MTU; +} + +void QLowEnergyControllerPrivate::handleFindInformationRequest(const QByteArray &packet) +{ + // Spec v4.2, Vol 3, Part F, 3.4.3.1-2 + + if (!checkPacketSize(packet, 5)) + return; + const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1); + const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3); + qCDebug(QT_BT_BLUEZ) << "client sends find information request; start:" << startingHandle + << "end:" << endingHandle; + if (!checkHandlePair(packet.at(0), startingHandle, endingHandle)) + return; + + QVector<Attribute> results = getAttributes(startingHandle, endingHandle); + if (results.isEmpty()) { + sendErrorResponse(packet.at(0), startingHandle, ATT_ERROR_ATTRIBUTE_NOT_FOUND); + return; + } + ensureUniformUuidSizes(results); + + QByteArray responsePrefix(2, Qt::Uninitialized); + const int uuidSize = getUuidSize(results.first().type); + responsePrefix[0] = ATT_OP_FIND_INFORMATION_RESPONSE; + responsePrefix[1] = uuidSize == 2 ? 0x1 : 0x2; + const int elementSize = sizeof(QLowEnergyHandle) + uuidSize; + const auto elemWriter = [](const Attribute &attr, char *&data) { + putDataAndIncrement(attr.handle, data); + putDataAndIncrement(attr.type, data); + }; + sendListResponse(responsePrefix, elementSize, results, elemWriter); + +} + +void QLowEnergyControllerPrivate::handleFindByTypeValueRequest(const QByteArray &packet) +{ + // Spec v4.2, Vol 3, Part F, 3.4.3.3-4 + + if (!checkPacketSize(packet, 7, mtuSize)) + return; + const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1); + const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3); + const quint16 type = bt_get_le16(packet.constData() + 5); + const QByteArray value = QByteArray::fromRawData(packet.constData() + 7, packet.count() - 7); + qCDebug(QT_BT_BLUEZ) << "client sends find by type value request; start:" << startingHandle + << "end:" << endingHandle << "type:" << type + << "value:" << value.toHex(); + if (!checkHandlePair(packet.at(0), startingHandle, endingHandle)) + return; + + const auto predicate = [value, this, type](const Attribute &attr) { + return attr.type == QBluetoothUuid(type) && attr.value == value + && checkReadPermissions(attr) == 0; + }; + const QVector<Attribute> results = getAttributes(startingHandle, endingHandle, predicate); + if (results.isEmpty()) { + sendErrorResponse(packet.at(0), startingHandle, ATT_ERROR_ATTRIBUTE_NOT_FOUND); + return; + } + + QByteArray responsePrefix(1, ATT_OP_FIND_BY_TYPE_VALUE_RESPONSE); + const int elemSize = 2 * sizeof(QLowEnergyHandle); + const auto elemWriter = [](const Attribute &attr, char *&data) { + putDataAndIncrement(attr.handle, data); + putDataAndIncrement(attr.groupEndHandle, data); + }; + sendListResponse(responsePrefix, elemSize, results, elemWriter); +} + +void QLowEnergyControllerPrivate::handleReadByTypeRequest(const QByteArray &packet) +{ + // Spec v4.2, Vol 3, Part F, 3.4.4.1-2 + + if (!checkPacketSize(packet, 7, 21)) + return; + const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1); + const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3); + const void * const typeStart = packet.constData() + 5; + const bool is16BitUuid = packet.count() == 7; + const bool is128BitUuid = packet.count() == 21; + QBluetoothUuid type; + if (is16BitUuid) { + type = QBluetoothUuid(bt_get_le16(typeStart)); + } else if (is128BitUuid) { + type = QBluetoothUuid(convert_uuid128(reinterpret_cast<const quint128 *>(typeStart))); + } else { + qCWarning(QT_BT_BLUEZ) << "read by type request has invalid packet size" << packet.count(); + sendErrorResponse(packet.at(0), 0, ATT_ERROR_INVALID_PDU); + return; + } + qCDebug(QT_BT_BLUEZ) << "client sends read by type request, start:" << startingHandle + << "end:" << endingHandle << "type:" << type; + if (!checkHandlePair(packet.at(0), startingHandle, endingHandle)) + return; + + // Get all attributes with matching type. + QVector<Attribute> results = getAttributes(startingHandle, endingHandle, + [type](const Attribute &attr) { return attr.type == type; }); + ensureUniformValueSizes(results); + + if (results.isEmpty()) { + sendErrorResponse(packet.at(0), startingHandle, ATT_ERROR_ATTRIBUTE_NOT_FOUND); + return; + } + + const int error = checkReadPermissions(results); + if (error) { + sendErrorResponse(packet.at(0), results.first().handle, error); + return; + } + + const int elementSize = sizeof(QLowEnergyHandle) + results.first().value.count(); + QByteArray responsePrefix(2, Qt::Uninitialized); + responsePrefix[0] = ATT_OP_READ_BY_TYPE_RESPONSE; + responsePrefix[1] = elementSize; + const auto elemWriter = [](const Attribute &attr, char *&data) { + putDataAndIncrement(attr.handle, data); + putDataAndIncrement(attr.value, data); + }; + sendListResponse(responsePrefix, elementSize, results, elemWriter); +} + +void QLowEnergyControllerPrivate::handleReadRequest(const QByteArray &packet) +{ + // Spec v4.2, Vol 3, Part F, 3.4.4.3-4 + + if (!checkPacketSize(packet, 3)) + return; + const QLowEnergyHandle handle = bt_get_le16(packet.constData() + 1); + qCDebug(QT_BT_BLUEZ) << "client sends read request; handle:" << handle; + + if (!checkHandle(packet, handle)) + return; + const Attribute &attribute = localAttributes.at(handle); + const int permissionsError = checkReadPermissions(attribute); + if (permissionsError) { + sendErrorResponse(packet.at(0), handle, permissionsError); + return; + } + + const int sentValueLength = qMin(attribute.value.count(), mtuSize - 1); + QByteArray response(1 + sentValueLength, Qt::Uninitialized); + response[0] = ATT_OP_READ_RESPONSE; + using namespace std; + memcpy(response.data() + 1, attribute.value.constData(), sentValueLength); + qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex(); + sendPacket(response); +} + +void QLowEnergyControllerPrivate::handleReadBlobRequest(const QByteArray &packet) +{ + // Spec v4.2, Vol 3, Part F, 3.4.4.5-6 + + if (!checkPacketSize(packet, 5)) + return; + const QLowEnergyHandle handle = bt_get_le16(packet.constData() + 1); + const quint16 valueOffset = bt_get_le16(packet.constData() + 3); + qCDebug(QT_BT_BLUEZ) << "client sends read blob request; handle:" << handle + << "offset:" << valueOffset; + + if (!checkHandle(packet, handle)) + return; + const Attribute &attribute = localAttributes.at(handle); + const int permissionsError = checkReadPermissions(attribute); + if (permissionsError) { + sendErrorResponse(packet.at(0), handle, permissionsError); + return; + } + if (valueOffset > attribute.value.count()) { + sendErrorResponse(packet.at(0), handle, ATT_ERROR_INVALID_OFFSET); + return; + } + if (attribute.value.count() <= mtuSize - 3) { + sendErrorResponse(packet.at(0), handle, ATT_ERROR_ATTRIBUTE_NOT_LONG); + return; + } + + // Yes, this value can be zero. + const int sentValueLength = qMin(attribute.value.count() - valueOffset, mtuSize - 1); + + QByteArray response(1 + sentValueLength, Qt::Uninitialized); + response[0] = ATT_OP_READ_BLOB_RESPONSE; + using namespace std; + memcpy(response.data() + 1, attribute.value.constData() + valueOffset, sentValueLength); + qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex(); + sendPacket(response); +} + +void QLowEnergyControllerPrivate::handleReadMultipleRequest(const QByteArray &packet) +{ + // Spec v4.2, Vol 3, Part F, 3.4.4.7-8 + + if (!checkPacketSize(packet, 5, mtuSize)) + return; + QVector<QLowEnergyHandle> handles((packet.count() - 1) / sizeof(QLowEnergyHandle)); + auto *packetPtr = reinterpret_cast<const QLowEnergyHandle *>(packet.constData() + 1); + for (int i = 0; i < handles.count(); ++i, ++packetPtr) + handles[i] = bt_get_le16(packetPtr); + qCDebug(QT_BT_BLUEZ) << "client sends read multiple request for handles" << handles; + + const auto it = std::find_if(handles.constBegin(), handles.constEnd(), + [this](QLowEnergyHandle handle) { return handle >= lastLocalHandle; }); + if (it != handles.constEnd()) { + sendErrorResponse(packet.at(0), *it, ATT_ERROR_INVALID_HANDLE); + return; + } + const QVector<Attribute> results = getAttributes(handles.first(), handles.last()); + QByteArray response(1, ATT_OP_READ_MULTIPLE_RESPONSE); + foreach (const Attribute &attr, results) { + const int error = checkReadPermissions(attr); + if (error) { + sendErrorResponse(packet.at(0), attr.handle, error); + return; + } + + // Note: We do not abort if no more values fit into the packet, because we still have to + // report possible permission errors for the other handles. + response += attr.value.left(mtuSize - response.count()); + } + + qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex(); + sendPacket(response); +} + +void QLowEnergyControllerPrivate::handleReadByGroupTypeRequest(const QByteArray &packet) +{ + // Spec v4.2, Vol 3, Part F, 3.4.4.9-10 + + if (!checkPacketSize(packet, 7, 21)) + return; + const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1); + const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3); + const bool is16BitUuid = packet.count() == 7; + const bool is128BitUuid = packet.count() == 21; + const void * const typeStart = packet.constData() + 5; + QBluetoothUuid type; + if (is16BitUuid) { + type = QBluetoothUuid(bt_get_le16(typeStart)); + } else if (is128BitUuid) { + type = QBluetoothUuid(convert_uuid128(reinterpret_cast<const quint128 *>(typeStart))); + } else { + qCWarning(QT_BT_BLUEZ) << "read by group type request has invalid packet size" + << packet.count(); + sendErrorResponse(packet.at(0), 0, ATT_ERROR_INVALID_PDU); + return; + } + qCDebug(QT_BT_BLUEZ) << "client sends read by group type request, start:" << startingHandle + << "end:" << endingHandle << "type:" << type; + + if (!checkHandlePair(packet.at(0), startingHandle, endingHandle)) + return; + if (type != QBluetoothUuid(static_cast<quint16>(GATT_PRIMARY_SERVICE)) + && type != QBluetoothUuid(static_cast<quint16>(GATT_SECONDARY_SERVICE))) { + sendErrorResponse(packet.at(0), startingHandle, ATT_ERROR_UNSUPPRTED_GROUP_TYPE); + return; + } + + QVector<Attribute> results = getAttributes(startingHandle, endingHandle, + [type](const Attribute &attr) { return attr.type == type; }); + if (results.isEmpty()) { + sendErrorResponse(packet.at(0), startingHandle, ATT_ERROR_ATTRIBUTE_NOT_FOUND); + return; + } + const int error = checkReadPermissions(results); + if (error) { + sendErrorResponse(packet.at(0), results.first().handle, error); + return; + } + + ensureUniformValueSizes(results); + + const int elementSize = 2 * sizeof(QLowEnergyHandle) + results.first().value.count(); + QByteArray responsePrefix(2, Qt::Uninitialized); + responsePrefix[0] = ATT_OP_READ_BY_GROUP_RESPONSE; + responsePrefix[1] = elementSize; + const auto elemWriter = [](const Attribute &attr, char *&data) { + putDataAndIncrement(attr.handle, data); + putDataAndIncrement(attr.groupEndHandle, data); + putDataAndIncrement(attr.value, data); + }; + sendListResponse(responsePrefix, elementSize, results, elemWriter); +} + +void QLowEnergyControllerPrivate::updateLocalAttributeValue( + QLowEnergyHandle handle, + const QByteArray &value, + QLowEnergyCharacteristic &characteristic, + QLowEnergyDescriptor &descriptor) +{ + localAttributes[handle].value = value; + foreach (const auto &service, localServices) { + if (handle < service->startHandle || handle > service->endHandle) + continue; + for (auto charIt = service->characteristicList.begin(); + charIt != service->characteristicList.end(); ++charIt) { + QLowEnergyServicePrivate::CharData &charData = charIt.value(); + if (handle == charIt.key() + 1) { // Char value decl comes right after char decl. + charData.value = value; + characteristic = QLowEnergyCharacteristic(service, charIt.key()); + return; + } + for (auto descIt = charData.descriptorList.begin(); + descIt != charData.descriptorList.end(); ++descIt) { + if (handle == descIt.key()) { + descIt.value().value = value; + descriptor = QLowEnergyDescriptor(service, charIt.key(), handle); + return; + } + } + } + } + qFatal("local services map inconsistent with local attribute map"); +} + +static bool isNotificationEnabled(quint16 clientConfigValue) { return clientConfigValue & 0x1; } +static bool isIndicationEnabled(quint16 clientConfigValue) { return clientConfigValue & 0x2; } + +void QLowEnergyControllerPrivate::writeCharacteristicForPeripheral( + QLowEnergyServicePrivate::CharData &charData, + const QByteArray &newValue) +{ + const QLowEnergyHandle valueHandle = charData.valueHandle; + Q_ASSERT(valueHandle <= lastLocalHandle); + Attribute &attribute = localAttributes[valueHandle]; + if (newValue.count() < attribute.minLength || newValue.count() > attribute.maxLength) { + qCWarning(QT_BT_BLUEZ) << "ignoring value of invalid length" << newValue.count() + << "for attribute" << valueHandle; + return; + } + attribute.value = newValue; + charData.value = newValue; + const bool hasNotifyProperty = attribute.properties & QLowEnergyCharacteristic::Notify; + const bool hasIndicateProperty + = attribute.properties & QLowEnergyCharacteristic::Indicate; + if (!hasNotifyProperty && !hasIndicateProperty) + return; + foreach (const QLowEnergyServicePrivate::DescData &desc, charData.descriptorList) { + if (desc.uuid != QBluetoothUuid::ClientCharacteristicConfiguration) + continue; + + // Notify/indicate currently connected client. + const bool isConnected = state == QLowEnergyController::ConnectedState; + if (isConnected) { + Q_ASSERT(desc.value.count() == 2); + quint16 configValue = bt_get_le16(desc.value.constData()); + if (isNotificationEnabled(configValue) && hasNotifyProperty) { + sendNotification(valueHandle); + } else if (isIndicationEnabled(configValue) && hasIndicateProperty) { + if (indicationInFlight) + scheduledIndications << valueHandle; + else + sendIndication(valueHandle); + } + } + + // Prepare notification/indication of unconnected, bonded clients. + for (auto it = clientConfigData.begin(); it != clientConfigData.end(); ++it) { + if (isConnected && it.key() == remoteDevice.toUInt64()) + continue; + QVector<ClientConfigurationData> &configDataList = it.value(); + for (ClientConfigurationData &configData : configDataList) { + if (configData.charValueHandle != valueHandle) + continue; + if ((isNotificationEnabled(configData.configValue) && hasNotifyProperty) + || (isIndicationEnabled(configData.configValue) && hasIndicateProperty)) { + configData.charValueWasUpdated = true; + break; + } + } + } + break; + } +} + +void QLowEnergyControllerPrivate::writeCharacteristicForCentral( + QLowEnergyHandle charHandle, + QLowEnergyHandle valueHandle, + const QByteArray &newValue, + bool writeWithResponse) +{ + const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size(); + + 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; + } + + putBtData(valueHandle, &packet[1]); + + QByteArray data(size, Qt::Uninitialized); + 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 << "with response:" << writeWithResponse << ")"; + + // Advantage of write without response is the quick turnaround. + // It can be send at any time and does not produce responses. + // Therefore we will not put them into the openRequest queue at all. + if (!writeWithResponse) { + sendPacket(data); + return; + } + + Request request; + request.payload = data; + request.command = ATT_OP_WRITE_REQUEST; + request.reference = charHandle; + request.reference2 = newValue; + openRequests.enqueue(request); + + sendNextPendingRequest(); +} + +void QLowEnergyControllerPrivate::writeDescriptorForPeripheral( + const QSharedPointer<QLowEnergyServicePrivate> &service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle, + const QByteArray &newValue) +{ + Q_ASSERT(descriptorHandle <= lastLocalHandle); + Attribute &attribute = localAttributes[descriptorHandle]; + if (newValue.count() < attribute.minLength || newValue.count() > attribute.maxLength) { + qCWarning(QT_BT_BLUEZ) << "invalid value of size" << newValue.count() + << "for attribute" << descriptorHandle; + return; + } + attribute.value = newValue; + service->characteristicList[charHandle].descriptorList[descriptorHandle].value = newValue; +} + +void QLowEnergyControllerPrivate::writeDescriptorForCentral( + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle, + const QByteArray &newValue) +{ + if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) { + sendNextPrepareWriteRequest(descriptorHandle, newValue, 0); + sendNextPendingRequest(); + return; + } + + quint8 packet[WRITE_REQUEST_HEADER_SIZE]; + packet[0] = ATT_OP_WRITE_REQUEST; + putBtData(descriptorHandle, &packet[1]); + + const int size = WRITE_REQUEST_HEADER_SIZE + newValue.size(); + QByteArray data(size, Qt::Uninitialized); + 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 << ")"; + + Request request; + request.payload = data; + request.command = ATT_OP_WRITE_REQUEST; + request.reference = (charHandle | (descriptorHandle << 16)); + request.reference2 = newValue; + openRequests.enqueue(request); + + sendNextPendingRequest(); +} + +void QLowEnergyControllerPrivate::handleWriteRequestOrCommand(const QByteArray &packet) +{ + // Spec v4.2, Vol 3, Part F, 3.4.5.1-3 + + const bool isRequest = packet.at(0) == ATT_OP_WRITE_REQUEST; + const bool isSigned = quint8(packet.at(0)) == quint8(ATT_OP_SIGNED_WRITE_COMMAND); + if (!checkPacketSize(packet, isSigned ? 15 : 3, mtuSize)) + return; + const QLowEnergyHandle handle = bt_get_le16(packet.constData() + 1); + qCDebug(QT_BT_BLUEZ) << "client sends" << (isSigned ? "signed" : "") << "write" + << (isRequest ? "request" : "command") << "for handle" << handle; + + if (!checkHandle(packet, handle)) + return; + + Attribute &attribute = localAttributes[handle]; + const QLowEnergyCharacteristic::PropertyType type = isRequest + ? QLowEnergyCharacteristic::Write : isSigned + ? QLowEnergyCharacteristic::WriteSigned : QLowEnergyCharacteristic::WriteNoResponse; + const int permissionsError = checkPermissions(attribute, type); + if (permissionsError) { + sendErrorResponse(packet.at(0), handle, permissionsError); + return; + } + + int valueLength; + if (isSigned) { + if (!isBonded()) { + qCWarning(QT_BT_BLUEZ) << "Ignoring signed write from non-bonded device."; + return; + } + if (securityLevel() >= BT_SECURITY_MEDIUM) { + qCWarning(QT_BT_BLUEZ) << "Ignoring signed write on encrypted link."; + return; + } + const auto signingDataIt = signingData.find(remoteDevice.toUInt64()); + if (signingDataIt == signingData.constEnd()) { + qCWarning(QT_BT_BLUEZ) << "No CSRK found for peer device, ignoring signed write"; + return; + } + + const quint32 signCounter = getBtData<quint32>(packet.data() + packet.count() - 12); + if (signCounter < signingDataIt.value().counter + 1) { + qCWarning(QT_BT_BLUEZ) << "Client's' sign counter" << signCounter + << "not greater than local sign counter" + << signingDataIt.value().counter + << "; ignoring signed write command."; + return; + } + + const quint64 macFromClient = getBtData<quint64>(packet.data() + packet.count() - 8); + const bool signatureCorrect = verifyMac(packet.left(packet.count() - 12), + signingDataIt.value().key, signCounter, macFromClient); + if (!signatureCorrect) { + qCWarning(QT_BT_BLUEZ) << "Signed Write packet has wrong signature, disconnecting"; + disconnectFromDevice(); // Recommended by spec v4.2, Vol 3, part C, 10.4.2 + return; + } + + signingDataIt.value().counter = signCounter; + storeSignCounter(); + valueLength = packet.count() - 15; + } else { + valueLength = packet.count() - 3; + } + + if (valueLength > attribute.maxLength) { + sendErrorResponse(packet.at(0), handle, ATT_ERROR_INVAL_ATTR_VALUE_LEN); + return; + } + + // If the attribute value has a fixed size and the value in the packet is shorter, + // then we overwrite only the start of the attribute value and keep the rest. + QByteArray value = packet.mid(3, valueLength); + if (attribute.minLength == attribute.maxLength && valueLength < attribute.minLength) + value += attribute.value.mid(valueLength, attribute.maxLength - valueLength); + + QLowEnergyCharacteristic characteristic; + QLowEnergyDescriptor descriptor; + updateLocalAttributeValue(handle, value, characteristic, descriptor); + + if (isRequest) { + const QByteArray response = QByteArray(1, ATT_OP_WRITE_RESPONSE); + sendPacket(response); + } + + if (characteristic.isValid()) { + emit characteristic.d_ptr->characteristicChanged(characteristic, value); + } else { + Q_ASSERT(descriptor.isValid()); + emit descriptor.d_ptr->descriptorWritten(descriptor, value); + } +} + +void QLowEnergyControllerPrivate::handlePrepareWriteRequest(const QByteArray &packet) +{ + // Spec v4.2, Vol 3, Part F, 3.4.6.1 + + if (!checkPacketSize(packet, 5, mtuSize)) + return; + const quint16 handle = bt_get_le16(packet.constData() + 1); + qCDebug(QT_BT_BLUEZ) << "client sends prepare write request for handle" << handle; + + if (!checkHandle(packet, handle)) + return; + const Attribute &attribute = localAttributes.at(handle); + const int permissionsError = checkPermissions(attribute, QLowEnergyCharacteristic::Write); + if (permissionsError) { + sendErrorResponse(packet.at(0), handle, permissionsError); + return; + } + if (openPrepareWriteRequests.count() >= maxPrepareQueueSize) { + sendErrorResponse(packet.at(0), handle, ATT_ERROR_PREPARE_QUEUE_FULL); + return; + } + + // The value is not checked here, but on the Execute request. + openPrepareWriteRequests << WriteRequest(handle, bt_get_le16(packet.constData() + 3), + packet.mid(5)); + + QByteArray response = packet; + response[0] = ATT_OP_PREPARE_WRITE_RESPONSE; + sendPacket(response); +} + +void QLowEnergyControllerPrivate::handleExecuteWriteRequest(const QByteArray &packet) +{ + // Spec v4.2, Vol 3, Part F, 3.4.6.3 + + if (!checkPacketSize(packet, 2)) + return; + const bool cancel = packet.at(1) == 0; + qCDebug(QT_BT_BLUEZ) << "client sends execute write request; flag is" + << (cancel ? "cancel" : "flush"); + + QVector<WriteRequest> requests = openPrepareWriteRequests; + openPrepareWriteRequests.clear(); + QVector<QLowEnergyCharacteristic> characteristics; + QVector<QLowEnergyDescriptor> descriptors; + if (!cancel) { + foreach (const WriteRequest &request, requests) { + Attribute &attribute = localAttributes[request.handle]; + if (request.valueOffset > attribute.value.count()) { + sendErrorResponse(packet.at(0), request.handle, ATT_ERROR_INVALID_OFFSET); + return; + } + const QByteArray newValue = attribute.value.left(request.valueOffset) + request.value; + if (newValue.count() > attribute.maxLength) { + sendErrorResponse(packet.at(0), request.handle, ATT_ERROR_INVAL_ATTR_VALUE_LEN); + return; + } + QLowEnergyCharacteristic characteristic; + QLowEnergyDescriptor descriptor; + // TODO: Redundant attribute lookup for the case of the same handle appearing + // more than once. + updateLocalAttributeValue(request.handle, newValue, characteristic, descriptor); + if (characteristic.isValid()) { + characteristics << characteristic; + } else if (descriptor.isValid()) { + Q_ASSERT(descriptor.isValid()); + descriptors << descriptor; + } + } + } + + sendPacket(QByteArray(1, ATT_OP_EXECUTE_WRITE_RESPONSE)); + + foreach (const QLowEnergyCharacteristic &characteristic, characteristics) + emit characteristic.d_ptr->characteristicChanged(characteristic, characteristic.value()); + foreach (const QLowEnergyDescriptor &descriptor, descriptors) + emit descriptor.d_ptr->descriptorWritten(descriptor, descriptor.value()); +} + +void QLowEnergyControllerPrivate::sendErrorResponse(quint8 request, quint16 handle, quint8 code) +{ + // An ATT command never receives an error response. + if (request == ATT_OP_WRITE_COMMAND || request == ATT_OP_SIGNED_WRITE_COMMAND) + return; + + QByteArray packet(ERROR_RESPONSE_HEADER_SIZE, Qt::Uninitialized); + packet[0] = ATT_OP_ERROR_RESPONSE; + packet[1] = request; + putBtData(handle, packet.data() + 2); + packet[4] = code; + qCWarning(QT_BT_BLUEZ) << "sending error response; request:" << request << "handle:" << handle + << "code:" << code; + sendPacket(packet); +} + +void QLowEnergyControllerPrivate::sendListResponse(const QByteArray &packetStart, int elemSize, + const QVector<Attribute> &attributes, const ElemWriter &elemWriter) +{ + const int offset = packetStart.count(); + const int elemCount = qMin(attributes.count(), (mtuSize - offset) / elemSize); + const int totalPacketSize = offset + elemCount * elemSize; + QByteArray response(totalPacketSize, Qt::Uninitialized); + using namespace std; + memcpy(response.data(), packetStart.constData(), offset); + char *data = response.data() + offset; + for_each(attributes.constBegin(), attributes.constBegin() + elemCount, + [&data, elemWriter](const Attribute &attr) { elemWriter(attr, data); }); + qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex(); + sendPacket(response); +} + +void QLowEnergyControllerPrivate::sendNotification(QLowEnergyHandle handle) +{ + sendNotificationOrIndication(ATT_OP_HANDLE_VAL_NOTIFICATION, handle); +} + +void QLowEnergyControllerPrivate::sendIndication(QLowEnergyHandle handle) +{ + Q_ASSERT(!indicationInFlight); + indicationInFlight = true; + sendNotificationOrIndication(ATT_OP_HANDLE_VAL_INDICATION, handle); +} + +void QLowEnergyControllerPrivate::sendNotificationOrIndication( + quint8 opCode, + QLowEnergyHandle handle) +{ + Q_ASSERT(handle <= lastLocalHandle); + const Attribute &attribute = localAttributes.at(handle); + const int maxValueLength = qMin(attribute.value.count(), mtuSize - 3); + QByteArray packet(3 + maxValueLength, Qt::Uninitialized); + packet[0] = opCode; + putBtData(handle, packet.data() + 1); + using namespace std; + memcpy(packet.data() + 3, attribute.value.constData(), maxValueLength); + qCDebug(QT_BT_BLUEZ) << "sending notification/indication:" << packet.toHex(); + sendPacket(packet); +} + +void QLowEnergyControllerPrivate::sendNextIndication() +{ + if (!scheduledIndications.isEmpty()) + sendIndication(scheduledIndications.takeFirst()); +} + +void QLowEnergyControllerPrivate::handleConnectionRequest() +{ + if (state != QLowEnergyController::AdvertisingState) { + qCWarning(QT_BT_BLUEZ) << "Incoming connection request in unexpected state" << state; + return; + } + Q_ASSERT(serverSocketNotifier); + serverSocketNotifier->setEnabled(false); + sockaddr_l2 clientAddr; + socklen_t clientAddrSize = sizeof clientAddr; + const int clientSocket = accept(serverSocketNotifier->socket(), + reinterpret_cast<sockaddr *>(&clientAddr), &clientAddrSize); + if (clientSocket == -1) { + // Not fatal in itself. The next one might succeed. + qCWarning(QT_BT_BLUEZ) << "accept() failed:" << qt_error_string(errno); + serverSocketNotifier->setEnabled(true); + return; + } + remoteDevice = QBluetoothAddress(convertAddress(clientAddr.l2_bdaddr.b)); + qCDebug(QT_BT_BLUEZ) << "GATT connection from device" << remoteDevice; + if (connectionHandle == 0) + qCWarning(QT_BT_BLUEZ) << "Received client connection, but no connection complete event"; + + closeServerSocket(); + l2cpSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol, this); + connect(l2cpSocket, &QBluetoothSocket::disconnected, + this, &QLowEnergyControllerPrivate::l2cpDisconnected); + connect(l2cpSocket, static_cast<void (QBluetoothSocket::*)(QBluetoothSocket::SocketError)> + (&QBluetoothSocket::error), this, &QLowEnergyControllerPrivate::l2cpErrorChanged); + connect(l2cpSocket, &QIODevice::readyRead, this, &QLowEnergyControllerPrivate::l2cpReadyRead); + l2cpSocket->d_ptr->lowEnergySocketType = addressType == QLowEnergyController::PublicAddress + ? BDADDR_LE_PUBLIC : BDADDR_LE_RANDOM; + l2cpSocket->setSocketDescriptor(clientSocket, QBluetoothServiceInfo::L2capProtocol, + QBluetoothSocket::ConnectedState, QIODevice::ReadWrite | QIODevice::Unbuffered); + restoreClientConfigurations(); + loadSigningDataIfNecessary(); + setState(QLowEnergyController::ConnectedState); +} + +void QLowEnergyControllerPrivate::closeServerSocket() +{ + if (!serverSocketNotifier) + return; + serverSocketNotifier->disconnect(); + close(serverSocketNotifier->socket()); + serverSocketNotifier->deleteLater(); + serverSocketNotifier = nullptr; +} + +bool QLowEnergyControllerPrivate::isBonded() const +{ + // Pairing does not necessarily imply bonding, but we don't know whether the + // bonding flag was set in the original pairing request. + return QBluetoothLocalDevice(localAdapter).pairingStatus(remoteDevice) + != QBluetoothLocalDevice::Unpaired; +} + +QVector<QLowEnergyControllerPrivate::TempClientConfigurationData> QLowEnergyControllerPrivate::gatherClientConfigData() +{ + QVector<TempClientConfigurationData> data; + foreach (const auto &service, localServices) { + for (auto charIt = service->characteristicList.begin(); + charIt != service->characteristicList.end(); ++charIt) { + QLowEnergyServicePrivate::CharData &charData = charIt.value(); + for (auto descIt = charData.descriptorList.begin(); + descIt != charData.descriptorList.end(); ++descIt) { + QLowEnergyServicePrivate::DescData &descData = descIt.value(); + if (descData.uuid == QBluetoothUuid::ClientCharacteristicConfiguration) { + data << TempClientConfigurationData(&descData, charData.valueHandle, + descIt.key()); + break; + } + } + } + } + return data; +} + +void QLowEnergyControllerPrivate::storeClientConfigurations() +{ + if (!isBonded()) { + clientConfigData.remove(remoteDevice.toUInt64()); + return; + } + QVector<ClientConfigurationData> clientConfigs; + const QVector<TempClientConfigurationData> &tempConfigList = gatherClientConfigData(); + foreach (const auto &tempConfigData, tempConfigList) { + Q_ASSERT(tempConfigData.descData->value.count() == 2); + const quint16 value = bt_get_le16(tempConfigData.descData->value.constData()); + if (value != 0) { + clientConfigs << ClientConfigurationData(tempConfigData.charValueHandle, + tempConfigData.configHandle, value); + } + } + clientConfigData.insert(remoteDevice.toUInt64(), clientConfigs); +} + +void QLowEnergyControllerPrivate::restoreClientConfigurations() +{ + const QVector<TempClientConfigurationData> &tempConfigList = gatherClientConfigData(); + const QVector<ClientConfigurationData> &restoredClientConfigs = isBonded() + ? clientConfigData.value(remoteDevice.toUInt64()) : QVector<ClientConfigurationData>(); + QVector<QLowEnergyHandle> notifications; + foreach (const auto &tempConfigData, tempConfigList) { + bool wasRestored = false; + foreach (const auto &restoredData, restoredClientConfigs) { + if (restoredData.charValueHandle == tempConfigData.charValueHandle) { + Q_ASSERT(tempConfigData.descData->value.count() == 2); + putBtData(restoredData.configValue, tempConfigData.descData->value.data()); + wasRestored = true; + if (restoredData.charValueWasUpdated) { + if (isNotificationEnabled(restoredData.configValue)) + notifications << restoredData.charValueHandle; + else if (isIndicationEnabled(restoredData.configValue)) + scheduledIndications << restoredData.charValueHandle; + } + break; + } + } + if (!wasRestored) + tempConfigData.descData->value = QByteArray(2, 0); // Default value. + Q_ASSERT(lastLocalHandle >= tempConfigData.configHandle); + Q_ASSERT(tempConfigData.configHandle > tempConfigData.charValueHandle); + localAttributes[tempConfigData.configHandle].value = tempConfigData.descData->value; + } + + foreach (const QLowEnergyHandle handle, notifications) + sendNotification(handle); + sendNextIndication(); +} + +void QLowEnergyControllerPrivate::loadSigningDataIfNecessary() +{ + const auto signingDataIt = signingData.constFind(remoteDevice.toUInt64()); + if (signingDataIt != signingData.constEnd()) + return; // We are up to date for this device. + const QString settingsFilePath = keySettingsFilePath(); + if (!QFileInfo(settingsFilePath).exists()) { + qCDebug(QT_BT_BLUEZ) << "No settings found for peer device."; + return; + } + QSettings settings(settingsFilePath, QSettings::IniFormat); + settings.beginGroup(QLatin1String("RemoteSignatureKey")); + const QByteArray keyString = settings.value(QLatin1String("Key")).toByteArray(); + if (keyString.isEmpty()) { + qCDebug(QT_BT_BLUEZ) << "No remote signature key found in settings file"; + return; + } + const QByteArray keyData = QByteArray::fromHex(keyString); + if (keyData.count() != int(sizeof(quint128))) { + qCWarning(QT_BT_BLUEZ) << "Remote signature key in settings file has invalid size" + << keyString.count(); + return; + } + qCDebug(QT_BT_BLUEZ) << "CSRK of peer device is" << keyString; + const quint32 counter = settings.value(QLatin1String("Counter"), 0).toUInt(); + quint128 csrk; + using namespace std; + memcpy(csrk.data, keyData.constData(), keyData.count()); + signingData.insert(remoteDevice.toUInt64(), SigningData(csrk, counter - 1)); +} + +void QLowEnergyControllerPrivate::storeSignCounter() +{ + const auto signingDataIt = signingData.constFind(remoteDevice.toUInt64()); + if (signingDataIt == signingData.constEnd()) + return; + const QString settingsFilePath = keySettingsFilePath(); + if (!QFileInfo(settingsFilePath).exists()) + return; + QSettings settings(settingsFilePath, QSettings::IniFormat); + if (!settings.isWritable()) + return; + settings.beginGroup(QLatin1String("RemoteSignatureKey")); + const QString counterKey = QLatin1String("Counter"); + if (!settings.allKeys().contains(counterKey)) + return; + const quint32 counterValue = signingDataIt.value().counter + 1; + if (counterValue == settings.value(counterKey).toUInt()) + return; + settings.setValue(counterKey, counterValue); +} + +QString QLowEnergyControllerPrivate::keySettingsFilePath() const +{ + return QString::fromLatin1("/var/lib/bluetooth/%1/%2/info") + .arg(localAdapter.toString(), remoteDevice.toString()); +} + +static QByteArray uuidToByteArray(const QBluetoothUuid &uuid) +{ + QByteArray ba; + if (uuid.minimumSize() == 2) { + ba.resize(2); + putBtData(uuid.toUInt16(), ba.data()); + } else { + ba.resize(16); + putBtData(uuid.toUInt128(), ba.data()); + } + return ba; +} + +void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &service, + QLowEnergyHandle startHandle) +{ + // Construct generic attribute data for the service with handles as keys. + // Otherwise a number of request handling functions will be awkward to write + // as well as computationally inefficient. + + localAttributes.resize(lastLocalHandle + 1); + Attribute serviceAttribute; + serviceAttribute.handle = startHandle; + serviceAttribute.type = QBluetoothUuid(static_cast<quint16>(service.type())); + serviceAttribute.properties = QLowEnergyCharacteristic::Read; + serviceAttribute.value = uuidToByteArray(service.uuid()); + QLowEnergyHandle currentHandle = startHandle; + foreach (const QLowEnergyService * const service, service.includedServices()) { + Attribute attribute; + attribute.handle = ++currentHandle; + attribute.type = QBluetoothUuid(GATT_INCLUDED_SERVICE); + attribute.properties = QLowEnergyCharacteristic::Read; + const bool includeUuidInValue = service->serviceUuid().minimumSize() == 2; + attribute.value.resize((2 + includeUuidInValue) * sizeof(QLowEnergyHandle)); + char *valueData = attribute.value.data(); + putDataAndIncrement(service->d_ptr->startHandle, valueData); + putDataAndIncrement(service->d_ptr->endHandle, valueData); + if (includeUuidInValue) + putDataAndIncrement(service->serviceUuid(), valueData); + localAttributes[attribute.handle] = attribute; + } + foreach (const QLowEnergyCharacteristicData &cd, service.characteristics()) { + Attribute attribute; + + // Characteristic declaration; + attribute.handle = ++currentHandle; + attribute.groupEndHandle = attribute.handle + 1 + cd.descriptors().count(); + attribute.type = QBluetoothUuid(GATT_CHARACTERISTIC); + attribute.properties = QLowEnergyCharacteristic::Read; + attribute.value.resize(1 + sizeof(QLowEnergyHandle) + cd.uuid().minimumSize()); + char *valueData = attribute.value.data(); + putDataAndIncrement(static_cast<quint8>(cd.properties()), valueData); + putDataAndIncrement(QLowEnergyHandle(currentHandle + 1), valueData); + putDataAndIncrement(cd.uuid(), valueData); + localAttributes[attribute.handle] = attribute; + + // Characteristic value declaration. + attribute.handle = ++currentHandle; + attribute.groupEndHandle = attribute.handle; + attribute.type = cd.uuid(); + attribute.properties = cd.properties(); + attribute.readConstraints = cd.readConstraints(); + attribute.writeConstraints = cd.writeConstraints(); + attribute.value = cd.value(); + attribute.minLength = cd.minimumValueLength(); + attribute.maxLength = cd.maximumValueLength(); + localAttributes[attribute.handle] = attribute; + + foreach (const QLowEnergyDescriptorData &dd, cd.descriptors()) { + attribute.handle = ++currentHandle; + attribute.groupEndHandle = attribute.handle; + attribute.type = dd.uuid(); + attribute.properties = QLowEnergyCharacteristic::PropertyTypes(); + attribute.readConstraints = AttAccessConstraints(); + attribute.writeConstraints = AttAccessConstraints(); + attribute.minLength = 0; + attribute.maxLength = INT_MAX; + + // Spec v4.2, Vol. 3, Part G, 3.3.3.x + if (attribute.type == QBluetoothUuid::CharacteristicExtendedProperties) { + attribute.properties = QLowEnergyCharacteristic::Read; + attribute.minLength = attribute.maxLength = 2; + } else if (attribute.type == QBluetoothUuid::CharacteristicPresentationFormat) { + attribute.properties = QLowEnergyCharacteristic::Read; + attribute.minLength = attribute.maxLength = 7; + } else if (attribute.type == QBluetoothUuid::CharacteristicAggregateFormat) { + attribute.properties = QLowEnergyCharacteristic::Read; + attribute.minLength = 4; + } else if (attribute.type == QBluetoothUuid::ClientCharacteristicConfiguration + || attribute.type == QBluetoothUuid::ServerCharacteristicConfiguration) { + attribute.properties = QLowEnergyCharacteristic::Read + | QLowEnergyCharacteristic::Write + | QLowEnergyCharacteristic::WriteNoResponse + | QLowEnergyCharacteristic::WriteSigned; + attribute.writeConstraints = dd.writeConstraints(); + attribute.minLength = attribute.maxLength = 2; + } else { + if (dd.isReadable()) + attribute.properties |= QLowEnergyCharacteristic::Read; + if (dd.isWritable()) { + attribute.properties |= QLowEnergyCharacteristic::Write; + attribute.properties |= QLowEnergyCharacteristic::WriteNoResponse; + attribute.properties |= QLowEnergyCharacteristic::WriteSigned; + } + attribute.readConstraints = dd.readConstraints(); + attribute.writeConstraints = dd.writeConstraints(); + } + + attribute.value = dd.value(); + if (attribute.value.count() < attribute.minLength + || attribute.value.count() > attribute.maxLength) { + qCWarning(QT_BT_BLUEZ) << "attribute of type" << attribute.type + << "has invalid length of" << attribute.value.count() + << "bytes"; + attribute.value = QByteArray(attribute.minLength, 0); + } + localAttributes[attribute.handle] = attribute; + } + } + serviceAttribute.groupEndHandle = currentHandle; + localAttributes[serviceAttribute.handle] = serviceAttribute; +} + +void QLowEnergyControllerPrivate::ensureUniformAttributes(QVector<Attribute> &attributes, + const std::function<int (const Attribute &)> &getSize) +{ + if (attributes.isEmpty()) + return; + const int firstSize = getSize(attributes.first()); + const auto it = std::find_if(attributes.begin() + 1, attributes.end(), + [firstSize, getSize](const Attribute &attr) { return getSize(attr) != firstSize; }); + if (it != attributes.end()) + attributes.erase(it, attributes.end()); + +} + +void QLowEnergyControllerPrivate::ensureUniformUuidSizes(QVector<Attribute> &attributes) +{ + ensureUniformAttributes(attributes, + [](const Attribute &attr) { return getUuidSize(attr.type); }); +} + +void QLowEnergyControllerPrivate::ensureUniformValueSizes(QVector<Attribute> &attributes) +{ + ensureUniformAttributes(attributes, + [](const Attribute &attr) { return attr.value.count(); }); +} + +QVector<QLowEnergyControllerPrivate::Attribute> QLowEnergyControllerPrivate::getAttributes(QLowEnergyHandle startHandle, + QLowEnergyHandle endHandle, const AttributePredicate &attributePredicate) +{ + QVector<Attribute> results; + if (startHandle > lastLocalHandle) + return results; + if (lastLocalHandle == 0) // We have no services at all. + return results; + Q_ASSERT(startHandle <= endHandle); // Must have been checked before. + const QLowEnergyHandle firstHandle = qMin(startHandle, lastLocalHandle); + const QLowEnergyHandle lastHandle = qMin(endHandle, lastLocalHandle); + for (QLowEnergyHandle i = firstHandle; i <= lastHandle; ++i) { + const Attribute &attr = localAttributes.at(i); + if (attributePredicate(attr)) + results << attr; + } + return results; +} + +int QLowEnergyControllerPrivate::checkPermissions(const Attribute &attr, + QLowEnergyCharacteristic::PropertyType type) +{ + const bool isReadAccess = type == QLowEnergyCharacteristic::Read; + const bool isWriteCommand = type == QLowEnergyCharacteristic::WriteNoResponse; + const bool isWriteAccess = type == QLowEnergyCharacteristic::Write + || type == QLowEnergyCharacteristic::WriteSigned + || isWriteCommand; + Q_ASSERT(isReadAccess || isWriteAccess); + if (!(attr.properties & type)) { + if (isReadAccess) + return ATT_ERROR_READ_NOT_PERM; + + // The spec says: If an attribute requires a signed write, then a non-signed write command + // can also be used if the link is encrypted. + const bool unsignedWriteOk = isWriteCommand + && (attr.properties & QLowEnergyCharacteristic::WriteSigned) + && securityLevel() >= BT_SECURITY_MEDIUM; + if (!unsignedWriteOk) + return ATT_ERROR_WRITE_NOT_PERM; + } + const AttAccessConstraints constraints = isReadAccess + ? attr.readConstraints : attr.writeConstraints; + if (constraints.testFlag(AttAuthorizationRequired)) + return ATT_ERROR_INSUF_AUTHORIZATION; // TODO: emit signal (and offer authorization function)? + if (constraints.testFlag(AttEncryptionRequired) && securityLevel() < BT_SECURITY_MEDIUM) + return ATT_ERROR_INSUF_ENCRYPTION; + if (constraints.testFlag(AttAuthenticationRequired) && securityLevel() < BT_SECURITY_HIGH) + return ATT_ERROR_INSUF_AUTHENTICATION; + if (false) + return ATT_ERROR_INSUF_ENCR_KEY_SIZE; + return 0; +} + +int QLowEnergyControllerPrivate::checkReadPermissions(const Attribute &attr) +{ + return checkPermissions(attr, QLowEnergyCharacteristic::Read); +} + +int QLowEnergyControllerPrivate::checkReadPermissions(QVector<Attribute> &attributes) +{ + if (attributes.isEmpty()) + return 0; + + // The logic prescribed in the spec is as follows: + // 1) If the first in a list of matching attributes has a permissions error, + // then that error is returned via an error response. + // 2) If any other element of that list would cause a permissions error, then all + // attributes from this one on are not part of the result set, but no error is returned. + const int error = checkReadPermissions(attributes.first()); + if (error) + return error; + const auto it = std::find_if(attributes.begin() + 1, attributes.end(), + [this](const Attribute &attr) { return checkReadPermissions(attr) != 0; }); + if (it != attributes.end()) + attributes.erase(it, attributes.end()); + return 0; +} + +bool QLowEnergyControllerPrivate::verifyMac(const QByteArray &message, const quint128 &csrk, + quint32 signCounter, quint64 expectedMac) +{ + if (!cmacVerifier) + cmacVerifier = new LeCmacVerifier; + return cmacVerifier->verify(LeCmacVerifier::createFullMessage(message, signCounter), csrk, + expectedMac); +} + QT_END_NAMESPACE |