| /**************************************************************************** |
| ** |
| ** 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 "qbluetoothservicediscoveryagent.h" |
| // The order is important (the first header contains |
| // the base class for a private socket) - workaround for |
| // dependencies problem. |
| #include "qbluetoothsocketbase_p.h" |
| #include "qbluetoothsocket_osx_p.h" |
| |
| #include "osx/osxbtrfcommchannel_p.h" |
| #include "osx/osxbtl2capchannel_p.h" |
| #include "qbluetoothlocaldevice.h" |
| #include "qbluetoothdeviceinfo.h" |
| #include "osx/osxbtutility_p.h" |
| #include "osx/uistrings_p.h" |
| #include "qbluetoothsocket.h" |
| |
| #include <QtCore/qloggingcategory.h> |
| #include <QtCore/qmetaobject.h> |
| |
| #include <algorithm> |
| #include <limits> |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace { |
| |
| using DarwinBluetooth::RetainPolicy; |
| using ObjCL2CAPChannel = QT_MANGLE_NAMESPACE(OSXBTL2CAPChannel); |
| using ObjCRFCOMMChannel = QT_MANGLE_NAMESPACE(OSXBTRFCOMMChannel); |
| |
| } // unnamed namespace |
| |
| QBluetoothSocketPrivate::QBluetoothSocketPrivate() |
| : writeChunk(std::numeric_limits<UInt16>::max()) |
| { |
| q_ptr = nullptr; |
| } |
| |
| QBluetoothSocketPrivate::~QBluetoothSocketPrivate() |
| { |
| } |
| |
| bool QBluetoothSocketPrivate::ensureNativeSocket(QBluetoothServiceInfo::Protocol type) |
| { |
| // For now - very simplistic, we don't call it in this file, public class |
| // only calls it in a ctor, setting the protocol RFCOMM (in case of Android) |
| // or, indeed, doing, socket-related initialization in BlueZ backend. |
| Q_ASSERT(socketType == QBluetoothServiceInfo::UnknownProtocol); |
| socketType = type; |
| return type != QBluetoothServiceInfo::UnknownProtocol; |
| } |
| |
| QString QBluetoothSocketPrivate::localName() const |
| { |
| const QBluetoothLocalDevice device; |
| return device.name(); |
| } |
| |
| QBluetoothAddress QBluetoothSocketPrivate::localAddress() const |
| { |
| const QBluetoothLocalDevice device; |
| return device.address(); |
| } |
| |
| quint16 QBluetoothSocketPrivate::localPort() const |
| { |
| return 0; |
| } |
| |
| QString QBluetoothSocketPrivate::peerName() const |
| { |
| QT_BT_MAC_AUTORELEASEPOOL; |
| |
| NSString *nsName = nil; |
| if (socketType == QBluetoothServiceInfo::RfcommProtocol) { |
| if (rfcommChannel) |
| nsName = [rfcommChannel.getAs<ObjCRFCOMMChannel>() peerName]; |
| } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { |
| if (l2capChannel) |
| nsName = [l2capChannel.getAs<ObjCL2CAPChannel>() peerName]; |
| } |
| |
| if (nsName) |
| return QString::fromNSString(nsName); |
| |
| return QString(); |
| } |
| |
| QBluetoothAddress QBluetoothSocketPrivate::peerAddress() const |
| { |
| BluetoothDeviceAddress addr = {}; |
| if (socketType == QBluetoothServiceInfo::RfcommProtocol) { |
| if (rfcommChannel) |
| addr = [rfcommChannel.getAs<ObjCRFCOMMChannel>() peerAddress]; |
| } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { |
| if (l2capChannel) |
| addr = [l2capChannel.getAs<ObjCL2CAPChannel>() peerAddress]; |
| } |
| |
| return OSXBluetooth::qt_address(&addr); |
| } |
| |
| quint16 QBluetoothSocketPrivate::peerPort() const |
| { |
| if (socketType == QBluetoothServiceInfo::RfcommProtocol) { |
| if (rfcommChannel) |
| return [rfcommChannel.getAs<ObjCRFCOMMChannel>() getChannelID]; |
| } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { |
| if (l2capChannel) |
| return [l2capChannel.getAs<ObjCL2CAPChannel>() getPSM]; |
| } |
| |
| return 0; |
| } |
| |
| void QBluetoothSocketPrivate::abort() |
| { |
| // Can never be called while we're in connectToService: |
| Q_ASSERT_X(!isConnecting, Q_FUNC_INFO, "internal inconsistency - " |
| "still in connectToService()"); |
| |
| if (socketType == QBluetoothServiceInfo::RfcommProtocol) |
| rfcommChannel.reset(); |
| else if (socketType == QBluetoothServiceInfo::L2capProtocol) |
| l2capChannel.reset(); |
| |
| Q_ASSERT(q_ptr); |
| |
| q_ptr->setSocketState(QBluetoothSocket::UnconnectedState); |
| emit q_ptr->readChannelFinished(); |
| emit q_ptr->disconnected(); |
| |
| } |
| |
| void QBluetoothSocketPrivate::close() |
| { |
| // Can never be called while we're in connectToService: |
| Q_ASSERT_X(!isConnecting, Q_FUNC_INFO, "internal inconsistency - " |
| "still in connectToService()"); |
| |
| if (!txBuffer.size()) |
| abort(); |
| } |
| |
| |
| qint64 QBluetoothSocketPrivate::writeData(const char *data, qint64 maxSize) |
| { |
| Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)"); |
| Q_ASSERT_X(maxSize > 0, Q_FUNC_INFO, "invalid data size"); |
| |
| if (state != QBluetoothSocket::ConnectedState) { |
| errorString = QCoreApplication::translate(SOCKET, SOC_NOWRITE); |
| q_ptr->setSocketError(QBluetoothSocket::OperationError); |
| return -1; |
| } |
| |
| // We do not have a real socket API under the hood, |
| // IOBluetoothL2CAPChannel is buffered (writeAsync). |
| |
| if (!txBuffer.size()) |
| QMetaObject::invokeMethod(this, [this](){_q_writeNotify();}, Qt::QueuedConnection); |
| |
| char *dst = txBuffer.reserve(int(maxSize)); |
| std::copy(data, data + maxSize, dst); |
| |
| return maxSize; |
| } |
| |
| qint64 QBluetoothSocketPrivate::readData(char *data, qint64 maxSize) |
| { |
| if (!data) |
| return 0; |
| |
| if (state != QBluetoothSocket::ConnectedState) { |
| errorString = QCoreApplication::translate(SOCKET, SOC_NOREAD); |
| q_ptr->setSocketError(QBluetoothSocket::OperationError); |
| return -1; |
| } |
| |
| if (!buffer.isEmpty()) |
| return buffer.read(data, int(maxSize)); |
| |
| return 0; |
| } |
| |
| qint64 QBluetoothSocketPrivate::bytesAvailable() const |
| { |
| return buffer.size(); |
| } |
| |
| bool QBluetoothSocketPrivate::canReadLine() const |
| { |
| return buffer.canReadLine(); |
| } |
| |
| qint64 QBluetoothSocketPrivate::bytesToWrite() const |
| { |
| return txBuffer.size(); |
| } |
| |
| bool QBluetoothSocketPrivate::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType, |
| QBluetoothSocket::SocketState socketState, QIODevice::OpenMode openMode) |
| { |
| Q_UNUSED(socketDescriptor) |
| Q_UNUSED(socketType) |
| Q_UNUSED(socketState) |
| Q_UNUSED(openMode) |
| |
| qCWarning(QT_BT_OSX) << "setting a socket descriptor is not supported by IOBluetooth"; |
| // Noop on macOS. |
| return true; |
| } |
| |
| void QBluetoothSocketPrivate::connectToServiceHelper(const QBluetoothAddress &address, quint16 port, |
| QIODevice::OpenMode openMode) |
| { |
| Q_UNUSED(address) |
| Q_UNUSED(port) |
| Q_UNUSED(openMode) |
| } |
| |
| void QBluetoothSocketPrivate::connectToService(const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode) |
| { |
| Q_ASSERT(q_ptr); |
| |
| OSXBluetooth::qt_test_iobluetooth_runloop(); |
| |
| if (state!= QBluetoothSocket::UnconnectedState && state != QBluetoothSocket::ServiceLookupState) { |
| qCWarning(QT_BT_OSX) << "called on a busy socket"; |
| errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS); |
| q_ptr->setSocketError(QBluetoothSocket::OperationError); |
| return; |
| } |
| |
| // Report this problem early, potentially avoid device discovery: |
| if (service.socketProtocol() == QBluetoothServiceInfo::UnknownProtocol) { |
| qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type"; |
| errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR); |
| q_ptr->setSocketError(QBluetoothSocket::UnsupportedProtocolError); |
| return; |
| } |
| |
| socketType = service.socketProtocol(); |
| |
| if (service.protocolServiceMultiplexer() > 0) { |
| connectToService(service.device().address(), |
| quint16(service.protocolServiceMultiplexer()), |
| openMode); |
| } else if (service.serverChannel() > 0) { |
| connectToService(service.device().address(), |
| quint16(service.serverChannel()), |
| openMode); |
| } else { |
| // Try service discovery. |
| if (service.serviceUuid().isNull()) { |
| qCWarning(QT_BT_OSX) << "No port, no PSM, and no " |
| "UUID provided, unable to connect"; |
| return; |
| } |
| |
| q_ptr->doDeviceDiscovery(service, openMode); |
| } |
| } |
| |
| void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, const QBluetoothUuid &uuid, |
| QIODevice::OpenMode openMode) |
| { |
| Q_ASSERT(q_ptr); |
| |
| OSXBluetooth::qt_test_iobluetooth_runloop(); |
| |
| // Report this problem early, avoid device discovery: |
| if (socketType == QBluetoothServiceInfo::UnknownProtocol) { |
| qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type"; |
| errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR); |
| q_ptr->setSocketError(QBluetoothSocket::UnsupportedProtocolError); |
| return; |
| } |
| |
| if (state != QBluetoothSocket::UnconnectedState) { |
| qCWarning(QT_BT_OSX) << "called on a busy socket"; |
| errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS); |
| q_ptr->setSocketError(QBluetoothSocket::OperationError); |
| return; |
| } |
| |
| QBluetoothDeviceInfo device(address, QString(), QBluetoothDeviceInfo::MiscellaneousDevice); |
| QBluetoothServiceInfo service; |
| service.setDevice(device); |
| service.setServiceUuid(uuid); |
| q_ptr->doDeviceDiscovery(service, openMode); |
| } |
| |
| void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, quint16 port, |
| QIODevice::OpenMode mode) |
| { |
| Q_ASSERT(q_ptr); |
| |
| OSXBluetooth::qt_test_iobluetooth_runloop(); |
| |
| if (socketType == QBluetoothServiceInfo::UnknownProtocol) { |
| qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type"; |
| errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR); |
| q_ptr->setSocketError(QBluetoothSocket::UnsupportedProtocolError); |
| return; |
| } |
| |
| Q_ASSERT_X(state == QBluetoothSocket::ServiceLookupState || state == QBluetoothSocket::UnconnectedState, |
| Q_FUNC_INFO, "invalid state"); |
| |
| q_ptr->setOpenMode(mode); |
| |
| socketError = QBluetoothSocket::NoSocketError; |
| errorString.clear(); |
| buffer.clear(); |
| txBuffer.clear(); |
| |
| IOReturn status = kIOReturnError; |
| // Setting socket state on q_ptr will emit a signal, |
| // we'd like to avoid any signal until this function completes. |
| const QBluetoothSocket::SocketState oldState = state; |
| // To prevent other connectToService calls (from QBluetoothSocket): |
| // and also avoid signals in delegate callbacks. |
| state = QBluetoothSocket::ConnectingState; |
| // We're still inside this function: |
| isConnecting = true; |
| |
| // We'll later (or now) have to set an open mode on QBluetoothSocket. |
| openMode = mode; |
| |
| if (socketType == QBluetoothServiceInfo::RfcommProtocol) { |
| rfcommChannel.reset([[ObjCRFCOMMChannel alloc] initWithDelegate:this], RetainPolicy::noInitialRetain); |
| if (rfcommChannel) |
| status = [rfcommChannel.getAs<ObjCRFCOMMChannel>() connectAsyncToDevice:address withChannelID:port]; |
| else |
| status = kIOReturnNoMemory; |
| } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { |
| l2capChannel.reset([[ObjCL2CAPChannel alloc] initWithDelegate:this], RetainPolicy::noInitialRetain); |
| if (l2capChannel) |
| status = [l2capChannel.getAs<ObjCL2CAPChannel>() connectAsyncToDevice:address withPSM:port]; |
| else |
| status = kIOReturnNoMemory; |
| } |
| |
| // We're probably still connecting, but at least are leaving this function: |
| isConnecting = false; |
| |
| // QBluetoothSocket will change the state and also emit |
| // a signal later if required. |
| if (status == kIOReturnSuccess && socketError == QBluetoothSocket::NoSocketError) { |
| if (state == QBluetoothSocket::ConnectedState) { |
| // Callback 'channelOpenComplete' fired before |
| // connectToService finished: |
| state = oldState; |
| // Connected, setOpenMode on a QBluetoothSocket. |
| q_ptr->setOpenMode(openMode); |
| q_ptr->setSocketState(QBluetoothSocket::ConnectedState); |
| emit q_ptr->connected(); |
| if (buffer.size()) // We also have some data already ... |
| emit q_ptr->readyRead(); |
| } else if (state == QBluetoothSocket::UnconnectedState) { |
| // Even if we have some data, we can not read it if |
| // state != ConnectedState. |
| buffer.clear(); |
| state = oldState; |
| q_ptr->setSocketError(QBluetoothSocket::UnknownSocketError); |
| } else { |
| // No error and we're connecting ... |
| state = oldState; |
| q_ptr->setSocketState(QBluetoothSocket::ConnectingState); |
| } |
| } else { |
| state = oldState; |
| if (status != kIOReturnSuccess) |
| errorString = OSXBluetooth::qt_error_string(status); |
| q_ptr->setSocketError(QBluetoothSocket::UnknownSocketError); |
| } |
| } |
| |
| void QBluetoothSocketPrivate::_q_writeNotify() |
| { |
| Q_ASSERT_X(socketType == QBluetoothServiceInfo::L2capProtocol |
| || socketType == QBluetoothServiceInfo::RfcommProtocol, |
| Q_FUNC_INFO, "invalid socket type"); |
| Q_ASSERT_X(l2capChannel || rfcommChannel, Q_FUNC_INFO, |
| "invalid socket (no open channel)"); |
| Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); |
| |
| if (txBuffer.size()) { |
| const bool isL2CAP = socketType == QBluetoothServiceInfo::L2capProtocol; |
| writeChunk.resize(isL2CAP ? std::numeric_limits<UInt16>::max() : |
| [rfcommChannel.getAs<ObjCRFCOMMChannel>() getMTU]); |
| |
| const int size = txBuffer.read(writeChunk.data(), writeChunk.size()); |
| IOReturn status = kIOReturnError; |
| if (!isL2CAP) |
| status = [rfcommChannel.getAs<ObjCRFCOMMChannel>() writeAsync:writeChunk.data() length:UInt16(size)]; |
| else |
| status = [l2capChannel.getAs<ObjCL2CAPChannel>() writeAsync:writeChunk.data() length:UInt16(size)]; |
| |
| if (status != kIOReturnSuccess) { |
| errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR); |
| q_ptr->setSocketError(QBluetoothSocket::NetworkError); |
| return; |
| } else { |
| emit q_ptr->bytesWritten(size); |
| } |
| } |
| |
| if (!txBuffer.size() && state == QBluetoothSocket::ClosingState) |
| close(); |
| } |
| |
| bool QBluetoothSocketPrivate::setRFCOMChannel(void *generic) |
| { |
| // A special case "constructor": on OS X we do not have a real listening socket, |
| // instead a bluetooth server "listens" for channel open notifications and |
| // creates (if asked by a user later) a "socket" object |
| // for this connection. This function initializes |
| // a "socket" from such an external channel (reported by a notification). |
| auto channel = static_cast<IOBluetoothRFCOMMChannel *>(generic); |
| // It must be a newborn socket! |
| Q_ASSERT_X(socketError == QBluetoothSocket::NoSocketError |
| && state == QBluetoothSocket::UnconnectedState && !rfcommChannel && !l2capChannel, |
| Q_FUNC_INFO, "unexpected socket state"); |
| |
| openMode = QIODevice::ReadWrite; |
| rfcommChannel.reset([[ObjCRFCOMMChannel alloc] initWithDelegate:this channel:channel], |
| RetainPolicy::noInitialRetain); |
| if (rfcommChannel) {// We do not handle errors, up to an external user. |
| q_ptr->setOpenMode(QIODevice::ReadWrite); |
| state = QBluetoothSocket::ConnectedState; |
| socketType = QBluetoothServiceInfo::RfcommProtocol; |
| } |
| |
| return rfcommChannel; |
| } |
| |
| bool QBluetoothSocketPrivate::setL2CAPChannel(void *generic) |
| { |
| // A special case "constructor": on OS X we do not have a real listening socket, |
| // instead a bluetooth server "listens" for channel open notifications and |
| // creates (if asked by a user later) a "socket" object |
| // for this connection. This function initializes |
| // a "socket" from such an external channel (reported by a notification). |
| auto channel = static_cast<IOBluetoothL2CAPChannel *>(generic); |
| |
| // It must be a newborn socket! |
| Q_ASSERT_X(socketError == QBluetoothSocket::NoSocketError |
| && state == QBluetoothSocket::UnconnectedState && !l2capChannel && !rfcommChannel, |
| Q_FUNC_INFO, "unexpected socket state"); |
| |
| openMode = QIODevice::ReadWrite; |
| l2capChannel.reset([[ObjCL2CAPChannel alloc] initWithDelegate:this channel:channel], RetainPolicy::noInitialRetain); |
| if (l2capChannel) {// We do not handle errors, up to an external user. |
| q_ptr->setOpenMode(QIODevice::ReadWrite); |
| state = QBluetoothSocket::ConnectedState; |
| socketType = QBluetoothServiceInfo::L2capProtocol; |
| } |
| |
| return l2capChannel; |
| } |
| |
| void QBluetoothSocketPrivate::setChannelError(IOReturn errorCode) |
| { |
| Q_UNUSED(errorCode) |
| |
| Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); |
| |
| if (isConnecting) { |
| // The delegate's method was called while we are still in |
| // connectToService ... will emit a moment later. |
| socketError = QBluetoothSocket::UnknownSocketError; |
| } else { |
| q_ptr->setSocketError(QBluetoothSocket::UnknownSocketError); |
| } |
| } |
| |
| void QBluetoothSocketPrivate::channelOpenComplete() |
| { |
| Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); |
| |
| if (!isConnecting) { |
| q_ptr->setSocketState(QBluetoothSocket::ConnectedState); |
| q_ptr->setOpenMode(openMode); |
| emit q_ptr->connected(); |
| } else { |
| state = QBluetoothSocket::ConnectedState; |
| // We are still in connectToService, it'll care |
| // about signals! |
| } |
| } |
| |
| void QBluetoothSocketPrivate::channelClosed() |
| { |
| Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); |
| |
| // Channel was closed by IOBluetooth and we can not write any data |
| // (thus close/abort probably will not work). |
| |
| if (!isConnecting) { |
| q_ptr->setSocketState(QBluetoothSocket::UnconnectedState); |
| q_ptr->setOpenMode(QIODevice::NotOpen); |
| emit q_ptr->readChannelFinished(); |
| emit q_ptr->disconnected(); |
| } else { |
| state = QBluetoothSocket::UnconnectedState; |
| // We are still in connectToService and do not want |
| // to emit any signals yet. |
| } |
| } |
| |
| void QBluetoothSocketPrivate::readChannelData(void *data, std::size_t size) |
| { |
| Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)"); |
| Q_ASSERT_X(size, Q_FUNC_INFO, "invalid data size (0)"); |
| Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)"); |
| |
| const char *src = static_cast<char *>(data); |
| char *dst = buffer.reserve(int(size)); |
| std::copy(src, src + size, dst); |
| |
| if (!isConnecting) { |
| // If we're still in connectToService, do not emit. |
| emit q_ptr->readyRead(); |
| } // else connectToService must check and emit readyRead! |
| } |
| |
| void QBluetoothSocketPrivate::writeComplete() |
| { |
| _q_writeNotify(); |
| } |
| |
| QT_END_NAMESPACE |