diff options
author | Christian Kandeler <christian.kandeler@theqtcompany.com> | 2016-02-03 17:52:55 +0100 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@theqtcompany.com> | 2016-02-17 13:09:35 +0000 |
commit | b1a2de541771c6b45d46cfa6613c10f5e52ece68 (patch) | |
tree | df023e3a73cf2ed627eccf29c479fa12004ac550 | |
parent | eef68dea35960670c1a1a1431d4541e1ed19c781 (diff) | |
download | qtconnectivity-b1a2de541771c6b45d46cfa6613c10f5e52ece68.tar.gz |
Bluetooth LE: Add support for Signed Write command in the central role.
Task-number: QTBUG-41175
Change-Id: I62d74236faf9161681306d952e409e23e0cea24d
Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
17 files changed, 212 insertions, 115 deletions
diff --git a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java index 5fe5d03d..5150a083 100644 --- a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java +++ b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java @@ -835,11 +835,13 @@ public class QtBluetoothLE { newJob.jobType = IoJobType.Write; // writeMode must be in sync with QLowEnergyService::WriteMode - // For now we ignore SignedWriteType as Qt doesn't support it yet. switch (writeMode) { case 1: //WriteWithoutResponse newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE; break; + case 2: //WriteSigned + newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_SIGNED; + break; default: newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT; break; diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index 99c9fe07..604f4de8 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -51,7 +51,7 @@ PRIVATE_HEADERS += \ qlowenergycontroller_p.h \ qlowenergyserviceprivate_p.h \ qleadvertiser_p.h \ - lecmacverifier_p.h + lecmaccalculator_p.h SOURCES += \ qbluetoothaddress.cpp\ @@ -104,7 +104,7 @@ config_bluez:qtHaveModule(dbus) { SOURCES += \ qleadvertiser_bluez.cpp \ qlowenergycontroller_bluez.cpp \ - lecmacverifier.cpp + lecmaccalculator.cpp config_linux_crypto_api:DEFINES += CONFIG_LINUX_CRYPTO_API else:message("Linux crypto API not present, signed writes will not work.") } else { diff --git a/src/bluetooth/bluez/hcimanager.cpp b/src/bluetooth/bluez/hcimanager.cpp index cf2f2a0f..dfb99d5a 100644 --- a/src/bluetooth/bluez/hcimanager.cpp +++ b/src/bluetooth/bluez/hcimanager.cpp @@ -486,8 +486,8 @@ void HciManager::handleHciAclPacket(const quint8 *data, int size) // qCDebug(QT_BT_BLUEZ) << "handle:" << aclData->handle << "PB:" << aclData->pbFlag // << "BC:" << aclData->bcFlag << "data len:" << aclData->dataLen; - // Consider only directed, complete messages from controller to host (i.e. incoming packets). - if (aclData->pbFlag != 2 || aclData->bcFlag != 0) + // Consider only directed, complete messages. + if ((aclData->pbFlag != 0 && aclData->pbFlag != 2) || aclData->bcFlag != 0) return; if (size < int(sizeof(L2CapHeader))) { @@ -516,7 +516,8 @@ void HciManager::handleHciAclPacket(const quint8 *data, int size) } quint128 csrk; memcpy(&csrk, data + 1, sizeof csrk); - emit signatureResolvingKeyReceived(aclData->handle, csrk); + const bool isRemoteKey = aclData->pbFlag == 2; + emit signatureResolvingKeyReceived(aclData->handle, isRemoteKey, csrk); } void HciManager::handleLeMetaEvent(const quint8 *data) diff --git a/src/bluetooth/bluez/hcimanager_p.h b/src/bluetooth/bluez/hcimanager_p.h index eb899c79..3bae92e5 100644 --- a/src/bluetooth/bluez/hcimanager_p.h +++ b/src/bluetooth/bluez/hcimanager_p.h @@ -91,7 +91,7 @@ signals: void commandCompleted(quint16 opCode, quint8 status, const QByteArray &data); void connectionComplete(quint16 handle); void connectionUpdate(quint16 handle, const QLowEnergyConnectionParameters ¶meters); - void signatureResolvingKeyReceived(quint16 connHandle, const quint128 &csrk); + void signatureResolvingKeyReceived(quint16 connHandle, bool remoteKey, const quint128 &csrk); private slots: void _q_readNotify(); diff --git a/src/bluetooth/lecmacverifier.cpp b/src/bluetooth/lecmaccalculator.cpp index 23db951c..1cda9576 100644 --- a/src/bluetooth/lecmacverifier.cpp +++ b/src/bluetooth/lecmaccalculator.cpp @@ -36,7 +36,7 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#include "lecmacverifier_p.h" +#include "lecmaccalculator_p.h" #include "bluez/bluez_data_p.h" @@ -57,7 +57,7 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) -LeCmacVerifier::LeCmacVerifier() +LeCmacCalculator::LeCmacCalculator() { #ifdef CONFIG_LINUX_CRYPTO_API m_baseSocket = socket(AF_ALG, SOCK_SEQPACKET, 0); @@ -81,13 +81,13 @@ LeCmacVerifier::LeCmacVerifier() #endif } -LeCmacVerifier::~LeCmacVerifier() +LeCmacCalculator::~LeCmacCalculator() { if (m_baseSocket != -1) close(m_baseSocket); } -QByteArray LeCmacVerifier::createFullMessage(const QByteArray &message, quint32 signCounter) +QByteArray LeCmacCalculator::createFullMessage(const QByteArray &message, quint32 signCounter) { // Spec v4.2, Vol 3, Part H, 2.4.5 QByteArray fullMessage = message; @@ -96,8 +96,7 @@ QByteArray LeCmacVerifier::createFullMessage(const QByteArray &message, quint32 return fullMessage; } -bool LeCmacVerifier::verify(const QByteArray &message, const quint128 &csrk, - quint64 expectedMac) const +quint64 LeCmacCalculator::calculateMac(const QByteArray &message, const quint128 &csrk) const { #ifdef CONFIG_LINUX_CRYPTO_API if (m_baseSocket == -1) @@ -108,7 +107,7 @@ bool LeCmacVerifier::verify(const QByteArray &message, const quint128 &csrk, sizeof csrkMsb).toHex(); if (setsockopt(m_baseSocket, 279 /* SOL_ALG */, ALG_SET_KEY, csrkMsb.data, sizeof csrkMsb) == -1) { qCWarning(QT_BT_BLUEZ) << "setsockopt() failed for crypto socket:" << strerror(errno); - return false; + return 0; } class SocketWrapper @@ -127,7 +126,7 @@ bool LeCmacVerifier::verify(const QByteArray &message, const quint128 &csrk, SocketWrapper cryptoSocket(accept(m_baseSocket, nullptr, 0)); if (cryptoSocket.value() == -1) { qCWarning(QT_BT_BLUEZ) << "accept() failed for crypto socket:" << strerror(errno); - return false; + return 0; } QByteArray messageSwapped(message.count(), Qt::Uninitialized); @@ -139,7 +138,7 @@ bool LeCmacVerifier::verify(const QByteArray &message, const quint128 &csrk, messageSwapped.count() - totalBytesWritten); if (bytesWritten == -1) { qCWarning(QT_BT_BLUEZ) << "writing to crypto socket failed:" << strerror(errno); - return false; + return 0; } totalBytesWritten += bytesWritten; } while (totalBytesWritten < messageSwapped.count()); @@ -151,14 +150,27 @@ bool LeCmacVerifier::verify(const QByteArray &message, const quint128 &csrk, sizeof mac - totalBytesRead); if (bytesRead == -1) { qCWarning(QT_BT_BLUEZ) << "reading from crypto socket failed:" << strerror(errno); - return false; + return 0; } totalBytesRead += bytesRead; } while (totalBytesRead < qint64(sizeof mac)); - mac = qFromBigEndian(mac); - if (mac != expectedMac) { - qCWarning(QT_BT_BLUEZ) << hex << "signature verification failed: calculated mac:" << mac - << "expected mac:" << expectedMac; + return qFromBigEndian(mac); +#else // CONFIG_LINUX_CRYPTO_API + Q_UNUSED(message); + Q_UNUSED(csrk); + qCWarning(QT_BT_BLUEZ) << "CMAC calculation failed due to missing Linux crypto API."; + return 0; +#endif +} + +bool LeCmacCalculator::verify(const QByteArray &message, const quint128 &csrk, + quint64 expectedMac) const +{ +#ifdef CONFIG_LINUX_CRYPTO_API + const quint64 actualMac = calculateMac(message, csrk); + if (actualMac != expectedMac) { + qCWarning(QT_BT_BLUEZ) << hex << "signature verification failed: calculated mac:" + << actualMac << "expected mac:" << expectedMac; return false; } return true; diff --git a/src/bluetooth/lecmacverifier_p.h b/src/bluetooth/lecmaccalculator_p.h index e09b6013..1777f637 100644 --- a/src/bluetooth/lecmacverifier_p.h +++ b/src/bluetooth/lecmaccalculator_p.h @@ -36,8 +36,8 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#ifndef LECMACVERIFIER_H -#define LECMACVERIFIER_H +#ifndef LECMACCALCULATOR_H +#define LECMACCALCULATOR_H // // W A R N I N G @@ -56,14 +56,17 @@ QT_BEGIN_NAMESPACE struct quint128; -class Q_AUTOTEST_EXPORT LeCmacVerifier +class Q_AUTOTEST_EXPORT LeCmacCalculator { public: - LeCmacVerifier(); - ~LeCmacVerifier(); + LeCmacCalculator(); + ~LeCmacCalculator(); static QByteArray createFullMessage(const QByteArray &message, quint32 signCounter); + quint64 calculateMac(const QByteArray &message, const quint128 &csrk) const; + + // Convenience function. bool verify(const QByteArray &message, const quint128 &csrk, quint64 expectedMac) const; private: diff --git a/src/bluetooth/qlowenergycontroller_android.cpp b/src/bluetooth/qlowenergycontroller_android.cpp index f740abc7..cd6603a3 100644 --- a/src/bluetooth/qlowenergycontroller_android.cpp +++ b/src/bluetooth/qlowenergycontroller_android.cpp @@ -178,7 +178,7 @@ void QLowEnergyControllerPrivate::writeCharacteristic( const QSharedPointer<QLowEnergyServicePrivate> service, const QLowEnergyHandle charHandle, const QByteArray &newValue, - bool writeWithResponse) + QLowEnergyService::WriteMode mode) { //TODO don't ignore WriteWithResponse, right now we assume responses Q_ASSERT(!service.isNull()); @@ -196,10 +196,10 @@ void QLowEnergyControllerPrivate::writeCharacteristic( if (hub) { qCDebug(QT_BT_ANDROID) << "Write characteristic with handle " << charHandle << newValue.toHex() << "(service:" << service->uuid - << ", writeWithResponse:" << writeWithResponse << ")"; + << ", writeWithResponse:" << (mode == QLowEnergyService::WriteWithResponse) + << ", signed:" << (mode == QLowEnergyService::WriteSigned) << ")"; result = hub->javaObject().callMethod<jboolean>("writeCharacteristic", "(I[BI)Z", - charHandle, payload, - writeWithResponse ? QLowEnergyService::WriteWithResponse : QLowEnergyService::WriteWithoutResponse); + charHandle, payload, mode); } if (env->ExceptionOccurred()) { diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index 729847f9..adac55fc 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -38,7 +38,7 @@ ** ****************************************************************************/ -#include "lecmacverifier_p.h" +#include "lecmaccalculator_p.h" #include "qlowenergycontroller_p.h" #include "qbluetoothsocket_p.h" #include "qleadvertiser_p.h" @@ -287,21 +287,25 @@ void QLowEnergyControllerPrivate::init() } ); 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)); + [this](quint16 handle, bool remoteKey, const quint128 &csrk) { + if (handle != connectionHandle) + return; + if ((remoteKey && role == QLowEnergyController::CentralRole) + || (!remoteKey && role == QLowEnergyController::PeripheralRole)) { + return; } - } + 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; + delete cmacCalculator; } class ServerSocket @@ -456,6 +460,7 @@ void QLowEnergyControllerPrivate::connectToDevice() // Unbuffered mode required to separate each GATT packet l2cpSocket->connectToService(remoteDevice, ATTRIBUTE_CHANNEL_ID, QIODevice::ReadWrite | QIODevice::Unbuffered); + loadSigningDataIfNecessary(LocalSigningKey); } void QLowEnergyControllerPrivate::l2cpConnected() @@ -1779,7 +1784,7 @@ void QLowEnergyControllerPrivate::writeCharacteristic( const QSharedPointer<QLowEnergyServicePrivate> service, const QLowEnergyHandle charHandle, const QByteArray &newValue, - bool writeWithResponse) + QLowEnergyService::WriteMode mode) { Q_ASSERT(!service.isNull()); @@ -1787,12 +1792,10 @@ void QLowEnergyControllerPrivate::writeCharacteristic( return; QLowEnergyServicePrivate::CharData &charData = service->characteristicList[charHandle]; - if (role == QLowEnergyController::PeripheralRole) { + if (role == QLowEnergyController::PeripheralRole) writeCharacteristicForPeripheral(charData, newValue); - } else { - writeCharacteristicForCentral(charHandle, charData.valueHandle, newValue, - writeWithResponse); - } + else + writeCharacteristicForCentral(service, charHandle, charData.valueHandle, newValue, mode); } void QLowEnergyControllerPrivate::writeDescriptor( @@ -2356,48 +2359,72 @@ void QLowEnergyControllerPrivate::writeCharacteristicForPeripheral( } } -void QLowEnergyControllerPrivate::writeCharacteristicForCentral( +void QLowEnergyControllerPrivate::writeCharacteristicForCentral(const QSharedPointer<QLowEnergyServicePrivate> &service, 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) { + QLowEnergyService::WriteMode mode) +{ + QByteArray packet(WRITE_REQUEST_HEADER_SIZE + newValue.count(), Qt::Uninitialized); + putBtData(valueHandle, packet.data() + 1); + memcpy(packet.data() + 3, newValue.constData(), newValue.count()); + bool writeWithResponse = false; + switch (mode) { + case QLowEnergyService::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 + // write value fits into single package + packet[0] = ATT_OP_WRITE_REQUEST; + writeWithResponse = true; + break; + case QLowEnergyService::WriteWithoutResponse: packet[0] = ATT_OP_WRITE_COMMAND; + break; + case QLowEnergyService::WriteSigned: + packet[0] = ATT_OP_SIGNED_WRITE_COMMAND; + if (!isBonded()) { + qCWarning(QT_BT_BLUEZ) << "signed write not possible: requires bond between devices"; + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } + if (securityLevel() >= BT_SECURITY_MEDIUM) { + qCWarning(QT_BT_BLUEZ) << "signed write not possible: not allowed on encrypted link"; + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } + const auto signingDataIt = signingData.find(remoteDevice.toUInt64()); + if (signingDataIt == signingData.end()) { + qCWarning(QT_BT_BLUEZ) << "signed write not possible: no signature key found"; + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } + ++signingDataIt.value().counter; + packet = LeCmacCalculator::createFullMessage(packet, signingDataIt.value().counter); + const quint64 mac = LeCmacCalculator().calculateMac(packet, signingDataIt.value().key); + packet.resize(packet.count() + sizeof mac); + putBtData(mac, packet.data() + packet.count() - sizeof mac); + storeSignCounter(LocalSigningKey); + break; } - 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 << ")"; + << "(size:" << packet.count() << "with response:" + << (mode == QLowEnergyService::WriteWithResponse) + << "signed:" << (mode == QLowEnergyService::WriteSigned) << ")"; // Advantage of write without response is the quick turnaround. - // It can be send at any time and does not produce responses. + // It can be sent at any time and does not produce responses. // Therefore we will not put them into the openRequest queue at all. if (!writeWithResponse) { - sendPacket(data); + sendPacket(packet); return; } Request request; - request.payload = data; + request.payload = packet; request.command = ATT_OP_WRITE_REQUEST; request.reference = charHandle; request.reference2 = newValue; @@ -2516,7 +2543,7 @@ void QLowEnergyControllerPrivate::handleWriteRequestOrCommand(const QByteArray & } signingDataIt.value().counter = signCounter; - storeSignCounter(); + storeSignCounter(RemoteSigningKey); valueLength = packet.count() - 15; } else { valueLength = packet.count() - 3; @@ -2730,7 +2757,7 @@ void QLowEnergyControllerPrivate::handleConnectionRequest() l2cpSocket->setSocketDescriptor(clientSocket, QBluetoothServiceInfo::L2capProtocol, QBluetoothSocket::ConnectedState, QIODevice::ReadWrite | QIODevice::Unbuffered); restoreClientConfigurations(); - loadSigningDataIfNecessary(); + loadSigningDataIfNecessary(RemoteSigningKey); setState(QLowEnergyController::ConnectedState); } @@ -2826,7 +2853,7 @@ void QLowEnergyControllerPrivate::restoreClientConfigurations() sendNextIndication(); } -void QLowEnergyControllerPrivate::loadSigningDataIfNecessary() +void QLowEnergyControllerPrivate::loadSigningDataIfNecessary(SigningKeyType keyType) { const auto signingDataIt = signingData.constFind(remoteDevice.toUInt64()); if (signingDataIt != signingData.constEnd()) @@ -2837,15 +2864,16 @@ void QLowEnergyControllerPrivate::loadSigningDataIfNecessary() return; } QSettings settings(settingsFilePath, QSettings::IniFormat); - settings.beginGroup(QLatin1String("RemoteSignatureKey")); + const QString group = signingKeySettingsGroup(keyType); + settings.beginGroup(group); const QByteArray keyString = settings.value(QLatin1String("Key")).toByteArray(); if (keyString.isEmpty()) { - qCDebug(QT_BT_BLUEZ) << "No remote signature key found in settings file"; + qCDebug(QT_BT_BLUEZ) << "Group" << group << "not 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" + qCWarning(QT_BT_BLUEZ) << "Signing key in settings file has invalid size" << keyString.count(); return; } @@ -2857,7 +2885,7 @@ void QLowEnergyControllerPrivate::loadSigningDataIfNecessary() signingData.insert(remoteDevice.toUInt64(), SigningData(csrk, counter - 1)); } -void QLowEnergyControllerPrivate::storeSignCounter() +void QLowEnergyControllerPrivate::storeSignCounter(SigningKeyType keyType) const { const auto signingDataIt = signingData.constFind(remoteDevice.toUInt64()); if (signingDataIt == signingData.constEnd()) @@ -2868,7 +2896,7 @@ void QLowEnergyControllerPrivate::storeSignCounter() QSettings settings(settingsFilePath, QSettings::IniFormat); if (!settings.isWritable()) return; - settings.beginGroup(QLatin1String("RemoteSignatureKey")); + settings.beginGroup(signingKeySettingsGroup(keyType)); const QString counterKey = QLatin1String("Counter"); if (!settings.allKeys().contains(counterKey)) return; @@ -2878,6 +2906,11 @@ void QLowEnergyControllerPrivate::storeSignCounter() settings.setValue(counterKey, counterValue); } +QString QLowEnergyControllerPrivate::signingKeySettingsGroup(SigningKeyType keyType) const +{ + return QLatin1String(keyType == LocalSigningKey ? "LocalSignatureKey" : "RemoteSignatureKey"); +} + QString QLowEnergyControllerPrivate::keySettingsFilePath() const { return QString::fromLatin1("/var/lib/bluetooth/%1/%2/info") @@ -3113,9 +3146,9 @@ int QLowEnergyControllerPrivate::checkReadPermissions(QVector<Attribute> &attrib 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, + if (!cmacCalculator) + cmacCalculator = new LeCmacCalculator; + return cmacCalculator->verify(LeCmacCalculator::createFullMessage(message, signCounter), csrk, expectedMac); } diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm index 62958c11..47f65965 100644 --- a/src/bluetooth/qlowenergycontroller_osx.mm +++ b/src/bluetooth/qlowenergycontroller_osx.mm @@ -664,7 +664,7 @@ void QLowEnergyControllerPrivateOSX::readCharacteristic(QSharedPointer<QLowEnerg void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service, QLowEnergyHandle charHandle, const QByteArray &newValue, - bool writeWithResponse) + QLowEnergyService::WriteMode mode) { Q_ASSERT_X(!service.isNull(), Q_FUNC_INFO, "invalid service (null)"); Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid controller"); @@ -697,7 +697,7 @@ void QLowEnergyControllerPrivateOSX::writeCharacteristic(QSharedPointer<QLowEner [manager write:newValueCopy charHandle:charHandle onService:serviceUuid - withResponse:writeWithResponse]; + withResponse:mode == QLowEnergyService::WriteWithResponse]; }); } diff --git a/src/bluetooth/qlowenergycontroller_osx_p.h b/src/bluetooth/qlowenergycontroller_osx_p.h index 6dc27aac..853e1f9b 100644 --- a/src/bluetooth/qlowenergycontroller_osx_p.h +++ b/src/bluetooth/qlowenergycontroller_osx_p.h @@ -124,7 +124,7 @@ private: QLowEnergyHandle charHandle); void writeCharacteristic(QSharedPointer<QLowEnergyServicePrivate> service, QLowEnergyHandle charHandle, const QByteArray &newValue, - bool writeWithResponse); + QLowEnergyService::WriteMode mode); quint16 updateValueOfCharacteristic(QLowEnergyHandle charHandle, const QByteArray &value, diff --git a/src/bluetooth/qlowenergycontroller_p.cpp b/src/bluetooth/qlowenergycontroller_p.cpp index b4bd9a0f..6d42984e 100644 --- a/src/bluetooth/qlowenergycontroller_p.cpp +++ b/src/bluetooth/qlowenergycontroller_p.cpp @@ -102,7 +102,7 @@ void QLowEnergyControllerPrivate::readDescriptor(const QSharedPointer<QLowEnergy void QLowEnergyControllerPrivate::writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> /*service*/, const QLowEnergyHandle /*charHandle*/, const QByteArray &/*newValue*/, - bool /*writeWithResponse*/) + QLowEnergyService::WriteMode /*writeMode*/) { } diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index 6e231dd1..2256025c 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -92,7 +92,7 @@ class QLowEnergyServiceData; #if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE) class HciManager; -class LeCmacVerifier; +class LeCmacCalculator; class QSocketNotifier; #elif defined(QT_ANDROID_BLUETOOTH) class LowEnergyNotificationHub; @@ -159,7 +159,7 @@ public: // write data void writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service, const QLowEnergyHandle charHandle, - const QByteArray &newValue, bool writeWithResponse = true); + const QByteArray &newValue, QLowEnergyService::WriteMode mode); void writeDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle, @@ -260,7 +260,7 @@ private: quint32 counter = quint32(-1); }; QHash<quint64, SigningData> signingData; - LeCmacVerifier *cmacVerifier = nullptr; + LeCmacCalculator *cmacCalculator = nullptr; bool requestPending; quint16 mtuSize; @@ -279,8 +279,11 @@ private: QVector<TempClientConfigurationData> gatherClientConfigData(); void storeClientConfigurations(); void restoreClientConfigurations(); - void loadSigningDataIfNecessary(); - void storeSignCounter(); + + enum SigningKeyType { LocalSigningKey, RemoteSigningKey }; + void loadSigningDataIfNecessary(SigningKeyType keyType); + void storeSignCounter(SigningKeyType keyType) const; + QString signingKeySettingsGroup(SigningKeyType keyType) const; QString keySettingsFilePath() const; void sendPacket(const QByteArray &packet); @@ -368,11 +371,11 @@ private: void writeCharacteristicForPeripheral( QLowEnergyServicePrivate::CharData &charData, const QByteArray &newValue); - void writeCharacteristicForCentral( + void writeCharacteristicForCentral(const QSharedPointer<QLowEnergyServicePrivate> &service, QLowEnergyHandle charHandle, QLowEnergyHandle valueHandle, const QByteArray &newValue, - bool writeWithResponse); + QLowEnergyService::WriteMode mode); void writeDescriptorForPeripheral( const QSharedPointer<QLowEnergyServicePrivate> &service, diff --git a/src/bluetooth/qlowenergyservice.cpp b/src/bluetooth/qlowenergyservice.cpp index c9f21c70..1e5c363d 100644 --- a/src/bluetooth/qlowenergyservice.cpp +++ b/src/bluetooth/qlowenergyservice.cpp @@ -255,6 +255,18 @@ QT_BEGIN_NAMESPACE write mode. Its adavantage is a quicker write operation as it may happen in between other device interactions. + + \value WriteSigned If a characteristic is written using this mode, the remote peripheral + shall not send a write confirmation. The operation's success + cannot be determined and the payload must not be longer than 8 bytes. + A bond must exist between the two devices and the link must not be + encrypted. + A characteristic must have set the + \l QLowEnergyCharacteristic::WriteSigned property to support this + write mode. + This value was introduced in Qt 5.7 and is currently only + supported on Android and on Linux with BlueZ 5 and a kernel version + 3.7 or newer. */ /*! @@ -696,20 +708,10 @@ void QLowEnergyService::writeCharacteristic( } // don't write if properties don't permit it - if (mode == WriteWithResponse) - { - d->controller->writeCharacteristic(characteristic.d_ptr, + d->controller->writeCharacteristic(characteristic.d_ptr, characteristic.attributeHandle(), newValue, - true); - } else if (mode == WriteWithoutResponse) { - d->controller->writeCharacteristic(characteristic.d_ptr, - characteristic.attributeHandle(), - newValue, - false); - } else { - d->setError(QLowEnergyService::OperationError); - } + mode); } /*! diff --git a/src/bluetooth/qlowenergyservice.h b/src/bluetooth/qlowenergyservice.h index 496c9dde..0449f4c9 100644 --- a/src/bluetooth/qlowenergyservice.h +++ b/src/bluetooth/qlowenergyservice.h @@ -82,7 +82,8 @@ public: enum WriteMode { WriteWithResponse = 0, - WriteWithoutResponse + WriteWithoutResponse, + WriteSigned }; Q_ENUM(WriteMode) diff --git a/src/bluetooth/qlowenergyservice_osx.mm b/src/bluetooth/qlowenergyservice_osx.mm index 5a78b814..97d64040 100644 --- a/src/bluetooth/qlowenergyservice_osx.mm +++ b/src/bluetooth/qlowenergyservice_osx.mm @@ -213,13 +213,7 @@ void QLowEnergyService::writeCharacteristic(const QLowEnergyCharacteristic &ch, return; } - // Don't write if properties don't permit it - if (mode == WriteWithResponse) - controller->writeCharacteristic(ch.d_ptr, ch.attributeHandle(), newValue, true); - else if (mode == WriteWithoutResponse) - controller->writeCharacteristic(ch.d_ptr, ch.attributeHandle(), newValue, false); - else - d_ptr->setError(QLowEnergyService::OperationError); + controller->writeCharacteristic(ch.d_ptr, ch.attributeHandle(), newValue, mode); } bool QLowEnergyService::contains(const QLowEnergyDescriptor &descriptor) const diff --git a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp index b7c95816..6fa9b023 100644 --- a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp +++ b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp @@ -121,7 +121,7 @@ void addCustomService() serviceData.addCharacteristic(charData); charData.setUuid(QBluetoothUuid(quint16(0x5001))); - charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned); + charData.setProperties(QLowEnergyCharacteristic::Read); charData.setReadConstraints(QBluetooth::AttAuthorizationRequired); // To test read failure. serviceData.addCharacteristic(charData); charData.setValue("something"); @@ -138,6 +138,12 @@ void addCustomService() charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify); serviceData.addCharacteristic(charData); + charData.setUuid(QBluetoothUuid(quint16(0x5004))); + charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned); + charData.setDescriptors(QList<QLowEnergyDescriptorData>()); + charData.setValue("initial"); + serviceData.addCharacteristic(charData); + addService(serviceData); } diff --git a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp index f487db00..ac2060e9 100644 --- a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp +++ b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp @@ -43,7 +43,7 @@ #include <QtTest/QtTest> #ifdef Q_OS_LINUX -#include <QtBluetooth/private/lecmacverifier_p.h> +#include <QtBluetooth/private/lecmaccalculator_p.h> #endif #include <algorithm> @@ -167,7 +167,7 @@ void TestQLowEnergyControllerGattServer::cmacVerifier() }; QFETCH(QByteArray, message); QFETCH(quint64, expectedMac); - const bool success = LeCmacVerifier().verify(message, csrk, expectedMac); + const bool success = LeCmacCalculator().verify(message, csrk, expectedMac); QVERIFY(success); #else // CONFIG_LINUX_CRYPTO_API QSKIP("CMAC verification test only applicable for developer builds on Linux " @@ -333,7 +333,7 @@ void TestQLowEnergyControllerGattServer::serverCommunication() QVERIFY(spy->wait(5000)); } QCOMPARE(customService->includedServices().count(), 0); - QCOMPARE(customService->characteristics().count(), 4); + QCOMPARE(customService->characteristics().count(), 5); QLowEnergyCharacteristic customChar = customService->characteristic(QBluetoothUuid(quint16(0x5000))); QVERIFY(customChar.isValid()); @@ -366,12 +366,43 @@ void TestQLowEnergyControllerGattServer::serverCommunication() = customChar4.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); QVERIFY(cc4ClientConfig.isValid()); + QLowEnergyCharacteristic customChar5 + = customService->characteristic(QBluetoothUuid(quint16(0x5004))); + QVERIFY(customChar5.isValid()); + QCOMPARE(customChar5.properties(), + QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned); + QCOMPARE(customChar5.descriptors().count(), 0); + QCOMPARE(customChar5.value(), QByteArray("initial")); + customService->writeCharacteristic(customChar, "whatever"); spy.reset(new QSignalSpy(customService.data(), static_cast<void (QLowEnergyService::*) (QLowEnergyService::ServiceError)>(&QLowEnergyService::error))); QVERIFY(spy->wait(3000)); QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError); + const bool isBonded = QBluetoothLocalDevice().pairingStatus(m_serverAddress) + != QBluetoothLocalDevice::Unpaired; + + customService->writeCharacteristic(customChar5, "1", QLowEnergyService::WriteSigned); + if (isBonded) { + // Signed write is done twice to test the sign counter stuff. + // Note: We assume here that the link is not encrypted, as this information is not exported. + customService->readCharacteristic(customChar5); + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead)); + QVERIFY(spy->wait(3000)); + QCOMPARE(customChar5.value(), QByteArray("1")); + customService->writeCharacteristic(customChar5, "2", QLowEnergyService::WriteSigned); + customService->readCharacteristic(customChar5); + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead)); + QVERIFY(spy->wait(3000)); + QCOMPARE(customChar5.value(), QByteArray("2")); + } else { + spy.reset(new QSignalSpy(customService.data(), static_cast<void (QLowEnergyService::*) + (QLowEnergyService::ServiceError)>(&QLowEnergyService::error))); + QVERIFY(spy->wait(3000)); + QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError); + } + QByteArray indicateValue(2, 0); qToLittleEndian<quint16>(2, reinterpret_cast<uchar *>(indicateValue.data())); customService->writeDescriptor(cc3ClientConfig, indicateValue); @@ -396,8 +427,6 @@ void TestQLowEnergyControllerGattServer::serverCommunication() spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::connectionUpdated)); QVERIFY(spy->wait(5000)); - const bool isBonded = QBluetoothLocalDevice().pairingStatus(m_serverAddress) - != QBluetoothLocalDevice::Unpaired; m_leController->disconnectFromDevice(); if (m_leController->state() != QLowEnergyController::UnconnectedState) { @@ -438,6 +467,17 @@ void TestQLowEnergyControllerGattServer::serverCommunication() if (isBonded) { QCOMPARE(cc3ClientConfig.value(), indicateValue); QCOMPARE(cc4ClientConfig.value(), notifyValue); + + // Do a third signed write to test sign counter persistence. + customChar5 = customService->characteristic(QBluetoothUuid(quint16(0x5004))); + QVERIFY(customChar5.isValid()); + QCOMPARE(customChar5.value(), QByteArray("2")); + customService->writeCharacteristic(customChar5, "3", QLowEnergyService::WriteSigned); + customService->readCharacteristic(customChar5); + spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead)); + QVERIFY(spy->wait(3000)); + QCOMPARE(customChar5.value(), QByteArray("3")); + } else { QCOMPARE(cc3ClientConfig.value(), QByteArray(2, 0)); QCOMPARE(cc4ClientConfig.value(), QByteArray(2, 0)); |