Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // Copyright (C) 2016 Javier S. Pedro <maemo@javispedro.com>
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:critical reason:data-parser
++
++ #include "hcimanager_p.h"
++
++ #include "qbluetoothsocketbase_p.h"
++ #include "qlowenergyconnectionparameters.h"
++
++ #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>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
++
++ HciManager::HciManager(const QBluetoothAddress& deviceAdapter) :
++ QObject(nullptr), 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(QSocketDescriptor)), 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<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 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(static_cast<int>(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(QBluezConst::OpCodeGroupField ogf, QBluezConst::OpCodeCommandField ocf, const QByteArray &parameters)
++ {
++ 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.size())
++ };
++ 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.size();
++ ++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<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)
++ return QBluetoothAddress(convertAddress(info[i].bdaddr.b));
++ }
++
++ return QBluetoothAddress();
++ }
++
++ QList<quint16> HciManager::activeLowEnergyConnections() const
++ {
++ if (!isValid())
++ return QList<quint16>();
++
++ 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 QList<quint16>();
++
++ 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 QList<quint16>();
++ }
++
++ QList<quint16> 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:" << Qt::hex << info[i].type;
++ break;
++ }
++ }
++
++ return activeLowEnergyHandles;
++ }
++
++ 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 &params)
++ {
++ 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 &params)
++ {
++ 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(QBluezConst::OgfLinkControl, QBluezConst::OcfLeConnectionUpdate, data);
++ }
++
++ bool HciManager::sendConnectionParameterUpdateRequest(quint16 handle,
++ const QLowEnergyConnectionParameters &params)
++ {
++ 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<int>(HCI_MAX_EVENT_SIZE, sizeof(AclData))];
++
++ const auto 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;
++ }
++
++ if (size == 0) {
++ qCWarning(QT_BT_BLUEZ) << "No data when trying to read HCI events";
++ 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:" << (HciManager::HciEvent)header->evt
++ << "type code:" << Qt::hex << header->evt;
++
++ switch ((HciManager::HciEvent)header->evt) {
++ case HciEvent::EVT_ENCRYPT_CHANGE: {
++ if (size < static_cast<int>(sizeof(evt_encrypt_change))) {
++ qCWarning(QT_BT_BLUEZ) << "EVT_ENCRYPT_CHANGE: unexpected event packet size";
++ return;
++ }
++ const evt_encrypt_change *event = reinterpret_cast<const evt_encrypt_change *>(data);
++ qCDebug(QT_BT_BLUEZ) << "HCI Encrypt change, status:"
++ << (event->status == 0 ? "Success" : "Failed")
++ << "handle:" << Qt::hex << event->handle
++ << "encrypt:" << event->encrypt;
++
++ QBluetoothAddress remoteDevice = addressForConnectionHandle(event->handle);
++ if (!remoteDevice.isNull())
++ emit encryptionChangedEvent(remoteDevice, event->status == 0);
++ } break;
++ case HciEvent::EVT_CMD_COMPLETE: {
++ constexpr auto eventSize = sizeof(evt_cmd_complete);
++ static_assert(eventSize == 3, "unexpected struct size");
++
++ // There is always a status byte right after the generic structure,
++ // that's why we need +1 here
++ if (size < static_cast<int>(eventSize + 1)) {
++ qCWarning(QT_BT_BLUEZ) << "EVT_CMD_COMPLETE: unexpected event packet size";
++ return;
++ }
++ auto * const event = reinterpret_cast<const evt_cmd_complete *>(data);
++
++ // Status byte is right after then event struct
++ const quint8 status = data[eventSize];
++ const auto additionalData = QByteArray(reinterpret_cast<const char *>(data)
++ + eventSize + 1, size - eventSize - 1);
++ emit commandCompleted(event->opcode, status, additionalData);
++ } break;
++ case HciEvent::EVT_LE_META_EVENT:
++ handleLeMetaEvent(data, size);
++ 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 (l2CapHeader.length != 17) {
++ qCWarning(QT_BT_BLUEZ) << "Unexpected length of the Signing Information packet:"
++ << l2CapHeader.length;
++ return;
++ }
++ if (*data != 0xa) // "Signing Information". Spec v4.2, Vol 3, Part H, 3.6.6
++ return;
++ BluezUint128 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, int size)
++ {
++ if (size == 0) {
++ qCWarning(QT_BT_BLUEZ) << "LE Meta Event: not enough bytes to extract event code";
++ return;
++ }
++
++ // Spec v5.3, Vol 4, part E, 7.7.65.*
++ switch (*data) {
++ case 0x1: // HCI_LE_Connection_Complete
++ case 0xA: // HCI_LE_Enhanced_Connection_Complete
++ {
++ // subevent code (1 byte) + status (1 byte) + handle (2 bytes)
++ if (size < 4) {
++ qCWarning(QT_BT_BLUEZ) << "LE Meta Event: not enough bytes to parse "
++ "Connection Complete event";
++ return;
++ }
++ // Skipping status here. TODO: should we process it?
++ 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));
++ // +1 byte for a status!
++ if (size < static_cast<int>(sizeof(ConnectionUpdateData) + 1)) {
++ qCWarning(QT_BT_BLUEZ) << "LE Meta Event: not enough bytes to parse "
++ "Connection Update Complete event";
++ return;
++ }
++
++ 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
++
++ #include "moc_hcimanager_p.cpp"