| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** 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 "osx/osxbtsocketlistener_p.h" |
| #include "qbluetoothserver_p.h" |
| |
| // The order is important: a workround for |
| // a private header included by private header |
| // (incorrectly handled dependencies). |
| #include "qbluetoothsocketbase_p.h" |
| #include "qbluetoothsocket_osx_p.h" |
| |
| #include "qbluetoothlocaldevice.h" |
| #include "osx/osxbtutility_p.h" |
| #include "osx/osxbluetooth_p.h" |
| #include "qbluetoothserver.h" |
| #include "qbluetoothsocket.h" |
| |
| #include <QtCore/qloggingcategory.h> |
| #include <QtCore/qscopedpointer.h> |
| #include <QtCore/qvariant.h> |
| #include <QtCore/qglobal.h> |
| #include <QtCore/qmutex.h> |
| |
| #include <Foundation/Foundation.h> |
| |
| #include <limits> |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace { |
| |
| using DarwinBluetooth::RetainPolicy; |
| using ServiceInfo = QBluetoothServiceInfo; |
| using ObjCListener = QT_MANGLE_NAMESPACE(OSXBTSocketListener); |
| |
| QMap<quint16, QBluetoothServerPrivate *> &busyPSMs() |
| { |
| static QMap<quint16, QBluetoothServerPrivate *> psms; |
| return psms; |
| } |
| |
| QMap<quint16, QBluetoothServerPrivate *> &busyChannels() |
| { |
| static QMap<quint16, QBluetoothServerPrivate *> channels; |
| return channels; |
| } |
| |
| typedef QMap<quint16, QBluetoothServerPrivate *>::iterator ServerMapIterator; |
| |
| } |
| |
| |
| QBluetoothServerPrivate::QBluetoothServerPrivate(ServiceInfo::Protocol type, |
| QBluetoothServer *parent) |
| : socket(nullptr), |
| maxPendingConnections(1), |
| securityFlags(QBluetooth::NoSecurity), |
| serverType(type), |
| q_ptr(parent), |
| m_lastError(QBluetoothServer::NoError), |
| port(0) |
| { |
| if (serverType == ServiceInfo::UnknownProtocol) |
| qCWarning(QT_BT_OSX) << "unknown protocol"; |
| } |
| |
| QBluetoothServerPrivate::~QBluetoothServerPrivate() |
| { |
| const QMutexLocker lock(&channelMapMutex()); |
| unregisterServer(this); |
| } |
| |
| bool QBluetoothServerPrivate::startListener(quint16 realPort) |
| { |
| Q_ASSERT_X(realPort, Q_FUNC_INFO, "invalid port"); |
| |
| if (serverType == ServiceInfo::UnknownProtocol) { |
| qCWarning(QT_BT_OSX) << "invalid protocol"; |
| return false; |
| } |
| |
| if (!listener) { |
| listener.reset([[ObjCListener alloc] initWithListener:this], |
| RetainPolicy::noInitialRetain); |
| } |
| |
| bool result = false; |
| if (serverType == ServiceInfo::RfcommProtocol) |
| result = [listener.getAs<ObjCListener>() listenRFCOMMConnectionsWithChannelID:realPort]; |
| else |
| result = [listener.getAs<ObjCListener>() listenL2CAPConnectionsWithPSM:realPort]; |
| |
| if (!result) |
| listener.reset(); |
| |
| return result; |
| } |
| |
| bool QBluetoothServerPrivate::isListening() const |
| { |
| if (serverType == ServiceInfo::UnknownProtocol) |
| return false; |
| |
| const QMutexLocker lock(&QBluetoothServerPrivate::channelMapMutex()); |
| return QBluetoothServerPrivate::registeredServer(q_ptr->serverPort(), serverType); |
| } |
| |
| void QBluetoothServerPrivate::stopListener() |
| { |
| listener.reset(); |
| } |
| |
| void QBluetoothServerPrivate::openNotifyRFCOMM(void *generic) |
| { |
| auto channel = static_cast<IOBluetoothRFCOMMChannel *>(generic); |
| |
| Q_ASSERT_X(listener, Q_FUNC_INFO, "invalid listener (nil)"); |
| Q_ASSERT_X(channel, Q_FUNC_INFO, "invalid channel (nil)"); |
| Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); |
| |
| PendingConnection newConnection(channel, RetainPolicy::doInitialRetain); |
| pendingConnections.append(newConnection); |
| |
| emit q_ptr->newConnection(); |
| } |
| |
| void QBluetoothServerPrivate::openNotifyL2CAP(void *generic) |
| { |
| auto channel = static_cast<IOBluetoothL2CAPChannel *>(generic); |
| |
| Q_ASSERT_X(listener, Q_FUNC_INFO, "invalid listener (nil)"); |
| Q_ASSERT_X(channel, Q_FUNC_INFO, "invalid channel (nil)"); |
| Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); |
| |
| PendingConnection newConnection(channel, RetainPolicy::doInitialRetain); |
| pendingConnections.append(newConnection); |
| |
| emit q_ptr->newConnection(); |
| } |
| |
| QMutex &QBluetoothServerPrivate::channelMapMutex() |
| { |
| static QMutex mutex; |
| return mutex; |
| } |
| |
| bool QBluetoothServerPrivate::channelIsBusy(quint16 channelID) |
| { |
| // External lock is required. |
| return busyChannels().contains(channelID); |
| } |
| |
| quint16 QBluetoothServerPrivate::findFreeChannel() |
| { |
| // External lock is required. |
| for (quint16 i = 1; i <= 30; ++i) { |
| if (!busyChannels().contains(i)) |
| return i; |
| } |
| |
| return 0; //Invalid port. |
| } |
| |
| bool QBluetoothServerPrivate::psmIsBusy(quint16 psm) |
| { |
| // External lock is required. |
| return busyPSMs().contains(psm); |
| } |
| |
| quint16 QBluetoothServerPrivate::findFreePSM() |
| { |
| // External lock is required. |
| for (quint16 i = 1, e = std::numeric_limits<qint16>::max(); i < e; i += 2) { |
| if (!psmIsBusy(i)) |
| return i; |
| } |
| |
| return 0; // Invalid PSM. |
| } |
| |
| void QBluetoothServerPrivate::registerServer(QBluetoothServerPrivate *server, quint16 port) |
| { |
| // External lock is required + port must be free. |
| Q_ASSERT_X(server, Q_FUNC_INFO, "invalid server (null)"); |
| |
| const ServiceInfo::Protocol type = server->serverType; |
| if (type == ServiceInfo::RfcommProtocol) { |
| Q_ASSERT_X(!channelIsBusy(port), Q_FUNC_INFO, "port is busy"); |
| busyChannels()[port] = server; |
| } else if (type == ServiceInfo::L2capProtocol) { |
| Q_ASSERT_X(!psmIsBusy(port), Q_FUNC_INFO, "port is busy"); |
| busyPSMs()[port] = server; |
| } else { |
| qCWarning(QT_BT_OSX) << "can not register a server " |
| "with unknown protocol type"; |
| } |
| } |
| |
| QBluetoothServerPrivate *QBluetoothServerPrivate::registeredServer(quint16 port, QBluetoothServiceInfo::Protocol protocol) |
| { |
| // Eternal lock is required. |
| if (protocol == ServiceInfo::RfcommProtocol) { |
| ServerMapIterator it = busyChannels().find(port); |
| if (it != busyChannels().end()) |
| return it.value(); |
| } else if (protocol == ServiceInfo::L2capProtocol) { |
| ServerMapIterator it = busyPSMs().find(port); |
| if (it != busyPSMs().end()) |
| return it.value(); |
| } else { |
| qCWarning(QT_BT_OSX) << "invalid protocol"; |
| } |
| |
| return nullptr; |
| } |
| |
| void QBluetoothServerPrivate::unregisterServer(QBluetoothServerPrivate *server) |
| { |
| // External lock is required. |
| const ServiceInfo::Protocol type = server->serverType; |
| const quint16 port = server->port; |
| |
| if (type == ServiceInfo::RfcommProtocol) { |
| ServerMapIterator it = busyChannels().find(port); |
| if (it != busyChannels().end()) { |
| busyChannels().erase(it); |
| } else { |
| qCWarning(QT_BT_OSX) << "server is not registered"; |
| } |
| } else if (type == ServiceInfo::L2capProtocol) { |
| ServerMapIterator it = busyPSMs().find(port); |
| if (it != busyPSMs().end()) { |
| busyPSMs().erase(it); |
| } else { |
| qCWarning(QT_BT_OSX) << "server is not registered"; |
| } |
| } else { |
| qCWarning(QT_BT_OSX) << "invalid protocol"; |
| } |
| } |
| |
| void QBluetoothServer::close() |
| { |
| d_ptr->listener.reset(); |
| |
| // Needs a lock :( |
| const QMutexLocker lock(&d_ptr->channelMapMutex()); |
| d_ptr->unregisterServer(d_ptr); |
| d_ptr->port = 0; |
| } |
| |
| bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) |
| { |
| OSXBluetooth::qt_test_iobluetooth_runloop(); |
| |
| if (d_ptr->listener) { |
| qCWarning(QT_BT_OSX) << "already in listen mode, close server first"; |
| return false; |
| } |
| |
| const QBluetoothLocalDevice device(address); |
| if (!device.isValid()) { |
| qCWarning(QT_BT_OSX) << "device does not support Bluetooth or" |
| << address.toString() |
| << "is not a valid local adapter"; |
| d_ptr->m_lastError = UnknownError; |
| emit error(UnknownError); |
| return false; |
| } |
| |
| const QBluetoothLocalDevice::HostMode hostMode = device.hostMode(); |
| if (hostMode == QBluetoothLocalDevice::HostPoweredOff) { |
| qCWarning(QT_BT_OSX) << "Bluetooth device is powered off"; |
| d_ptr->m_lastError = PoweredOffError; |
| emit error(PoweredOffError); |
| return false; |
| } |
| |
| const ServiceInfo::Protocol type = d_ptr->serverType; |
| |
| if (type == ServiceInfo::UnknownProtocol) { |
| qCWarning(QT_BT_OSX) << "invalid protocol"; |
| d_ptr->m_lastError = UnsupportedProtocolError; |
| emit error(d_ptr->m_lastError); |
| return false; |
| } |
| |
| d_ptr->m_lastError = QBluetoothServer::NoError; |
| |
| // Now we have to register a (fake) port, doing a proper (?) lock. |
| const QMutexLocker lock(&d_ptr->channelMapMutex()); |
| |
| if (port) { |
| if (type == ServiceInfo::RfcommProtocol) { |
| if (d_ptr->channelIsBusy(port)) { |
| qCWarning(QT_BT_OSX) << "server port:" << port |
| << "already registered"; |
| d_ptr->m_lastError = ServiceAlreadyRegisteredError; |
| } |
| } else { |
| if (d_ptr->psmIsBusy(port)) { |
| qCWarning(QT_BT_OSX) << "server port:" << port |
| << "already registered"; |
| d_ptr->m_lastError = ServiceAlreadyRegisteredError; |
| } |
| } |
| } else { |
| type == ServiceInfo::RfcommProtocol ? port = d_ptr->findFreeChannel() |
| : port = d_ptr->findFreePSM(); |
| } |
| |
| if (d_ptr->m_lastError != QBluetoothServer::NoError) { |
| emit error(d_ptr->m_lastError); |
| return false; |
| } |
| |
| if (!port) { |
| qCWarning(QT_BT_OSX) << "all ports are busy"; |
| d_ptr->m_lastError = ServiceAlreadyRegisteredError; |
| emit error(d_ptr->m_lastError); |
| return false; |
| } |
| |
| // It's a fake port, the real one will be different |
| // (provided after a service was registered). |
| d_ptr->port = port; |
| d_ptr->registerServer(d_ptr, port); |
| d_ptr->listener.reset([[ObjCListener alloc] initWithListener:d_ptr], |
| RetainPolicy::noInitialRetain); |
| |
| return true; |
| } |
| |
| void QBluetoothServer::setMaxPendingConnections(int numConnections) |
| { |
| d_ptr->maxPendingConnections = numConnections; |
| } |
| |
| bool QBluetoothServer::hasPendingConnections() const |
| { |
| return d_ptr->pendingConnections.size(); |
| } |
| |
| QBluetoothSocket *QBluetoothServer::nextPendingConnection() |
| { |
| if (!d_ptr->pendingConnections.size()) |
| return nullptr; |
| |
| QScopedPointer<QBluetoothSocket> newSocket(new QBluetoothSocket); |
| QBluetoothServerPrivate::PendingConnection channel(d_ptr->pendingConnections.front()); |
| |
| // Remove it even if we have some errors below. |
| d_ptr->pendingConnections.pop_front(); |
| |
| if (d_ptr->serverType == ServiceInfo::RfcommProtocol) { |
| if (!static_cast<QBluetoothSocketPrivate *>(newSocket->d_ptr)->setRFCOMChannel(channel.getAs<IOBluetoothRFCOMMChannel>())) |
| return nullptr; |
| } else { |
| if (!static_cast<QBluetoothSocketPrivate *>(newSocket->d_ptr)->setL2CAPChannel(channel.getAs<IOBluetoothL2CAPChannel>())) |
| return nullptr; |
| } |
| |
| return newSocket.take(); |
| } |
| |
| QBluetoothAddress QBluetoothServer::serverAddress() const |
| { |
| return QBluetoothLocalDevice().address(); |
| } |
| |
| quint16 QBluetoothServer::serverPort() const |
| { |
| return d_ptr->port; |
| } |
| |
| void QBluetoothServer::setSecurityFlags(QBluetooth::SecurityFlags security) |
| { |
| Q_UNUSED(security) |
| Q_UNIMPLEMENTED(); |
| } |
| |
| QBluetooth::SecurityFlags QBluetoothServer::securityFlags() const |
| { |
| Q_UNIMPLEMENTED(); |
| return QBluetooth::NoSecurity; |
| } |
| |
| QT_END_NAMESPACE |