summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@digia.com>2014-09-10 12:10:25 +0200
committerAlex Blasche <alexander.blasche@digia.com>2014-09-16 08:59:10 +0200
commit27abf796987c137d2660efd8e73b0e0fc8ffad0c (patch)
tree0bc4d4715b83f11cf4845e38e3074a6e039f9ffd
parent3d87d02970237c9e7a8ef8d921702bcc755130c0 (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.cpp1
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp70
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h9
-rw-r--r--tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp66
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.
*/