summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@theqtcompany.com>2015-12-02 15:02:47 +0100
committerAlex Blasche <alexander.blasche@theqtcompany.com>2015-12-04 14:17:33 +0000
commit38b765206c352cc88123a03025024b3e5e368a4a (patch)
tree189a37106251c87d499b1122bafa1b06ca3a4753
parentb0a0b2572a5060c268f96a77716e7ea4e3065da4 (diff)
Bluetooth LE: Implement ATT access permissions.
Change-Id: I456d083d45569ea8d61f0a659f72646d653143d1 Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
-rw-r--r--src/bluetooth/qbluetooth.cpp14
-rw-r--r--src/bluetooth/qbluetooth.h14
-rw-r--r--src/bluetooth/qlowenergycharacteristicdata.cpp49
-rw-r--r--src/bluetooth/qlowenergycharacteristicdata.h8
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp64
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h3
-rw-r--r--src/bluetooth/qlowenergydescriptordata.cpp72
-rw-r--r--src/bluetooth/qlowenergydescriptordata.h10
-rw-r--r--tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp8
-rw-r--r--tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp30
10 files changed, 243 insertions, 29 deletions
diff --git a/src/bluetooth/qbluetooth.cpp b/src/bluetooth/qbluetooth.cpp
index 7a1f42ea..03b08bb4 100644
--- a/src/bluetooth/qbluetooth.cpp
+++ b/src/bluetooth/qbluetooth.cpp
@@ -67,6 +67,20 @@ namespace QBluetooth {
Simple Pairing from Bluetooth 2.1 or greater is required.
Legacy pairing is not permitted.
*/
+
+/*!
+ \enum QBluetooth::AttAccessConstraint
+
+ This enum describes the possible requirements for reading or writing an ATT attribute.
+
+ \value AttAuthorizationRequired
+ The client needs authorization from the ATT server to access the attribute.
+ \value AttAuthenticationRequired
+ The client needs to be authenticated to access the attribute.
+ \value AttEncryptionRequired
+ The attribute can only be accessed if the connection is encrypted.
+*/
+
}
/*!
diff --git a/src/bluetooth/qbluetooth.h b/src/bluetooth/qbluetooth.h
index f448af02..1053b388 100644
--- a/src/bluetooth/qbluetooth.h
+++ b/src/bluetooth/qbluetooth.h
@@ -39,6 +39,10 @@
QT_BEGIN_NAMESPACE
namespace QBluetooth {
+
+// TODO Qt 6: Merge these two enums? But note that ATT Authorization has no equivalent
+// on the socket security level.
+
enum Security {
NoSecurity = 0x00,
Authorization = 0x01,
@@ -49,6 +53,16 @@ enum Security {
Q_DECLARE_FLAGS(SecurityFlags, Security)
Q_DECLARE_OPERATORS_FOR_FLAGS(SecurityFlags)
+
+enum AttAccessConstraint {
+ AttAuthorizationRequired = 0x1,
+ AttAuthenticationRequired = 0x2,
+ AttEncryptionRequired = 0x4,
+};
+
+Q_DECLARE_FLAGS(AttAccessConstraints, AttAccessConstraint)
+Q_DECLARE_OPERATORS_FOR_FLAGS(AttAccessConstraints)
+
}
typedef quint16 QLowEnergyHandle;
diff --git a/src/bluetooth/qlowenergycharacteristicdata.cpp b/src/bluetooth/qlowenergycharacteristicdata.cpp
index a64af7a3..f11779a5 100644
--- a/src/bluetooth/qlowenergycharacteristicdata.cpp
+++ b/src/bluetooth/qlowenergycharacteristicdata.cpp
@@ -51,6 +51,8 @@ struct QLowEnergyCharacteristicDataPrivate : public QSharedData
QLowEnergyCharacteristic::PropertyTypes properties;
QList<QLowEnergyDescriptorData> descriptors;
QByteArray value;
+ QBluetooth::AttAccessConstraints readConstraints;
+ QBluetooth::AttAccessConstraints writeConstraints;
};
/*!
@@ -157,6 +159,44 @@ void QLowEnergyCharacteristicData::addDescriptor(const QLowEnergyDescriptorData
}
/*!
+ Specifies that clients need to fulfill \a constraints to read the value of this characteristic.
+ */
+void QLowEnergyCharacteristicData::setReadConstraints(QBluetooth::AttAccessConstraints constraints)
+{
+ d->readConstraints = constraints;
+}
+
+/*!
+ Returns the constraints needed for a client to read the value of this characteristic.
+ If \l properties() does not include \l QLowEnergyCharacteristic::Read, this value is irrelevant.
+ By default, there are no read constraints.
+ */
+QBluetooth::AttAccessConstraints QLowEnergyCharacteristicData::readConstraints() const
+{
+ return d->readConstraints;
+}
+
+/*!
+ Specifies that clients need to fulfill \a constraints to write the value of this characteristic.
+ */
+void QLowEnergyCharacteristicData::setWriteConstraints(QBluetooth::AttAccessConstraints constraints)
+{
+ d->writeConstraints = constraints;
+}
+
+/*!
+ Returns the constraints needed for a client to write the value of this characteristic.
+ If \l properties() does not include either of \l QLowEnergyCharacteristic::Write,
+ \l QLowEnergyCharacteristic::WriteNoResponse and \l QLowEnergyCharacteristic::WriteSigned,
+ this value is irrelevant.
+ By default, there are no write constraints.
+ */
+QBluetooth::AttAccessConstraints QLowEnergyCharacteristicData::writeConstraints() const
+{
+ return d->writeConstraints;
+}
+
+/*!
Returns true if and only if this characteristic is valid, that is, it has a non-null UUID.
*/
bool QLowEnergyCharacteristicData::isValid() const
@@ -175,8 +215,13 @@ bool QLowEnergyCharacteristicData::isValid() const
*/
bool operator==(const QLowEnergyCharacteristicData &cd1, const QLowEnergyCharacteristicData &cd2)
{
- return cd1.d == cd2.d || (cd1.uuid() == cd2.uuid() && cd1.properties() == cd2.properties()
- && cd1.descriptors() == cd2.descriptors() && cd1.value() == cd2.value());
+ return cd1.d == cd2.d || (
+ cd1.uuid() == cd2.uuid()
+ && cd1.properties() == cd2.properties()
+ && cd1.descriptors() == cd2.descriptors()
+ && cd1.value() == cd2.value()
+ && cd1.readConstraints() == cd2.readConstraints()
+ && cd1.writeConstraints() == cd2.writeConstraints());
}
/*!
diff --git a/src/bluetooth/qlowenergycharacteristicdata.h b/src/bluetooth/qlowenergycharacteristicdata.h
index ac78af5e..dc99f002 100644
--- a/src/bluetooth/qlowenergycharacteristicdata.h
+++ b/src/bluetooth/qlowenergycharacteristicdata.h
@@ -64,12 +64,16 @@ public:
void setDescriptors(const QList<QLowEnergyDescriptorData> &descriptors);
void addDescriptor(const QLowEnergyDescriptorData &descriptor);
+ void setReadConstraints(QBluetooth::AttAccessConstraints constraints);
+ QBluetooth::AttAccessConstraints readConstraints() const;
+
+ void setWriteConstraints(QBluetooth::AttAccessConstraints constraints);
+ QBluetooth::AttAccessConstraints writeConstraints() const;
+
bool isValid() const;
void swap(QLowEnergyCharacteristicData &other) Q_DECL_NOTHROW { qSwap(d, other.d); }
- // TODO: authentication/authorization requirements (separate for reading and writing!)
-
private:
QSharedDataPointer<QLowEnergyCharacteristicDataPrivate> d;
};
diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp
index 461be068..a298ea25 100644
--- a/src/bluetooth/qlowenergycontroller_bluez.cpp
+++ b/src/bluetooth/qlowenergycontroller_bluez.cpp
@@ -132,6 +132,8 @@ 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)
@@ -2493,6 +2495,8 @@ void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServ
attribute.groupEndHandle = attribute.handle;
attribute.type = cd.uuid();
attribute.properties = cd.properties();
+ attribute.readConstraints = cd.readConstraints();
+ attribute.writeConstraints = cd.writeConstraints();
attribute.value = cd.value();
localAttributes[attribute.handle] = attribute;
@@ -2500,7 +2504,33 @@ void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServ
attribute.handle = ++currentHandle;
attribute.groupEndHandle = attribute.handle;
attribute.type = dd.uuid();
- attribute.properties = QLowEnergyCharacteristic::Read; // TODO: The different descriptor types have different read/write capabilities.
+ attribute.properties = QLowEnergyCharacteristic::PropertyTypes();
+ attribute.readConstraints = AttAccessConstraints();
+ attribute.writeConstraints = AttAccessConstraints();
+ // Spec v4.2, Vol. 3, Part G, 3.3.3.x
+ if (attribute.type == QBluetoothUuid::CharacteristicExtendedProperties
+ || attribute.type == QBluetoothUuid::CharacteristicPresentationFormat
+ || attribute.type == QBluetoothUuid::CharacteristicAggregateFormat) {
+ attribute.properties = QLowEnergyCharacteristic::Read;
+ } else if (attribute.type == QBluetoothUuid::ClientCharacteristicConfiguration
+ || attribute.type == QBluetoothUuid::ServerCharacteristicConfiguration) {
+ attribute.properties = QLowEnergyCharacteristic::Read
+ | QLowEnergyCharacteristic::Write
+ | QLowEnergyCharacteristic::WriteNoResponse
+ | QLowEnergyCharacteristic::WriteSigned;
+ attribute.writeConstraints = dd.writeConstraints();
+ } 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();
localAttributes[attribute.handle] = attribute;
}
@@ -2556,27 +2586,23 @@ QVector<QLowEnergyControllerPrivate::Attribute> QLowEnergyControllerPrivate::get
int QLowEnergyControllerPrivate::checkPermissions(const Attribute &attr,
QLowEnergyCharacteristic::PropertyType type)
{
- // TODO: Actual permission checks.
- if (false)
- return ATT_ERROR_INSUF_AUTHORIZATION;
- if (false)
+ const bool isReadAccess = type == QLowEnergyCharacteristic::Read;
+ const bool isWriteAccess = type == QLowEnergyCharacteristic::Write
+ || type == QLowEnergyCharacteristic::WriteNoResponse
+ || type == QLowEnergyCharacteristic::WriteSigned;
+ Q_ASSERT(isReadAccess || isWriteAccess);
+ if (!(attr.properties & type))
+ return isReadAccess ? ATT_ERROR_READ_NOT_PERM : 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;
- if (false)
- return ATT_ERROR_INSUF_ENCRYPTION;
- if (!(attr.properties & type)) {
- switch (type) {
- case QLowEnergyCharacteristic::Read:
- return ATT_ERROR_READ_NOT_PERM;
- case QLowEnergyCharacteristic::Write:
- case QLowEnergyCharacteristic::WriteSigned:
- case QLowEnergyCharacteristic::WriteNoResponse:
- return ATT_ERROR_WRITE_NOT_PERM;
- default:
- Q_ASSERT(false); // Cannot happen.
- }
- }
return 0;
}
diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h
index f18a0384..e693e84d 100644
--- a/src/bluetooth/qlowenergycontroller_p.h
+++ b/src/bluetooth/qlowenergycontroller_p.h
@@ -179,9 +179,10 @@ public:
QLowEnergyHandle handle;
QLowEnergyHandle groupEndHandle;
QLowEnergyCharacteristic::PropertyTypes properties;
+ QBluetooth::AttAccessConstraints readConstraints;
+ QBluetooth::AttAccessConstraints writeConstraints;
QBluetoothUuid type;
QByteArray value;
- // TODO: authentication/authorization requirements
};
QVector<Attribute> localAttributes;
diff --git a/src/bluetooth/qlowenergydescriptordata.cpp b/src/bluetooth/qlowenergydescriptordata.cpp
index 40bb571c..db5ea238 100644
--- a/src/bluetooth/qlowenergydescriptordata.cpp
+++ b/src/bluetooth/qlowenergydescriptordata.cpp
@@ -39,8 +39,14 @@ QT_BEGIN_NAMESPACE
struct QLowEnergyDescriptorDataPrivate : public QSharedData
{
+ QLowEnergyDescriptorDataPrivate() : readable(true), writable(true) {}
+
QBluetoothUuid uuid;
QByteArray value;
+ QBluetooth::AttAccessConstraints readConstraints;
+ QBluetooth::AttAccessConstraints writeConstraints;
+ bool readable;
+ bool writable;
};
/*!
@@ -53,6 +59,10 @@ struct QLowEnergyDescriptorDataPrivate : public QSharedData
An object of this class provides a descriptor to be added to a
\l QLowEnergyCharacteristicData object via \l QLowEnergyCharacteristicData::addDescriptor().
+ \note The member functions related to access permissions are only applicable to those
+ types of descriptors for which the Bluetooth specification does not prescribe if
+ and how their values can be accessed.
+
\sa QLowEnergyCharacteristicData
\sa QLowEnergyServiceData
\sa QLowEnergyController::addService
@@ -127,6 +137,60 @@ bool QLowEnergyDescriptorData::isValid() const
}
/*!
+ Specifies whether the value of this descriptor is \a readable and if so, under which
+ \a constraints.
+ \sa setWritePermissions()
+ */
+void QLowEnergyDescriptorData::setReadPermissions(bool readable,
+ QBluetooth::AttAccessConstraints constraints)
+{
+ d->readable = readable;
+ d->readConstraints = constraints;
+}
+
+/*! Returns \c true if the value of this descriptor is readable and \c false otherwise. */
+bool QLowEnergyDescriptorData::isReadable() const
+{
+ return d->readable;
+}
+
+/*!
+ Returns the constraints under which the value of this descriptor can be read. This value
+ is only relevant if \l isReadable() returns \c true.
+ */
+QBluetooth::AttAccessConstraints QLowEnergyDescriptorData::readConstraints() const
+{
+ return d->readConstraints;
+}
+
+/*!
+ Specifies whether the value of this descriptor is \a writable and if so, under which
+ \a constraints.
+ \sa setReadPermissions()
+ */
+void QLowEnergyDescriptorData::setWritePermissions(bool writable,
+ QBluetooth::AttAccessConstraints constraints)
+{
+ d->writable = writable;
+ d->writeConstraints = constraints;
+}
+
+/*! Returns \c true if the value of this descriptor is writable and \c false otherwise. */
+bool QLowEnergyDescriptorData::isWritable() const
+{
+ return d->writable;
+}
+
+/*!
+ Returns the constraints under which the value of this descriptor can be written. This value
+ is only relevant if \l isWritable() returns \c true.
+ */
+QBluetooth::AttAccessConstraints QLowEnergyDescriptorData::writeConstraints() const
+{
+ return d->writeConstraints;
+}
+
+/*!
\fn void QLowEnergyDescriptorData::swap(QLowEnergyDescriptorData &other)
Swaps this object with \a other.
*/
@@ -137,7 +201,13 @@ bool QLowEnergyDescriptorData::isValid() const
*/
bool operator==(const QLowEnergyDescriptorData &d1, const QLowEnergyDescriptorData &d2)
{
- return d1.d == d2.d || (d1.uuid() == d2.uuid() && d1.value() == d2.value());
+ return d1.d == d2.d || (
+ d1.uuid() == d2.uuid()
+ && d1.value() == d2.value()
+ && d1.isReadable() == d2.isReadable()
+ && d1.isWritable() == d2.isWritable()
+ && d1.readConstraints() == d2.readConstraints()
+ && d1.writeConstraints() == d2.writeConstraints());
}
/*!
diff --git a/src/bluetooth/qlowenergydescriptordata.h b/src/bluetooth/qlowenergydescriptordata.h
index 189b73e4..c46eb249 100644
--- a/src/bluetooth/qlowenergydescriptordata.h
+++ b/src/bluetooth/qlowenergydescriptordata.h
@@ -64,7 +64,15 @@ public:
bool isValid() const;
- // TODO: read permissions, write permissions, authentication/authorization (only applicable for some descriptors)
+ void setReadPermissions(bool readable,
+ QBluetooth::AttAccessConstraints constraints = QBluetooth::AttAccessConstraints());
+ bool isReadable() const;
+ QBluetooth::AttAccessConstraints readConstraints() const;
+
+ void setWritePermissions(bool writable,
+ QBluetooth::AttAccessConstraints constraints = QBluetooth::AttAccessConstraints());
+ bool isWritable() const;
+ QBluetooth::AttAccessConstraints writeConstraints() const;
void swap(QLowEnergyDescriptorData &other) Q_DECL_NOTHROW { qSwap(d, other.d); }
diff --git a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp
index ddba0ddb..b1fc7256 100644
--- a/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp
+++ b/tests/auto/qlowenergycontroller-gattserver/server/qlowenergycontroller-gattserver.cpp
@@ -100,7 +100,7 @@ void addGenericAccessService()
charData.setUuid(QBluetoothUuid::Appearance);
charData.setProperties(QLowEnergyCharacteristic::Read);
QByteArray value(2, 0);
- value[0] = -1; // 128 => Generic computer.
+ value[0] = 64; // Generic Phone
charData.setValue(value);
serviceData.addCharacteristic(charData);
@@ -120,6 +120,12 @@ void addCustomService()
charData.setValue(QByteArray(1024, 'x')); // Long value to test "Read Blob".
serviceData.addCharacteristic(charData);
+ charData.setUuid(QBluetoothUuid(quint16(0x5001))); // Made up.
+ charData.setProperties(QLowEnergyCharacteristic::Read);
+ charData.setReadConstraints(QBluetooth::AttAuthorizationRequired); // To test read failure.
+ serviceData.addCharacteristic(charData);
+ charData.setValue("something");
+
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 12d0db17..25098273 100644
--- a/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp
+++ b/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp
@@ -46,6 +46,8 @@
#include <algorithm>
+using namespace QBluetooth;
+
class TestQLowEnergyControllerGattServer : public QObject
{
Q_OBJECT
@@ -213,7 +215,7 @@ void TestQLowEnergyControllerGattServer::initialServices()
QCOMPARE(appearanceChar.descriptors().count(), 0);
QCOMPARE(appearanceChar.properties(), QLowEnergyCharacteristic::Read);
QByteArray appearanceValue(2, 0);
- appearanceValue[0] = -1;
+ appearanceValue[0] = 64;
QCOMPARE(appearanceChar.value(), appearanceValue);
const QScopedPointer<QLowEnergyService> runningSpeedService(
@@ -254,12 +256,24 @@ void TestQLowEnergyControllerGattServer::initialServices()
QVERIFY(spy->wait(3000));
}
QCOMPARE(customService->includedServices().count(), 0);
- QCOMPARE(customService->characteristics().count(), 1);
+ QCOMPARE(customService->characteristics().count(), 2);
QLowEnergyCharacteristic customChar
= customService->characteristic(QBluetoothUuid(quint16(0x5000)));
QVERIFY(customChar.isValid());
QCOMPARE(customChar.descriptors().count(), 0);
QCOMPARE(customChar.value(), QByteArray(1024, 'x'));
+
+ QLowEnergyCharacteristic customChar2
+ = customService->characteristic(QBluetoothUuid(quint16(0x5001)));
+ QVERIFY(customChar2.isValid());
+ QCOMPARE(customChar2.descriptors().count(), 0);
+ QCOMPARE(customChar2.value(), QByteArray()); // Was not readable due to authorization requirement.
+
+ 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);
}
void TestQLowEnergyControllerGattServer::controllerType()
@@ -282,6 +296,13 @@ void TestQLowEnergyControllerGattServer::serviceData()
descData.setValue("xyz");
QCOMPARE(descData.value().constData(), "xyz");
+ descData.setReadPermissions(true, AttAuthenticationRequired);
+ QCOMPARE(descData.isReadable(), true);
+ QCOMPARE(descData.readConstraints(), AttAuthenticationRequired);
+
+ descData.setWritePermissions(false);
+ QCOMPARE(descData.isWritable(), false);
+
QLowEnergyDescriptorData descData2(QBluetoothUuid::ReportReference, "abc");
QVERIFY(descData2 != QLowEnergyDescriptorData());
QVERIFY(descData2.isValid());
@@ -304,6 +325,11 @@ void TestQLowEnergyControllerGattServer::serviceData()
charData.setProperties(props);
QCOMPARE(charData.properties(), props);
+ charData.setReadConstraints(AttEncryptionRequired);
+ QCOMPARE(charData.readConstraints(), AttEncryptionRequired);
+ charData.setWriteConstraints(AttAuthenticationRequired | AttAuthorizationRequired);
+ QCOMPARE(charData.writeConstraints(), AttAuthenticationRequired | AttAuthorizationRequired);
+
charData.setDescriptors(QList<QLowEnergyDescriptorData>() << descData << descData2);
QLowEnergyDescriptorData descData3(QBluetoothUuid::ExternalReportReference, "someval");
charData.addDescriptor(descData3);