From 3d87d02970237c9e7a8ef8d921702bcc755130c0 Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Fri, 12 Sep 2014 11:13:42 +0200 Subject: 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 --- src/bluetooth/bluez/bluez.pri | 6 +- src/bluetooth/bluez/bluez_data_p.h | 143 +++++++++++++++++++ src/bluetooth/bluez/hcimanager.cpp | 285 +++++++++++++++++++++++++++++++++++++ src/bluetooth/bluez/hcimanager_p.h | 90 ++++++++++++ 4 files changed, 522 insertions(+), 2 deletions(-) create mode 100644 src/bluetooth/bluez/hcimanager.cpp create mode 100644 src/bluetooth/bluez/hcimanager_p.h 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 #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 + +#include +#include +#include +#include + +#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 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 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 +#include +#include +#include +#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 runningEvents; +}; + +QT_END_NAMESPACE + +#endif // HCIMANAGER_P_H -- cgit v1.2.3