summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@theqtcompany.com>2015-10-15 12:00:13 +0200
committerAlex Blasche <alexander.blasche@theqtcompany.com>2015-11-19 06:52:35 +0000
commit57353d3f91f89e6e364b9798277048e25b9c63b8 (patch)
treef8bd609be5ade91e7b09dfb8f1d48578e51021e7
parenta8d80a81d56f0fb85c41bf6ec5716c5ce7e5dac0 (diff)
QLowEnergyController: Listen for and accept GATT connection requests.
BlueZ only. No requests are handled yet. Change-Id: I25058989beb5b3ae02a4f43eeaec09c8225198dc Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
-rw-r--r--src/bluetooth/qlowenergycontroller.cpp8
-rw-r--r--src/bluetooth/qlowenergycontroller.h2
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp122
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h5
4 files changed, 132 insertions, 5 deletions
diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp
index 9a9327a5..f8832f1a 100644
--- a/src/bluetooth/qlowenergycontroller.cpp
+++ b/src/bluetooth/qlowenergycontroller.cpp
@@ -306,6 +306,10 @@ void QLowEnergyControllerPrivate::setState(
return;
state = newState;
+ if (state == QLowEnergyController::UnconnectedState
+ && role == QLowEnergyController::PeripheralRole) {
+ remoteDevice.clear();
+ }
emit q->stateChanged(state);
}
@@ -769,10 +773,8 @@ QLowEnergyService *QLowEnergyController::createServiceObject(
/*!
Starts advertising the data given in \a advertisingData and \a scanResponseData, using
the parameters set in \a parameters. The controller has to be in the \l PeripheralRole.
- \omit
- If \a parameters indicates that the advertisement should be connectable, then this call
+ If \a parameters indicates that the advertisement should be connectable, then this function
also starts listening for incoming client connections.
- \endomit
Providing \a scanResponseData is not required, as it is not applicable for certain
configurations of \c parameters.
diff --git a/src/bluetooth/qlowenergycontroller.h b/src/bluetooth/qlowenergycontroller.h
index 66928da8..1abc395c 100644
--- a/src/bluetooth/qlowenergycontroller.h
+++ b/src/bluetooth/qlowenergycontroller.h
@@ -93,6 +93,8 @@ public:
QObject *parent = 0);
static QLowEnergyController *createPeripheral(QObject *parent = 0);
+ // TODO: Allow to set connection timeout (disconnect when no data has been exchanged for n seconds).
+
~QLowEnergyController();
QBluetoothAddress localAddress() const;
diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp
index 45d392d8..ea7a763a 100644
--- a/src/bluetooth/qlowenergycontroller_bluez.cpp
+++ b/src/bluetooth/qlowenergycontroller_bluez.cpp
@@ -43,6 +43,9 @@
#include <QtBluetooth/QLowEnergyService>
#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
#define ATTRIBUTE_CHANNEL_ID 4
@@ -205,7 +208,8 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
securityLevelValue(-1),
encryptionChangePending(false),
hciManager(0),
- advertiser(0)
+ advertiser(0),
+ serverSocketNotifier(0)
{
qRegisterMetaType<QList<QLowEnergyHandle> >();
@@ -220,7 +224,58 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate()
{
+ closeServerSocket();
}
+
+class ServerSocket
+{
+public:
+ bool listen(const QBluetoothAddress &localAdapter)
+ {
+ m_socket = ::socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+ if (m_socket == -1) {
+ qCWarning(QT_BT_BLUEZ) << "socket creation failed:" << qt_error_string(errno);
+ return false;
+ }
+ sockaddr_l2 addr;
+
+ // memset should be in std namespace for C++ compilers, but we also need to support
+ // broken ones that put it in the global one.
+ using namespace std;
+ memset(&addr, 0, sizeof addr);
+
+ addr.l2_family = AF_BLUETOOTH;
+ addr.l2_cid = htobs(ATTRIBUTE_CHANNEL_ID);
+ addr.l2_bdaddr_type = BDADDR_LE_PUBLIC;
+ convertAddress(localAdapter.toUInt64(), addr.l2_bdaddr.b);
+ if (::bind(m_socket, reinterpret_cast<sockaddr *>(&addr), sizeof addr) == -1) {
+ qCWarning(QT_BT_BLUEZ) << "bind() failed:" << qt_error_string(errno);
+ return false;
+ }
+ if (::listen(m_socket, 1)) {
+ qCWarning(QT_BT_BLUEZ) << "listen() failed:" << qt_error_string(errno);
+ return false;
+ }
+ return true;
+ }
+
+ ~ServerSocket()
+ {
+ if (m_socket != -1)
+ close(m_socket);
+ }
+
+ int takeSocket()
+ {
+ const int socket = m_socket;
+ m_socket = -1;
+ return socket;
+ }
+
+private:
+ int m_socket = -1;
+};
+
void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters &params,
const QLowEnergyAdvertisingData &advertisingData,
const QLowEnergyAdvertisingData &scanResponseData)
@@ -234,6 +289,24 @@ void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingPa
}
setState(QLowEnergyController::AdvertisingState);
advertiser->startAdvertising();
+ if (params.mode() == QLowEnergyAdvertisingParameters::AdvNonConnInd
+ || params.mode() == QLowEnergyAdvertisingParameters::AdvScanInd) {
+ qCDebug(QT_BT_BLUEZ) << "Non-connectable advertising requested, "
+ "not listening for connections.";
+ return;
+ }
+
+ ServerSocket serverSocket;
+ if (!serverSocket.listen(localAdapter)) {
+ setError(QLowEnergyController::AdvertisingError);
+ setState(QLowEnergyController::UnconnectedState);
+ return;
+ }
+
+ const int socketFd = serverSocket.takeSocket();
+ serverSocketNotifier = new QSocketNotifier(socketFd, QSocketNotifier::Read, this);
+ connect(serverSocketNotifier, &QSocketNotifier::activated, this,
+ &QLowEnergyControllerPrivate::handleConnectionRequest);
}
void QLowEnergyControllerPrivate::stopAdvertising()
@@ -315,7 +388,8 @@ void QLowEnergyControllerPrivate::l2cpDisconnected()
{
Q_Q(QLowEnergyController);
- securityLevelValue = -1;
+ invalidateServices();
+ resetController();
setState(QLowEnergyController::UnconnectedState);
emit q->disconnected();
}
@@ -1772,4 +1846,48 @@ void QLowEnergyControllerPrivate::handleAdvertisingError()
setState(QLowEnergyController::UnconnectedState);
}
+void QLowEnergyControllerPrivate::handleConnectionRequest()
+{
+ if (state != QLowEnergyController::AdvertisingState) {
+ qCWarning(QT_BT_BLUEZ) << "Incoming connection request in unexpected state" << state;
+ return;
+ }
+ Q_ASSERT(serverSocketNotifier);
+ serverSocketNotifier->setEnabled(false);
+ sockaddr_l2 clientAddr;
+ socklen_t clientAddrSize = sizeof clientAddr;
+ const int clientSocket = accept(serverSocketNotifier->socket(),
+ reinterpret_cast<sockaddr *>(&clientAddr), &clientAddrSize);
+ if (clientSocket == -1) {
+ // Not fatal in itself. The next one might succeed.
+ qCWarning(QT_BT_BLUEZ) << "accept() failed:" << qt_error_string(errno);
+ serverSocketNotifier->setEnabled(true);
+ return;
+ }
+ remoteDevice = QBluetoothAddress(convertAddress(clientAddr.l2_bdaddr.b));
+ qCDebug(QT_BT_BLUEZ) << "GATT connection from device" << remoteDevice;
+
+ closeServerSocket();
+ l2cpSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol, this);
+ connect(l2cpSocket, &QBluetoothSocket::disconnected,
+ this, &QLowEnergyControllerPrivate::l2cpDisconnected);
+ connect(l2cpSocket, static_cast<void (QBluetoothSocket::*)(QBluetoothSocket::SocketError)>
+ (&QBluetoothSocket::error), this, &QLowEnergyControllerPrivate::l2cpErrorChanged);
+ connect(l2cpSocket, &QIODevice::readyRead, this, &QLowEnergyControllerPrivate::l2cpReadyRead);
+ l2cpSocket->d_ptr->lowEnergySocketType = addressType == QLowEnergyController::PublicAddress
+ ? BDADDR_LE_PUBLIC : BDADDR_LE_RANDOM;
+ l2cpSocket->setSocketDescriptor(clientSocket, QBluetoothServiceInfo::L2capProtocol);
+ setState(QLowEnergyController::ConnectedState);
+}
+
+void QLowEnergyControllerPrivate::closeServerSocket()
+{
+ if (!serverSocketNotifier)
+ return;
+ serverSocketNotifier->disconnect();
+ close(serverSocketNotifier->socket());
+ serverSocketNotifier->deleteLater();
+ serverSocketNotifier = nullptr;
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h
index 722de692..d91c2c73 100644
--- a/src/bluetooth/qlowenergycontroller_p.h
+++ b/src/bluetooth/qlowenergycontroller_p.h
@@ -80,6 +80,7 @@ QT_BEGIN_NAMESPACE
#if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE)
class HciManager;
+class QSocketNotifier;
#elif defined(QT_ANDROID_BLUETOOTH)
class LowEnergyNotificationHub;
#endif
@@ -180,6 +181,10 @@ private:
HciManager *hciManager;
QLeAdvertiser *advertiser;
+ QSocketNotifier *serverSocketNotifier;
+
+ void handleConnectionRequest();
+ void closeServerSocket();
void sendCommand(const QByteArray &packet);
void sendNextPendingRequest();