diff options
Diffstat (limited to 'src/bluetooth/bluez/hcimanager.cpp')
-rw-r--r-- | src/bluetooth/bluez/hcimanager.cpp | 325 |
1 files changed, 300 insertions, 25 deletions
diff --git a/src/bluetooth/bluez/hcimanager.cpp b/src/bluetooth/bluez/hcimanager.cpp index 30511ae5..93bf941b 100644 --- a/src/bluetooth/bluez/hcimanager.cpp +++ b/src/bluetooth/bluez/hcimanager.cpp @@ -1,32 +1,38 @@ /**************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd. -** Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> -** Contact: http://www.qt.io/licensing/ +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Javier S. Pedro <maemo@javispedro.com> +** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. ** -** $QT_BEGIN_LICENSE:LGPL21$ +** $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 http://www.qt.io/terms-conditions. For further -** information use the contact form at http://www.qt.io/contact-us. +** 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 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. +** 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. ** -** As a special exception, The Qt Company gives you certain additional -** rights. These rights are described in The Qt Company LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** 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$ ** @@ -35,13 +41,16 @@ #include "hcimanager_p.h" #include "qbluetoothsocket_p.h" +#include "qlowenergyconnectionparameters.h" -#include <QtCore/QLoggingCategory> +#include <QtCore/qloggingcategory.h> +#include <cstring> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> +#include <sys/uio.h> #include <unistd.h> #define HCIGETCONNLIST _IOR('H', 212, int) @@ -136,7 +145,7 @@ int HciManager::hciForAddress(const QBluetoothAddress &deviceAdapter) int result = memcmp(&adapter, &devInfo.bdaddr, sizeof(bdaddr_t)); if (result == 0 || deviceAdapter.isNull()) // addresses match - return devRequest->dev_id; + return devInfo.dev_id; } return -1; @@ -174,6 +183,59 @@ bool HciManager::monitorEvent(HciManager::HciEvent event) 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<uint8_t>(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<char *>(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 */ @@ -226,13 +288,106 @@ QBluetoothAddress HciManager::addressForConnectionHandle(quint16 handle) const return QBluetoothAddress(); } +quint16 forceIntervalIntoRange(double connectionInterval) +{ + return qMin<double>(qMax<double>(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<quint16>(0, qMin<quint16>(params.latency(), 499)); + data.slaveLatency = qToLittleEndian(latency); + const quint16 timeout + = qMax<quint16>(100, qMin<quint16>(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<char *>(&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[HCI_MAX_EVENT_SIZE]; + unsigned char buffer[qMax<int>(HCI_MAX_EVENT_SIZE, sizeof(AclData))]; int size; size = ::read(hciSocket, buffer, sizeof(buffer)); @@ -243,16 +398,29 @@ void HciManager::_q_readNotify() return; } - const unsigned char *data = buffer; + 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]; + } +} - // Not interested in anything but valid HCI events - if ((size < HCI_EVENT_HDR_SIZE + 1) || buffer[0] != HCI_EVENT_PKT) +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 *)(&buffer[1]); + hci_event_hdr *header = (hci_event_hdr *) data; - size = size - HCI_EVENT_HDR_SIZE - 1; - data = data + HCI_EVENT_HDR_SIZE + 1; + size -= HCI_EVENT_HDR_SIZE; + data += HCI_EVENT_HDR_SIZE; if (header->plen != size) { qCWarning(QT_BT_BLUEZ) << "Invalid HCI event packet size"; @@ -275,10 +443,117 @@ void HciManager::_q_readNotify() emit encryptionChangedEvent(remoteDevice, event->status == 0); } break; + case EVT_CMD_COMPLETE: { + auto * const event = reinterpret_cast<const evt_cmd_complete *>(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<int>(sizeof *event)); + const quint8 status = data[sizeof *event]; + const auto additionalData = QByteArray(reinterpret_cast<const char *>(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<AclData *>(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<const L2CapHeader*>(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<const ConnectionUpdateData *>(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 |