summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qbluetoothserver_osx.mm
diff options
context:
space:
mode:
authorTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-09-23 17:18:05 +0200
committerTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-09-26 18:19:54 +0200
commitd66c466a135e4bebc293256f6ca361b287fd9bdb (patch)
tree503ba3d58787adfcc6dca6b3cab16b47bd0c32ae /src/bluetooth/qbluetoothserver_osx.mm
parentd1d77c8210ecf7a89a81dad69d21b91e06bf129e (diff)
Port QBluetoothServer to OS X.
Implement QBluetoothServer using IOBluetooth framework. - Add empty (for now) implementation + modify .pro file. - Add a 'socket listener'. Actually, there are no sockets, no 'listen' but I still have to emulate this to make a server work. - Implement (to some degree) QBluetoothServer::listen member functions: on OS X QBluetoothServer::listen(address, port) does not really create a listening socket, it just checks that this port is not busy yet (IOBluetooth can either listen on a port you provide, or can listen on any port, but it can not select some port and listen on it. Only after service registered (with 'invalid' port first) - we have a real channelID or PSM. - Server port - either a "fake" port assigned by QBluetoothServer::listen, or a real port as registered by IOBluetooth. - Update a dependency. - Implement nextPendingConnection. - Implement fake server ports (something similar to Android version), but on OS X these fake ports can later be replaced with real ports. - Service info updates PSM/ChannelID with a real port and also starts a listener. - Unregister a server (dtor, close, etc.) - Do not update a 'fake' port with a real one: it can happen, that a real port is already taken by some 'fake' port and this will break the whole idea of fake ports. Let them be always fake, the real is required only when starting a 'listener'. - With 'fake' server ports '0' is not valid anymore (use serverPort() instead). Change-Id: I44537a35891c6806e58ec874a18bd938d4b41c53 Reviewed-by: Alex Blasche <alexander.blasche@digia.com>
Diffstat (limited to 'src/bluetooth/qbluetoothserver_osx.mm')
-rw-r--r--src/bluetooth/qbluetoothserver_osx.mm501
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