summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@digia.com>2014-09-12 11:13:42 +0200
committerAlex Blasche <alexander.blasche@digia.com>2014-09-16 08:59:06 +0200
commit3d87d02970237c9e7a8ef8d921702bcc755130c0 (patch)
treeb10bb3235d5b572d13f93b27cb1715ffdc1a11e6
parent8650ab69889218ec857475da3dfd99624f714585 (diff)
downloadqtconnectivity-3d87d02970237c9e7a8ef8d921702bcc755130c0.tar.gz
Add class for HCI protocol interaction
The first step is to monitor encryption changes. Later we will add more events and possibly commands as needed. Change-Id: I03ca547678bbfc971f53b32b1efde601685dd7e1 Reviewed-by: Lars Knoll <lars.knoll@digia.com>
-rw-r--r--src/bluetooth/bluez/bluez.pri6
-rw-r--r--src/bluetooth/bluez/bluez_data_p.h143
-rw-r--r--src/bluetooth/bluez/hcimanager.cpp285
-rw-r--r--src/bluetooth/bluez/hcimanager_p.h90
4 files changed, 522 insertions, 2 deletions
diff --git a/src/bluetooth/bluez/bluez.pri b/src/bluetooth/bluez/bluez.pri
index be5a02a7..46727cbc 100644
--- a/src/bluetooth/bluez/bluez.pri
+++ b/src/bluetooth/bluez/bluez.pri
@@ -17,7 +17,8 @@ HEADERS += bluez/manager_p.h \
bluez/obex_client1_bluez5_p.h \
bluez/obex_objectpush1_bluez5_p.h \
bluez/obex_transfer1_bluez5_p.h \
- bluez/bluez_data_p.h
+ bluez/bluez_data_p.h \
+ bluez/hcimanager_p.h
SOURCES += bluez/manager.cpp \
bluez/adapter.cpp \
@@ -37,4 +38,5 @@ SOURCES += bluez/manager.cpp \
bluez/profile1.cpp \
bluez/obex_client1_bluez5.cpp \
bluez/obex_objectpush1_bluez5.cpp \
- bluez/obex_transfer1_bluez5.cpp
+ bluez/obex_transfer1_bluez5.cpp \
+ bluez/hcimanager.cpp
diff --git a/src/bluetooth/bluez/bluez_data_p.h b/src/bluetooth/bluez/bluez_data_p.h
index 3e5b0245..7c799977 100644
--- a/src/bluetooth/bluez/bluez_data_p.h
+++ b/src/bluetooth/bluez/bluez_data_p.h
@@ -50,8 +50,10 @@
#include <QtBluetooth/QBluetoothUuid>
#define BTPROTO_L2CAP 0
+#define BTPROTO_HCI 1
#define BTPROTO_RFCOMM 3
+#define SOL_HCI 0
#define SOL_L2CAP 6
#define SOL_RFCOMM 18
#ifndef SOL_BLUETOOTH
@@ -84,6 +86,7 @@ struct bt_security {
#define BDADDR_LE_PUBLIC 0x01
#define BDADDR_LE_RANDOM 0x02
+
/* Byte order conversions */
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define htobs(d) (d)
@@ -187,4 +190,144 @@ static inline void ntoh128(const quint128 *src, quint128 *dst)
#define hton128(x, y) ntoh128(x, y)
+// HCI related
+
+#define HCI_MAX_DEV 16
+
+#define HCI_MAX_EVENT_SIZE 260
+
+// HCI sockopts
+#define HCI_FILTER 2
+
+// HCI packet types
+#define HCI_EVENT_PKT 0x04
+#define HCI_VENDOR_PKT 0xff
+
+#define HCI_FLT_TYPE_BITS 31
+#define HCI_FLT_EVENT_BITS 63
+
+struct sockaddr_hci {
+ sa_family_t hci_family;
+ unsigned short hci_dev;
+ unsigned short hci_channel;
+};
+
+struct hci_dev_req {
+ uint16_t dev_id;
+ uint32_t dev_opt;
+};
+
+struct hci_dev_list_req {
+ uint16_t dev_num;
+ struct hci_dev_req dev_req[0];
+};
+
+struct hci_dev_stats {
+ uint32_t err_rx;
+ uint32_t err_tx;
+ uint32_t cmd_tx;
+ uint32_t evt_rx;
+ uint32_t acl_tx;
+ uint32_t acl_rx;
+ uint32_t sco_tx;
+ uint32_t sco_rx;
+ uint32_t byte_rx;
+ uint32_t byte_tx;
+};
+
+struct hci_dev_info {
+ uint16_t dev_id;
+ char name[8];
+
+ bdaddr_t bdaddr;
+
+ uint32_t flags;
+ uint8_t type;
+
+ uint8_t features[8];
+
+ uint32_t pkt_type;
+ uint32_t link_policy;
+ uint32_t link_mode;
+
+ uint16_t acl_mtu;
+ uint16_t acl_pkts;
+ uint16_t sco_mtu;
+ uint16_t sco_pkts;
+
+ struct hci_dev_stats stat;
+};
+
+struct hci_conn_info {
+ uint16_t handle;
+ bdaddr_t bdaddr;
+ uint8_t type;
+ uint8_t out;
+ uint16_t state;
+ uint32_t link_mode;
+};
+
+struct hci_conn_list_req {
+ uint16_t dev_id;
+ uint16_t conn_num;
+ struct hci_conn_info conn_info[0];
+};
+
+struct hci_filter {
+ uint32_t type_mask;
+ uint32_t event_mask[2];
+ uint16_t opcode;
+};
+
+static inline void hci_set_bit(int nr, void *addr)
+{
+ *((uint32_t *) addr + (nr >> 5)) |= (1 << (nr & 31));
+}
+static inline void hci_clear_bit(int nr, void *addr)
+{
+ *((uint32_t *) addr + (nr >> 5)) &= ~(1 << (nr & 31));
+}
+static inline void hci_filter_clear(struct hci_filter *f)
+{
+ memset(f, 0, sizeof(*f));
+}
+static inline void hci_filter_set_ptype(int t, struct hci_filter *f)
+{
+ hci_set_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask);
+}
+static inline void hci_filter_clear_ptype(int t, struct hci_filter *f)
+{
+ hci_clear_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask);
+}
+static inline void hci_filter_set_event(int e, struct hci_filter *f)
+{
+ hci_set_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask);
+}
+static inline void hci_filter_clear_event(int e, struct hci_filter *f)
+{
+ hci_clear_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask);
+}
+static inline void hci_filter_all_ptypes(struct hci_filter *f)
+{
+ memset((void *) &f->type_mask, 0xff, sizeof(f->type_mask));
+}
+static inline void hci_filter_all_events(struct hci_filter *f)
+{
+ memset((void *) f->event_mask, 0xff, sizeof(f->event_mask));
+}
+
+typedef struct {
+ uint8_t evt;
+ uint8_t plen;
+} __attribute__ ((packed)) hci_event_hdr;
+#define HCI_EVENT_HDR_SIZE 2
+
+#define EVT_ENCRYPT_CHANGE 0x08
+typedef struct {
+ uint8_t status;
+ uint16_t handle;
+ uint8_t encrypt;
+} __attribute__ ((packed)) evt_encrypt_change;
+#define EVT_ENCRYPT_CHANGE_SIZE 4
+
#endif // BLUEZ_DATA_P_H
diff --git a/src/bluetooth/bluez/hcimanager.cpp b/src/bluetooth/bluez/hcimanager.cpp
new file mode 100644
index 00000000..be8ec6cc
--- /dev/null
+++ b/src/bluetooth/bluez/hcimanager.cpp
@@ -0,0 +1,285 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "hcimanager_p.h"
+
+#include "qbluetoothsocket_p.h"
+
+#include <QtCore/QLoggingCategory>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#define HCIGETCONNLIST _IOR('H', 212, int)
+#define HCIGETDEVINFO _IOR('H', 211, int)
+#define HCIGETDEVLIST _IOR('H', 210, int)
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
+
+HciManager::HciManager(const QBluetoothAddress& deviceAdapter, QObject *parent) :
+ QObject(parent), hciSocket(-1), hciDev(-1), notifier(0)
+{
+ hciSocket = ::socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI);
+ if (hciSocket < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Cannot open HCI socket";
+ return; //TODO error report
+ }
+
+ hciDev = hciForAddress(deviceAdapter);
+ if (hciDev < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Cannot find hci dev for" << deviceAdapter.toString();
+ close(hciSocket);
+ hciSocket = -1;
+ return;
+ }
+
+ struct sockaddr_hci addr;
+
+ memset(&addr, 0, sizeof(struct sockaddr_hci));
+ addr.hci_dev = hciDev;
+ addr.hci_family = AF_BLUETOOTH;
+
+ if (::bind(hciSocket, (struct sockaddr *) (&addr), sizeof(addr)) < 0) {
+ qCWarning(QT_BT_BLUEZ) << "HCI bind failed:" << strerror(errno);
+ close(hciSocket);
+ hciSocket = hciDev = -1;
+ return;
+ }
+
+ notifier = new QSocketNotifier(hciSocket, QSocketNotifier::Read, this);
+ connect(notifier, SIGNAL(activated(int)), this, SLOT(_q_readNotify()));
+
+}
+
+HciManager::~HciManager()
+{
+ if (hciSocket >= 0)
+ ::close(hciSocket);
+
+}
+
+bool HciManager::isValid() const
+{
+ if (hciSocket && hciDev >= 0)
+ return true;
+ return false;
+}
+
+int HciManager::hciForAddress(const QBluetoothAddress &deviceAdapter)
+{
+ if (hciSocket < 0)
+ return -1;
+
+ bdaddr_t adapter;
+ convertAddress(deviceAdapter.toUInt64(), adapter.b);
+
+ struct hci_dev_req *devRequest = 0;
+ struct hci_dev_list_req *devRequestList = 0;
+ struct hci_dev_info devInfo;
+ const int devListSize = sizeof(struct hci_dev_list_req)
+ + HCI_MAX_DEV * sizeof(struct hci_dev_req);
+
+ devRequestList = (hci_dev_list_req *) malloc(devListSize);
+ if (!devRequestList)
+ return -1;
+
+ QScopedPointer<hci_dev_list_req, QScopedPointerPodDeleter> p(devRequestList);
+
+ memset(p.data(), 0, devListSize);
+ p->dev_num = HCI_MAX_DEV;
+ devRequest = p->dev_req;
+
+ if (ioctl(hciSocket, HCIGETDEVLIST, devRequestList) < 0)
+ return -1;
+
+ for (int i = 0; i < devRequestList->dev_num; i++) {
+ devInfo.dev_id = (devRequest+i)->dev_id;
+ if (ioctl(hciSocket, HCIGETDEVINFO, &devInfo) < 0) {
+ continue;
+ }
+
+ int result = memcmp(&adapter, &devInfo.bdaddr, sizeof(bdaddr_t));
+ if (result == 0 || deviceAdapter.isNull()) // addresses match
+ return devRequest->dev_id;
+ }
+
+ return -1;
+}
+
+/*
+ * Returns true if \a event was successfully enabled
+ */
+bool HciManager::monitorEvent(HciManager::HciEvent event)
+{
+ if (!isValid())
+ return false;
+
+ // this event is already enabled
+ if (runningEvents.contains(event))
+ return true;
+
+ hci_filter filter;
+ socklen_t length = sizeof(hci_filter);
+ if (getsockopt(hciSocket, SOL_HCI, HCI_FILTER, &filter, &length) < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Cannot retrieve HCI filter settings";
+ return false;
+ }
+
+ hci_filter_set_ptype(HCI_EVENT_PKT, &filter);
+ hci_filter_set_event(event, &filter);
+ //hci_filter_all_events(&filter);
+
+ if (setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &filter, sizeof(hci_filter)) < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Could not set HCI socket options:" << strerror(errno);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Unsubscribe from all events
+ */
+void HciManager::stopEvents()
+{
+ if (!isValid())
+ return;
+
+ hci_filter filter;
+ hci_filter_clear(&filter);
+
+ if (setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &filter, sizeof(hci_filter)) < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Could not clear HCI socket options:" << strerror(errno);
+ return;
+ }
+
+ runningEvents.clear();
+}
+
+QBluetoothAddress HciManager::addressForConnectionHandle(quint16 handle) const
+{
+ if (!isValid())
+ return QBluetoothAddress();
+
+ hci_conn_info *info;
+ hci_conn_list_req *infoList;
+
+ const int maxNoOfConnections = 20;
+ infoList = (hci_conn_list_req *)
+ malloc(sizeof(hci_conn_list_req) + maxNoOfConnections * sizeof(hci_conn_info));
+
+ if (!infoList)
+ return QBluetoothAddress();
+
+ QScopedPointer<hci_conn_list_req, QScopedPointerPodDeleter> p(infoList);
+ p->conn_num = maxNoOfConnections;
+ p->dev_id = hciDev;
+ info = p->conn_info;
+
+ if (ioctl(hciSocket, HCIGETCONNLIST, (void *) infoList) < 0) {
+ qCWarning(QT_BT_BLUEZ) << "Cannot retrieve connection list";
+ return QBluetoothAddress();
+ }
+
+ for (int i = 0; i < infoList->conn_num; i++) {
+ if (info[i].handle == handle) {
+ quint64 converted;
+ convertAddress(info[i].bdaddr.b, converted);
+
+ return QBluetoothAddress(converted);
+ }
+ }
+
+ return QBluetoothAddress();
+}
+
+/*!
+ * Process all incoming HCI events. Function cannot process anything else but events.
+ */
+void HciManager::_q_readNotify()
+{
+
+ unsigned char buffer[HCI_MAX_EVENT_SIZE];
+ int size;
+
+ size = ::read(hciSocket, buffer, sizeof(buffer));
+ if (size < 0) {
+ if (errno != EAGAIN && errno != EINTR)
+ qCWarning(QT_BT_BLUEZ) << "Failed reading HCI events:" << qt_error_string(errno);
+
+ return;
+ }
+
+ const unsigned char *data = buffer;
+
+ // Not interested in anything but valid HCI events
+ if ((size < HCI_EVENT_HDR_SIZE + 1) || buffer[0] != HCI_EVENT_PKT)
+ return;
+
+ hci_event_hdr *header = (hci_event_hdr *)(&buffer[1]);
+
+ size = size - HCI_EVENT_HDR_SIZE - 1;
+ data = data + HCI_EVENT_HDR_SIZE + 1;
+
+ if (header->plen != size) {
+ qCWarning(QT_BT_BLUEZ) << "Invalid HCI event packet size";
+ return;
+ }
+
+ qCDebug(QT_BT_BLUEZ) << "HCI event triggered, type:" << hex << header->evt;
+
+ switch (header->evt) {
+ case EVT_ENCRYPT_CHANGE:
+ {
+ const evt_encrypt_change *event = (evt_encrypt_change *) data;
+ qCDebug(QT_BT_BLUEZ) << "HCI Encrypt change, status:"
+ << (event->status == 0 ? "Success" : "Failed")
+ << "handle:" << hex << event->handle
+ << "encrypt:" << event->encrypt;
+
+ QBluetoothAddress remoteDevice = addressForConnectionHandle(event->handle);
+ if (!remoteDevice.isNull())
+ emit encryptionChangedEvent(remoteDevice, event->status == 0);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/bluez/hcimanager_p.h b/src/bluetooth/bluez/hcimanager_p.h
new file mode 100644
index 00000000..3a923519
--- /dev/null
+++ b/src/bluetooth/bluez/hcimanager_p.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef HCIMANAGER_P_H
+#define HCIMANAGER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QObject>
+#include <QtCore/QSet>
+#include <QtCore/QSocketNotifier>
+#include <QtBluetooth/QBluetoothAddress>
+#include "bluez/bluez_data_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class HciManager : public QObject
+{
+ Q_OBJECT
+public:
+ enum HciEvent {
+ EncryptChangeEvent = EVT_ENCRYPT_CHANGE,
+ };
+
+ explicit HciManager(const QBluetoothAddress &deviceAdapter, QObject *parent = 0);
+ ~HciManager();
+
+ bool isValid() const;
+ bool monitorEvent(HciManager::HciEvent event);
+ void stopEvents();
+ QBluetoothAddress addressForConnectionHandle(quint16 handle) const;
+
+
+signals:
+ void encryptionChangedEvent(const QBluetoothAddress &address, bool wasSuccess);
+
+private slots:
+ void _q_readNotify();
+
+private:
+ int hciForAddress(const QBluetoothAddress &deviceAdapter);
+
+ int hciSocket;
+ int hciDev;
+ QSocketNotifier *notifier;
+ QSet<HciManager::HciEvent> runningEvents;
+};
+
+QT_END_NAMESPACE
+
+#endif // HCIMANAGER_P_H