diff options
author | Alex Blasche <alexander.blasche@digia.com> | 2014-09-10 12:10:25 +0200 |
---|---|---|
committer | Alex Blasche <alexander.blasche@digia.com> | 2014-09-16 08:59:10 +0200 |
commit | 27abf796987c137d2660efd8e73b0e0fc8ffad0c (patch) | |
tree | 0bc4d4715b83f11cf4845e38e3074a6e039f9ffd | |
parent | 3d87d02970237c9e7a8ef8d921702bcc755130c0 (diff) |
Upgrade ATT reads requests to encrypted links when indicated
This is triggered if the GATT
server complains about missing authorization/encryption
when reading an attribute.
The same mechanism has to be applied to all remaining
read and write types
Change-Id: Ia8330951ffdc61afb98424557bbeffe444e9a812
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
-rw-r--r-- | src/bluetooth/bluez/hcimanager.cpp | 1 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_bluez.cpp | 70 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_p.h | 9 | ||||
-rw-r--r-- | tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp | 66 |
4 files changed, 138 insertions, 8 deletions
diff --git a/src/bluetooth/bluez/hcimanager.cpp b/src/bluetooth/bluez/hcimanager.cpp index be8ec6cc..449f0825 100644 --- a/src/bluetooth/bluez/hcimanager.cpp +++ b/src/bluetooth/bluez/hcimanager.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtBluetooth module of the Qt Toolkit. diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index 1659ec42..71a0281a 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -35,6 +35,7 @@ #include "qlowenergycontroller_p.h" #include "qbluetoothsocket_p.h" #include "bluez/bluez_data_p.h" +#include "bluez/hcimanager_p.h" #include <QtCore/QLoggingCategory> #include <QtBluetooth/QBluetoothSocket> @@ -190,9 +191,20 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() state(QLowEnergyController::UnconnectedState), error(QLowEnergyController::NoError), l2cpSocket(0), requestPending(false), - mtuSize(ATT_DEFAULT_LE_MTU) + mtuSize(ATT_DEFAULT_LE_MTU), + securityLevelValue(-1), + encryptionChangePending(false), + hciManager(0) { qRegisterMetaType<QList<QLowEnergyHandle> >(); + + hciManager = new HciManager(localAdapter, this); + if (!hciManager->isValid()) + return; + + hciManager->monitorEvent(HciManager::EncryptChangeEvent); + connect(hciManager, SIGNAL(encryptionChangedEvent(QBluetoothAddress,bool)), + this, SLOT(encryptionChangedEvent(QBluetoothAddress,bool))); } QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() @@ -246,7 +258,7 @@ void QLowEnergyControllerPrivate::l2cpConnected() { Q_Q(QLowEnergyController); - securityLevel(); + securityLevelValue = securityLevel(); exchangeMTU(); setState(QLowEnergyController::ConnectedState); @@ -263,6 +275,7 @@ void QLowEnergyControllerPrivate::l2cpDisconnected() { Q_Q(QLowEnergyController); + securityLevelValue = -1; setState(QLowEnergyController::UnconnectedState); emit q->disconnected(); } @@ -340,6 +353,26 @@ void QLowEnergyControllerPrivate::l2cpReadyRead() sendNextPendingRequest(); } +void QLowEnergyControllerPrivate::encryptionChangedEvent( + const QBluetoothAddress &address, bool wasSuccess) +{ + if (remoteDevice != address) + return; + + encryptionChangePending = false; + securityLevelValue = securityLevel(); + + if (!wasSuccess) { + // We could not increase the security of the link + // The next request was requeued due to security error + // skip it to avoid endless loop of security negotiations + Q_ASSERT(!openRequests.isEmpty()); + openRequests.removeFirst(); + } + + sendNextPendingRequest(); +} + void QLowEnergyControllerPrivate::sendCommand(const QByteArray &packet) { qint64 result = l2cpSocket->write(packet.constData(), @@ -354,7 +387,7 @@ void QLowEnergyControllerPrivate::sendCommand(const QByteArray &packet) void QLowEnergyControllerPrivate::sendNextPendingRequest() { - if (openRequests.isEmpty() || requestPending) + if (openRequests.isEmpty() || requestPending || encryptionChangePending) return; const Request &request = openRequests.head(); @@ -600,6 +633,26 @@ void QLowEnergyControllerPrivate::processReply( const QLowEnergyHandle charHandle = (handleData & 0xffff); const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff); + if (isErrorResponse) { + switch (response.constData()[4]) { + case ATT_ERROR_INSUF_AUTHORIZATION: + case ATT_ERROR_INSUF_ENCRYPTION: + case ATT_ERROR_INSUF_AUTHENTICATION: + if (!hciManager->isValid()) + break; + if (!hciManager->monitorEvent(HciManager::EncryptChangeEvent)) + break; + if (securityLevelValue != BT_SECURITY_HIGH) { + qCDebug(QT_BT_BLUEZ) << "Requesting encrypted link"; + if (setSecurityLevel(BT_SECURITY_HIGH)) + encryptionChangePending = true; + } + break; + default: + break; + } + } + // we ignore error response if (!isErrorResponse) { if (!descriptorHandle) @@ -614,6 +667,13 @@ void QLowEnergyControllerPrivate::processReply( request.reference2.toBool()); break; } + } else { + if (encryptionChangePending) { + // Just requested a security level change. + // Retry the same command again once the change has happened + openRequests.prepend(request); + break; + } } if (request.reference2.toBool()) { @@ -1151,8 +1211,6 @@ void QLowEnergyControllerPrivate::exchangeMTU() int QLowEnergyControllerPrivate::securityLevel() const { - qCDebug(QT_BT_BLUEZ) << "Getting security level"; - int socket = l2cpSocket->socketDescriptor(); if (socket < 0) { qCWarning(QT_BT_BLUEZ) << "Invalid l2cp socket, aborting getting of sec level"; @@ -1192,8 +1250,6 @@ int QLowEnergyControllerPrivate::securityLevel() const bool QLowEnergyControllerPrivate::setSecurityLevel(int level) { - qCDebug(QT_BT_BLUEZ) << "Setting security level:" << level; - if (level > BT_SECURITY_HIGH || level < BT_SECURITY_LOW) return false; diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index 219d2f0f..43d96f0b 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -46,6 +46,10 @@ QT_BEGIN_NAMESPACE +#if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE) +class HciManager; +#endif + typedef QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate> > ServiceDataMap; class QLowEnergyControllerPrivate : public QObject @@ -122,6 +126,10 @@ private: QQueue<Request> openRequests; bool requestPending; quint16 mtuSize; + int securityLevelValue; + bool encryptionChangePending; + + HciManager *hciManager; void sendCommand(const QByteArray &packet); void sendNextPendingRequest(); @@ -156,6 +164,7 @@ private slots: void l2cpDisconnected(); void l2cpErrorChanged(QBluetoothSocket::SocketError); void l2cpReadyRead(); + void encryptionChangedEvent(const QBluetoothAddress&, bool); #endif private: QLowEnergyController *q_ptr; diff --git a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp index b5e9dd31..4af96f3c 100644 --- a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp +++ b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp @@ -69,7 +69,7 @@ private slots: void tst_writeCharacteristic(); void tst_writeCharacteristicNoResponse(); void tst_writeDescriptor(); - + void tst_encryption(); private: void verifyServiceProperties(const QLowEnergyService *info); @@ -1844,6 +1844,70 @@ void tst_QLowEnergyController::tst_writeDescriptor() } /* + * Tests encrypted read/write. + * This test is semi manual as the test device environment is very specific. + * Adjust the various uuids and addresses at the top to cater for the current + * situation. By default this test is skipped. + */ +void tst_QLowEnergyController::tst_encryption() +{ + QSKIP("Skipping encryption"); + + //Adjust the uuids and device address as see fit to match + //values that match the current test environment + //The target characteristic must be readble and writable + //under encryption to test dynamic switching of security level + QBluetoothAddress encryptedDevice(QString("00:02:5B:00:15:10")); + QBluetoothUuid serviceUuid(QBluetoothUuid::GenericAccess); + QBluetoothUuid characterristicUuid(QBluetoothUuid::DeviceName); + + QLowEnergyController control(encryptedDevice); + QCOMPARE(control.error(), QLowEnergyController::NoError); + + control.connectToDevice(); + { + QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState, + 30000); + } + + if (control.state() == QLowEnergyController::ConnectingState + || control.error() != QLowEnergyController::NoError) { + // default BTLE backend forever hangs in ConnectingState + QSKIP("Cannot connect to remote device"); + } + + QCOMPARE(control.state(), QLowEnergyController::ConnectedState); + QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished())); + QSignalSpy stateSpy(&control, SIGNAL(stateChanged(QLowEnergyController::ControllerState))); + control.discoverServices(); + QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 10000); + QCOMPARE(stateSpy.count(), 2); + QCOMPARE(stateSpy.at(0).at(0).value<QLowEnergyController::ControllerState>(), + QLowEnergyController::DiscoveringState); + QCOMPARE(stateSpy.at(1).at(0).value<QLowEnergyController::ControllerState>(), + QLowEnergyController::DiscoveredState); + + QList<QBluetoothUuid> uuids = control.services(); + QVERIFY(uuids.contains(serviceUuid)); + + QLowEnergyService *service = control.createServiceObject(serviceUuid, this); + QVERIFY(service); + service->discoverDetails(); + QTRY_VERIFY_WITH_TIMEOUT( + service->state() == QLowEnergyService::ServiceDiscovered, 30000); + + QLowEnergyCharacteristic characteristic = service->characteristic( + characterristicUuid); + + QVERIFY(characteristic.isValid()); + qDebug() << "Encrypted char value:" << characteristic.value().toHex() << characteristic.value(); + QVERIFY(!characteristic.value().isEmpty()); + + delete service; + control.disconnectFromDevice(); +} + +/* Tests write without responses. We utilize the Over-The-Air image update service of the SensorTag. */ |