blob: 83d7e060c4d4b6897bc43ce8bbdf83653c85214d [file] [log] [blame]
/****************************************************************************
**
** 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