summaryrefslogtreecommitdiffstats
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)
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