diff options
Diffstat (limited to 'src/bluetooth/qbluetoothserver_osx.mm')
-rw-r--r-- | src/bluetooth/qbluetoothserver_osx.mm | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/src/bluetooth/qbluetoothserver_osx.mm b/src/bluetooth/qbluetoothserver_osx.mm new file mode 100644 index 00000000..412fb28c --- /dev/null +++ b/src/bluetooth/qbluetoothserver_osx.mm @@ -0,0 +1,501 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt 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 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "osx/osxbtsocketlistener_p.h" +#include "qbluetoothserver_osx_p.h" +#include "qbluetoothsocket_osx_p.h" +#include "qbluetoothlocaldevice.h" +#include "osx/osxbtutility_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> + +// Import, since Obj-C headers do not have inclusion guards. +#import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h> +#import <IOBluetooth/objc/IOBluetoothL2CAPChannel.h> + +#include <limits> + +QT_BEGIN_NAMESPACE + +namespace { + +typedef QBluetoothServiceInfo QSInfo; + +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(QSInfo::Protocol type, QBluetoothServer *q) + : serverType(type), + q_ptr(q), + lastError(QBluetoothServer::NoError), + port(0), + maxPendingConnections(1) +{ + Q_ASSERT_X(q_ptr, "QBluetoothServerPrivate", "invalid q_ptr (null)"); + if (serverType == QSInfo::UnknownProtocol) { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::QBluetoothServerPrivate(), " + "unknown protocol"; + } +} + +QBluetoothServerPrivate::~QBluetoothServerPrivate() +{ + // Actually, not good, but lock must be acquired. + // TODO: test this. + const QMutexLocker lock(&channelMapMutex()); + unregisterServer(this); +} + +void QBluetoothServerPrivate::_q_newConnection() +{ + // Noop, we have openNotify for this. +} + +bool QBluetoothServerPrivate::startListener(quint16 realPort) +{ + Q_ASSERT_X(realPort, "startListener", "invalid port"); + + if (serverType == QSInfo::UnknownProtocol) { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::startListener(), " + "invalid protocol"; + return false; + } + + if (!listener) + listener.reset([[ObjCListener alloc] initWithListener:this]); + + bool result = false; + if (serverType == QSInfo::RfcommProtocol) + result = [listener listenRFCOMMConnectionsWithChannelID:realPort]; + else + result = [listener listenL2CAPConnectionsWithPSM:realPort]; + + if (!result) + listener.reset(nil); + + return result; +} + +void QBluetoothServerPrivate::stopListener() +{ + listener.reset(nil); +} + +void QBluetoothServerPrivate::openNotify(IOBluetoothRFCOMMChannel *channel) +{ + Q_ASSERT_X(listener, "openNotify", "invalid listener (nil)"); + Q_ASSERT_X(channel, "openNotify", "invalid channel (nil)"); + Q_ASSERT_X(q_ptr, "openNotify", "invalid q_ptr (null)"); + + PendingConnection newConnection(channel, true); + pendingConnections.append(newConnection); + + emit q_ptr->newConnection(); +} + +void QBluetoothServerPrivate::openNotify(IOBluetoothL2CAPChannel *channel) +{ + Q_ASSERT_X(listener, "openNotify", "invalid listener (nil)"); + Q_ASSERT_X(channel, "openNotify", "invalid channel (nil)"); + Q_ASSERT_X(q_ptr, "openNotify", "invalid q_ptr (null)"); + + PendingConnection newConnection(channel, true); + 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, "registerServer", "invalid server (null)"); + + const QSInfo::Protocol type = server->serverType; + if (type == QSInfo::RfcommProtocol) { + Q_ASSERT_X(!channelIsBusy(port), "registerServer", + "port is busy"); + busyChannels()[port] = server; + } else if (type == QSInfo::L2capProtocol) { + Q_ASSERT_X(!psmIsBusy(port), "registerServer", + "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 == QSInfo::RfcommProtocol) { + ServerMapIterator it = busyChannels().find(port); + if (it != busyChannels().end()) + return it.value(); + } else if (protocol == QSInfo::L2capProtocol) { + ServerMapIterator it = busyPSMs().find(port); + if (it != busyPSMs().end()) + return it.value(); + } else { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::registeredServer(), " + "invalid protocol"; + } + + return Q_NULLPTR; +} + +void QBluetoothServerPrivate::unregisterServer(QBluetoothServerPrivate *server) +{ + // External lock is required. + const QSInfo::Protocol type = server->serverType; + const quint16 port = server->port; + + if (type == QSInfo::RfcommProtocol) { + ServerMapIterator it = busyChannels().find(port); + if (it != busyChannels().end()) { + busyChannels().erase(it); + } else { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::unregisterServer(), " + "server is not registered"; + } + } else if (type == QSInfo::L2capProtocol) { + ServerMapIterator it = busyPSMs().find(port); + if (it != busyPSMs().end()) { + busyPSMs().erase(it); + } else { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::unregisterServer(), " + "server is not registered"; + } + } else { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::unregisterServer(), " + "invalid protocol"; + } +} + + +QBluetoothServer::QBluetoothServer(QSInfo::Protocol serverType, QObject *parent) + : QObject(parent), + d_ptr(new QBluetoothServerPrivate(serverType, this)) +{ +} + +QBluetoothServer::~QBluetoothServer() +{ + delete d_ptr; +} + +void QBluetoothServer::close() +{ + d_ptr->listener.reset(nil); + + // Needs a lock :( + const QMutexLocker lock(&d_ptr->channelMapMutex()); + d_ptr->unregisterServer(d_ptr); +} + +bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) +{ + typedef QBluetoothServerPrivate::ObjCListener ObjCListener; + + if (d_ptr->listener) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen() ", + "already in listen mode, " + "close server first"; + return false; + } + + const QBluetoothLocalDevice device(address); + if (!device.isValid()) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), device does not support Bluetooth or " + << address.toString() + << " is not a valid local adapter"; + d_ptr->lastError = UnknownError; + emit error(UnknownError); + return false; + } + + const QBluetoothLocalDevice::HostMode hostMode = device.hostMode(); + if (hostMode == QBluetoothLocalDevice::HostPoweredOff) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), " + "bluetooth device is powered off"; + d_ptr->lastError = PoweredOffError; + emit error(PoweredOffError); + return false; + } + + const QSInfo::Protocol type = d_ptr->serverType; + + if (type == QSInfo::UnknownProtocol) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), " + "invalid protocol"; + d_ptr->lastError = UnsupportedProtocolError; + emit error(d_ptr->lastError); + return false; + } + + d_ptr->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 == QSInfo::RfcommProtocol) { + if (d_ptr->channelIsBusy(port)) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), server port: " + << port << "already registered"; + d_ptr->lastError = ServiceAlreadyRegisteredError; + } + } else { + if (d_ptr->psmIsBusy(port)) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), server port: " + << port << "already registered"; + d_ptr->lastError = ServiceAlreadyRegisteredError; + } + } + } else { + type == QSInfo::RfcommProtocol ? port = d_ptr->findFreeChannel() + : port = d_ptr->findFreePSM(); + } + + if (d_ptr->lastError != QBluetoothServer::NoError) { + emit error(d_ptr->lastError); + return false; + } + + if (!port) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), all ports are busy"; + d_ptr->lastError = ServiceAlreadyRegisteredError; + emit error(d_ptr->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]); + + return true; +} + +QBluetoothServiceInfo QBluetoothServer::listen(const QBluetoothUuid &uuid, const QString &serviceName) +{ + if (!listen()) + return QBluetoothServiceInfo(); + + QBluetoothServiceInfo serviceInfo; + serviceInfo.setAttribute(QSInfo::ServiceName, serviceName); + serviceInfo.setAttribute(QSInfo::BrowseGroupList, + QBluetoothUuid(QBluetoothUuid::PublicBrowseGroup)); + + QSInfo::Sequence classId; + classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort)); + serviceInfo.setAttribute(QSInfo::BluetoothProfileDescriptorList, classId); + + classId.prepend(QVariant::fromValue(uuid)); + serviceInfo.setAttribute(QSInfo::ServiceClassIds, classId); + serviceInfo.setServiceUuid(uuid); + + QSInfo::Sequence protocolDescriptorList; + QSInfo::Sequence protocol; + protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap)); + if (d_ptr->serverType == QSInfo::L2capProtocol) + protocol << QVariant::fromValue(serverPort()); + protocolDescriptorList.append(QVariant::fromValue(protocol)); + protocol.clear(); + + if (d_ptr->serverType == QBluetoothServiceInfo::RfcommProtocol) { + protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm)) + << QVariant::fromValue(quint8(serverPort())); + protocolDescriptorList.append(QVariant::fromValue(protocol)); + } + + serviceInfo.setAttribute(QSInfo::ProtocolDescriptorList, + protocolDescriptorList); + + + // It's now up to a service info to acquire a real PSM/channel ID + // (provided by IOBluetooth) and start a listener. + if (!serviceInfo.registerService()) + return QBluetoothServiceInfo(); + + return serviceInfo; +} + +bool QBluetoothServer::isListening() const +{ + return d_ptr->listener; +} + +void QBluetoothServer::setMaxPendingConnections(int numConnections) +{ + // That's a 'fake' limit, it affects nothing. + d_ptr->maxPendingConnections = numConnections; +} + +int QBluetoothServer::maxPendingConnections() const +{ + // That's a 'fake' limit, it affects nothing. + return d_ptr->maxPendingConnections; +} + +bool QBluetoothServer::hasPendingConnections() const +{ + return d_ptr->pendingConnections.size(); +} + +QBluetoothSocket *QBluetoothServer::nextPendingConnection() +{ + if (!d_ptr->pendingConnections.size()) + return Q_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 == QSInfo::RfcommProtocol) { + if (!newSocket->d_ptr->setChannel(static_cast<IOBluetoothRFCOMMChannel *>(channel))) + return Q_NULLPTR; + } else { + if (!newSocket->d_ptr->setChannel(static_cast<IOBluetoothL2CAPChannel *>(channel))) + return Q_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) + // Not implemented (yet?) +} + +QBluetooth::SecurityFlags QBluetoothServer::securityFlags() const +{ + // Not implemented (yet?) + return QBluetooth::NoSecurity; +} + +QSInfo::Protocol QBluetoothServer::serverType() const +{ + return d_ptr->serverType; +} + +QBluetoothServer::Error QBluetoothServer::error() const +{ + return d_ptr->lastError; +} + +#include "moc_qbluetoothserver.cpp" + +QT_END_NAMESPACE |