summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qlowenergycontroller_bluez.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/qlowenergycontroller_bluez.cpp')
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp170
1 files changed, 158 insertions, 12 deletions
diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp
index e5a3d8de..ca3f7760 100644
--- a/src/bluetooth/qlowenergycontroller_bluez.cpp
+++ b/src/bluetooth/qlowenergycontroller_bluez.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Copyright (C) 2016 Javier S. Pedro <maemo@javispedro.com>
** Contact: https://www.qt.io/licensing/
**
@@ -44,6 +44,9 @@
#include "qleadvertiser_p.h"
#include "bluez/bluez_data_p.h"
#include "bluez/hcimanager_p.h"
+#include "bluez/remotedevicemanager_p.h"
+#include "bluez/bluez5_helper_p.h"
+#include "bluez/bluetoothmanagement_p.h"
#include <QtCore/QFileInfo>
#include <QtCore/QLoggingCategory>
@@ -527,6 +530,70 @@ void QLowEnergyControllerPrivate::connectToDevice()
if (l2cpSocket)
delete l2cpSocket;
+ createServicesForCentralIfRequired();
+
+ // check for active running connections
+ // BlueZ 5.37+ (maybe even earlier versions) can have pending BTLE connections
+ // Only one active L2CP socket to CID 0x4 possible at a time
+ // this check is not performed for BlueZ 4 based platforms as bluetoothd
+ // does not support BTLE management
+
+ if (!isBluez5()) {
+ establishL2cpClientSocket();
+ return;
+ }
+
+ QVector<quint16> activeHandles = hciManager->activeLowEnergyConnections();
+ if (!activeHandles.isEmpty()) {
+ qCWarning(QT_BT_BLUEZ) << "Cannot connect due to pending active LE connections";
+
+ if (!device1Manager) {
+ device1Manager = new RemoteDeviceManager(localAdapter, this);
+ connect(device1Manager, &RemoteDeviceManager::finished,
+ this, &QLowEnergyControllerPrivate::activeConnectionTerminationDone);
+ }
+
+ QVector<QBluetoothAddress> connectedAddresses;
+ for (const auto handle: activeHandles) {
+ const QBluetoothAddress addr = hciManager->addressForConnectionHandle(handle);
+ if (!addr.isNull())
+ connectedAddresses.push_back(addr);
+ }
+ device1Manager->scheduleJob(RemoteDeviceManager::JobType::JobDisconnectDevice, connectedAddresses);
+ } else {
+ establishL2cpClientSocket();
+ }
+}
+
+/*!
+ * Handles outcome of attempts to close external connections.
+ */
+void QLowEnergyControllerPrivate::activeConnectionTerminationDone()
+{
+ if (!device1Manager)
+ return;
+
+ qCDebug(QT_BT_BLUEZ) << "RemoteDeviceManager finished attempting"
+ << "to close external connections";
+
+ QVector<quint16> activeHandles = hciManager->activeLowEnergyConnections();
+ if (!activeHandles.isEmpty()) {
+ qCWarning(QT_BT_BLUEZ) << "Cannot close pending external BTLE connections. Aborting connect attempt";
+ setError(QLowEnergyController::ConnectionError);
+ setState(QLowEnergyController::UnconnectedState);
+ return;
+ } else {
+ establishL2cpClientSocket();
+ }
+}
+
+/*!
+ * Establishes the L2CP client socket.
+ */
+void QLowEnergyControllerPrivate::establishL2cpClientSocket()
+{
+ //we are already in Connecting state
+
l2cpSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol, this);
connect(l2cpSocket, SIGNAL(connected()), this, SLOT(l2cpConnected()));
connect(l2cpSocket, SIGNAL(disconnected()), this, SLOT(l2cpDisconnected()));
@@ -534,10 +601,20 @@ void QLowEnergyControllerPrivate::connectToDevice()
this, SLOT(l2cpErrorChanged(QBluetoothSocket::SocketError)));
connect(l2cpSocket, SIGNAL(readyRead()), this, SLOT(l2cpReadyRead()));
- if (addressType == QLowEnergyController::PublicAddress)
- l2cpSocket->d_ptr->lowEnergySocketType = BDADDR_LE_PUBLIC;
- else if (addressType == QLowEnergyController::RandomAddress)
- l2cpSocket->d_ptr->lowEnergySocketType = BDADDR_LE_RANDOM;
+ quint32 addressTypeToUse = (addressType == QLowEnergyController::PublicAddress)
+ ? BDADDR_LE_PUBLIC : BDADDR_LE_RANDOM;
+ if (BluetoothManagement::instance()->isMonitoringEnabled()) {
+ // if monitoring is possible and it's private then we force it to the relevant option
+ if (BluetoothManagement::instance()->isAddressRandom(remoteDevice)) {
+ addressTypeToUse = BDADDR_LE_RANDOM;
+ }
+ }
+
+ qCDebug(QT_BT_BLUEZ) << "addresstypeToUse:"
+ << (addressTypeToUse == BDADDR_LE_RANDOM
+ ? QStringLiteral("Random") : QStringLiteral("Public"));
+
+ l2cpSocket->d_ptr->lowEnergySocketType = addressTypeToUse;
int sockfd = l2cpSocket->socketDescriptor();
if (sockfd < 0) {
@@ -569,6 +646,72 @@ void QLowEnergyControllerPrivate::connectToDevice()
loadSigningDataIfNecessary(LocalSigningKey);
}
+void QLowEnergyControllerPrivate::createServicesForCentralIfRequired()
+{
+ bool ok = false;
+ int value = qEnvironmentVariableIntValue("QT_DEFAULT_CENTRAL_SERVICES", &ok);
+ if (Q_UNLIKELY(ok && value == 0))
+ return; //nothing to do
+
+ //do not add the services each time we start a connection
+ if (localServices.contains(QBluetoothUuid(QBluetoothUuid::GenericAccess)))
+ return;
+
+ qCDebug(QT_BT_BLUEZ) << "Creating default GAP/GATT services";
+
+ //populate Generic Access service
+ //for now the values are static
+ QLowEnergyServiceData gapServiceData;
+ gapServiceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
+ gapServiceData.setUuid(QBluetoothUuid::GenericAccess);
+
+ QLowEnergyCharacteristicData gapDeviceName;
+ gapDeviceName.setUuid(QBluetoothUuid::DeviceName);
+ gapDeviceName.setProperties(QLowEnergyCharacteristic::Read);
+
+ QBluetoothLocalDevice mainAdapter;
+ gapDeviceName.setValue(mainAdapter.name().toLatin1()); //static name
+
+ QLowEnergyCharacteristicData gapAppearance;
+ gapAppearance.setUuid(QBluetoothUuid::Appearance);
+ gapAppearance.setProperties(QLowEnergyCharacteristic::Read);
+ gapAppearance.setValue(QByteArray::fromHex("80")); // Generic Computer (0x80)
+
+ QLowEnergyCharacteristicData gapPrivacyFlag;
+ gapPrivacyFlag.setUuid(QBluetoothUuid::PeripheralPrivacyFlag);
+ gapPrivacyFlag.setProperties(QLowEnergyCharacteristic::Read);
+ gapPrivacyFlag.setValue(QByteArray::fromHex("00")); // disable privacy
+
+ gapServiceData.addCharacteristic(gapDeviceName);
+ gapServiceData.addCharacteristic(gapAppearance);
+ gapServiceData.addCharacteristic(gapPrivacyFlag);
+
+ Q_Q(QLowEnergyController);
+ QLowEnergyService *service = addServiceHelper(gapServiceData);
+ if (service)
+ service->setParent(q);
+
+ QLowEnergyServiceData gattServiceData;
+ gattServiceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
+ gattServiceData.setUuid(QBluetoothUuid::GenericAttribute);
+
+ QLowEnergyCharacteristicData serviceChangedChar;
+ serviceChangedChar.setUuid(QBluetoothUuid::ServiceChanged);
+ serviceChangedChar.setProperties(QLowEnergyCharacteristic::Indicate);
+ //arbitrary range of 2 bit handle range (1-4
+ serviceChangedChar.setValue(QByteArray::fromHex("0104"));
+
+ const QLowEnergyDescriptorData clientConfig(
+ QBluetoothUuid::ClientCharacteristicConfiguration,
+ QByteArray(2, 0));
+ serviceChangedChar.addDescriptor(clientConfig);
+ gattServiceData.addCharacteristic(serviceChangedChar);
+
+ service = addServiceHelper(gattServiceData);
+ if (service)
+ service->setParent(q);
+}
+
void QLowEnergyControllerPrivate::l2cpConnected()
{
Q_Q(QLowEnergyController);
@@ -610,6 +753,10 @@ void QLowEnergyControllerPrivate::l2cpErrorChanged(QBluetoothSocket::SocketError
setError(QLowEnergyController::NetworkError);
qCDebug(QT_BT_BLUEZ) << "Network IO error while talking to LE device";
break;
+ case QBluetoothSocket::RemoteHostClosedError:
+ setError(QLowEnergyController::RemoteHostClosedError);
+ qCDebug(QT_BT_BLUEZ) << "Remote host closed the connection";
+ break;
case QBluetoothSocket::UnknownSocketError:
case QBluetoothSocket::UnsupportedProtocolError:
case QBluetoothSocket::OperationError:
@@ -1601,9 +1748,9 @@ void QLowEnergyControllerPrivate::readServiceValuesByOffset(
{
const QLowEnergyHandle charHandle = (handleData & 0xffff);
const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff);
- quint8 packet[READ_REQUEST_HEADER_SIZE];
- packet[0] = ATT_OP_READ_BLOB_REQUEST;
+ QByteArray data(READ_BLOB_REQUEST_HEADER_SIZE, Qt::Uninitialized);
+ data[0] = ATT_OP_READ_BLOB_REQUEST;
QLowEnergyHandle handleToRead = charHandle;
if (descriptorHandle) {
@@ -1624,11 +1771,8 @@ void QLowEnergyControllerPrivate::readServiceValuesByOffset(
}
}
- 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);
+ putBtData(handleToRead, data.data() + 1);
+ putBtData(offset, data.data() + 3);
Request request;
request.payload = data;
@@ -1768,8 +1912,10 @@ bool QLowEnergyControllerPrivate::setSecurityLevel(int level)
switch (level) { // fall through intendeds
case BT_SECURITY_HIGH:
optval |= L2CAP_LM_SECURE;
+ Q_FALLTHROUGH();
case BT_SECURITY_MEDIUM:
optval |= L2CAP_LM_ENCRYPT;
+ Q_FALLTHROUGH();
case BT_SECURITY_LOW:
optval |= L2CAP_LM_AUTH;
break;