/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2016 Javier S. Pedro ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** 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 The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/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 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "hcimanager_p.h" #include "qbluetoothsocketbase_p.h" #include "qlowenergyconnectionparameters.h" #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) HciManager::HciManager(const QBluetoothAddress& deviceAdapter, QObject *parent) : QObject(parent), hciSocket(-1), hciDev(-1) { 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 = nullptr; struct hci_dev_list_req *devRequestList = nullptr; 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 devInfo.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 // TODO runningEvents does not seem to be used 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; } bool HciManager::monitorAclPackets() { if (!isValid()) return false; 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_ACL_PKT, &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; } bool HciManager::sendCommand(OpCodeGroupField ogf, OpCodeCommandField ocf, const QByteArray ¶meters) { qCDebug(QT_BT_BLUEZ) << "sending command; ogf:" << ogf << "ocf:" << ocf; quint8 packetType = HCI_COMMAND_PKT; hci_command_hdr command = { opCodePack(ogf, ocf), static_cast(parameters.count()) }; static_assert(sizeof command == 3, "unexpected struct size"); struct iovec iv[3]; iv[0].iov_base = &packetType; iv[0].iov_len = 1; iv[1].iov_base = &command; iv[1].iov_len = sizeof command; int ivn = 2; if (!parameters.isEmpty()) { iv[2].iov_base = const_cast(parameters.constData()); // const_cast is safe, since iov_base will not get modified. iv[2].iov_len = parameters.count(); ++ivn; } while (writev(hciSocket, iv, ivn) < 0) { if (errno == EAGAIN || errno == EINTR) continue; qCDebug(QT_BT_BLUEZ()) << "hci command failure:" << strerror(errno); return false; } qCDebug(QT_BT_BLUEZ) << "command sent successfully"; 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) return QBluetoothAddress(convertAddress(info[i].bdaddr.b)); } return QBluetoothAddress(); } QVector HciManager::activeLowEnergyConnections() const { if (!isValid()) return QVector(); 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 QVector(); 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 QVector(); } QVector activeLowEnergyHandles; for (int i = 0; i < infoList->conn_num; i++) { switch (info[i].type) { case SCO_LINK: case ACL_LINK: case ESCO_LINK: continue; case LE_LINK: activeLowEnergyHandles.append(info[i].handle); break; default: qCWarning(QT_BT_BLUEZ) << "Unknown active connection type:" << hex << info[i].type; break; } } return activeLowEnergyHandles; } quint16 forceIntervalIntoRange(double connectionInterval) { return qMin(qMax(7.5, connectionInterval), 4000) / 1.25; } struct ConnectionUpdateData { quint16 minInterval; quint16 maxInterval; quint16 slaveLatency; quint16 timeout; }; ConnectionUpdateData connectionUpdateData(const QLowEnergyConnectionParameters ¶ms) { ConnectionUpdateData data; const quint16 minInterval = forceIntervalIntoRange(params.minimumInterval()); const quint16 maxInterval = forceIntervalIntoRange(params.maximumInterval()); data.minInterval = qToLittleEndian(minInterval); data.maxInterval = qToLittleEndian(maxInterval); const quint16 latency = qMax(0, qMin(params.latency(), 499)); data.slaveLatency = qToLittleEndian(latency); const quint16 timeout = qMax(100, qMin(32000, params.supervisionTimeout())) / 10; data.timeout = qToLittleEndian(timeout); return data; } bool HciManager::sendConnectionUpdateCommand(quint16 handle, const QLowEnergyConnectionParameters ¶ms) { struct CommandParams { quint16 handle; ConnectionUpdateData data; quint16 minCeLength; quint16 maxCeLength; } commandParams; commandParams.handle = qToLittleEndian(handle); commandParams.data = connectionUpdateData(params); commandParams.minCeLength = 0; commandParams.maxCeLength = qToLittleEndian(quint16(0xffff)); const QByteArray data = QByteArray::fromRawData(reinterpret_cast(&commandParams), sizeof commandParams); return sendCommand(OgfLinkControl, OcfLeConnectionUpdate, data); } bool HciManager::sendConnectionParameterUpdateRequest(quint16 handle, const QLowEnergyConnectionParameters ¶ms) { ConnectionUpdateData connUpdateData = connectionUpdateData(params); // Vol 3, part A, 4 struct SignalingPacket { quint8 code; quint8 identifier; quint16 length; } signalingPacket; signalingPacket.code = 0x12; signalingPacket.identifier = ++sigPacketIdentifier; const quint16 sigPacketLen = sizeof connUpdateData; signalingPacket.length = qToLittleEndian(sigPacketLen); L2CapHeader l2CapHeader; const quint16 l2CapHeaderLen = sizeof signalingPacket + sigPacketLen; l2CapHeader.length = qToLittleEndian(l2CapHeaderLen); l2CapHeader.channelId = qToLittleEndian(quint16(SIGNALING_CHANNEL_ID)); // Vol 2, part E, 5.4.2 AclData aclData; aclData.handle = qToLittleEndian(handle); // Works because the next two values are zero. aclData.pbFlag = 0; aclData.bcFlag = 0; aclData.dataLen = qToLittleEndian(quint16(sizeof l2CapHeader + l2CapHeaderLen)); struct iovec iv[5]; quint8 packetType = HCI_ACL_PKT; iv[0].iov_base = &packetType; iv[0].iov_len = 1; iv[1].iov_base = &aclData; iv[1].iov_len = sizeof aclData; iv[2].iov_base = &l2CapHeader; iv[2].iov_len = sizeof l2CapHeader; iv[3].iov_base = &signalingPacket; iv[3].iov_len = sizeof signalingPacket; iv[4].iov_base = &connUpdateData; iv[4].iov_len = sizeof connUpdateData; while (writev(hciSocket, iv, sizeof iv / sizeof *iv) < 0) { if (errno == EAGAIN || errno == EINTR) continue; qCDebug(QT_BT_BLUEZ()) << "failure writing HCI ACL packet:" << strerror(errno); return false; } qCDebug(QT_BT_BLUEZ) << "Connection Update Request packet sent successfully"; return true; } /*! * Process all incoming HCI events. Function cannot process anything else but events. */ void HciManager::_q_readNotify() { unsigned char buffer[qMax(HCI_MAX_EVENT_SIZE, sizeof(AclData))]; 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; } switch (buffer[0]) { case HCI_EVENT_PKT: handleHciEventPacket(buffer + 1, size - 1); break; case HCI_ACL_PKT: handleHciAclPacket(buffer + 1, size - 1); break; default: qCWarning(QT_BT_BLUEZ) << "Ignoring unexpected HCI packet type" << buffer[0]; } } void HciManager::handleHciEventPacket(const quint8 *data, int size) { if (size < HCI_EVENT_HDR_SIZE) { qCWarning(QT_BT_BLUEZ) << "Unexpected HCI event packet size:" << size; return; } hci_event_hdr *header = (hci_event_hdr *) data; size -= HCI_EVENT_HDR_SIZE; data += HCI_EVENT_HDR_SIZE; 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; case EVT_CMD_COMPLETE: { auto * const event = reinterpret_cast(data); static_assert(sizeof *event == 3, "unexpected struct size"); // There is always a status byte right after the generic structure. Q_ASSERT(size > static_cast(sizeof *event)); const quint8 status = data[sizeof *event]; const auto additionalData = QByteArray(reinterpret_cast(data) + sizeof *event + 1, size - sizeof *event - 1); emit commandCompleted(event->opcode, status, additionalData); } break; case LeMetaEvent: handleLeMetaEvent(data); break; default: break; } } void HciManager::handleHciAclPacket(const quint8 *data, int size) { if (size < int(sizeof(AclData))) { qCWarning(QT_BT_BLUEZ) << "Unexpected HCI ACL packet size"; return; } quint16 rawAclData[sizeof(AclData) / sizeof(quint16)]; rawAclData[0] = bt_get_le16(data); rawAclData[1] = bt_get_le16(data + sizeof(quint16)); const AclData *aclData = reinterpret_cast(rawAclData); data += sizeof *aclData; size -= sizeof *aclData; // Consider only directed, complete messages. if ((aclData->pbFlag != 0 && aclData->pbFlag != 2) || aclData->bcFlag != 0) return; if (size < aclData->dataLen) { qCWarning(QT_BT_BLUEZ) << "HCI ACL packet data size" << size << "is smaller than specified size" << aclData->dataLen; return; } // qCDebug(QT_BT_BLUEZ) << "handle:" << aclData->handle << "PB:" << aclData->pbFlag // << "BC:" << aclData->bcFlag << "data len:" << aclData->dataLen; if (size < int(sizeof(L2CapHeader))) { qCWarning(QT_BT_BLUEZ) << "Unexpected HCI ACL packet size"; return; } L2CapHeader l2CapHeader = *reinterpret_cast(data); l2CapHeader.channelId = qFromLittleEndian(l2CapHeader.channelId); l2CapHeader.length = qFromLittleEndian(l2CapHeader.length); data += sizeof l2CapHeader; size -= sizeof l2CapHeader; if (size < l2CapHeader.length) { qCWarning(QT_BT_BLUEZ) << "L2Cap payload size" << size << "is smaller than specified size" << l2CapHeader.length; return; } // qCDebug(QT_BT_BLUEZ) << "l2cap channel id:" << l2CapHeader.channelId // << "payload length:" << l2CapHeader.length; if (l2CapHeader.channelId != SECURITY_CHANNEL_ID) return; if (*data != 0xa) // "Signing Information". Spec v4.2, Vol 3, Part H, 3.6.6 return; if (size != 17) { qCWarning(QT_BT_BLUEZ) << "Unexpected key size" << size << "in Signing Information packet"; return; } quint128 csrk; memcpy(&csrk, data + 1, sizeof csrk); const bool isRemoteKey = aclData->pbFlag == 2; emit signatureResolvingKeyReceived(aclData->handle, isRemoteKey, csrk); } void HciManager::handleLeMetaEvent(const quint8 *data) { // Spec v4.2, Vol 2, part E, 7.7.65ff switch (*data) { case 0x1: { const quint16 handle = bt_get_le16(data + 2); emit connectionComplete(handle); break; } case 0x3: { // TODO: From little endian! struct ConnectionUpdateData { quint8 status; quint16 handle; quint16 interval; quint16 latency; quint16 timeout; } __attribute((packed)); const auto * const updateData = reinterpret_cast(data + 1); if (updateData->status == 0) { QLowEnergyConnectionParameters params; const double interval = qFromLittleEndian(updateData->interval) * 1.25; params.setIntervalRange(interval, interval); params.setLatency(qFromLittleEndian(updateData->latency)); params.setSupervisionTimeout(qFromLittleEndian(updateData->timeout) * 10); emit connectionUpdate(qFromLittleEndian(updateData->handle), params); } break; } default: break; } } QT_END_NAMESPACE