/**************************************************************************** ** ** 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_macos_p.h" #include "darwin/btrfcommchannel_p.h" #include "darwin/btl2capchannel_p.h" #include "qbluetoothlocaldevice.h" #include "qbluetoothdeviceinfo.h" #include "darwin/btutility_p.h" #include "darwin/uistrings_p.h" #include "qbluetoothsocket.h" #include #include #include #include QT_BEGIN_NAMESPACE namespace { using DarwinBluetooth::RetainPolicy; using ObjCL2CAPChannel = QT_MANGLE_NAMESPACE(DarwinBTL2CAPChannel); using ObjCRFCOMMChannel = QT_MANGLE_NAMESPACE(DarwinBTRFCOMMChannel); } // unnamed namespace QBluetoothSocketPrivate::QBluetoothSocketPrivate() : writeChunk(std::numeric_limits::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() peerName]; } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { if (l2capChannel) nsName = [l2capChannel.getAs() peerName]; } if (nsName) return QString::fromNSString(nsName); return QString(); } QBluetoothAddress QBluetoothSocketPrivate::peerAddress() const { BluetoothDeviceAddress addr = {}; if (socketType == QBluetoothServiceInfo::RfcommProtocol) { if (rfcommChannel) addr = [rfcommChannel.getAs() peerAddress]; } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { if (l2capChannel) addr = [l2capChannel.getAs() peerAddress]; } return DarwinBluetooth::qt_address(&addr); } quint16 QBluetoothSocketPrivate::peerPort() const { if (socketType == QBluetoothServiceInfo::RfcommProtocol) { if (rfcommChannel) return [rfcommChannel.getAs() getChannelID]; } else if (socketType == QBluetoothServiceInfo::L2capProtocol) { if (l2capChannel) return [l2capChannel.getAs() 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_DARWIN) << "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); DarwinBluetooth::qt_test_iobluetooth_runloop(); if (state!= QBluetoothSocket::UnconnectedState && state != QBluetoothSocket::ServiceLookupState) { qCWarning(QT_BT_DARWIN) << "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_DARWIN) << 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_DARWIN) << "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); DarwinBluetooth::qt_test_iobluetooth_runloop(); // Report this problem early, avoid device discovery: if (socketType == QBluetoothServiceInfo::UnknownProtocol) { qCWarning(QT_BT_DARWIN) << 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_DARWIN) << "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); DarwinBluetooth::qt_test_iobluetooth_runloop(); if (socketType == QBluetoothServiceInfo::UnknownProtocol) { qCWarning(QT_BT_DARWIN) << 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() 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() 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 = DarwinBluetooth::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::max() : [rfcommChannel.getAs() getMTU]); const int size = txBuffer.read(writeChunk.data(), writeChunk.size()); IOReturn status = kIOReturnError; if (!isL2CAP) status = [rfcommChannel.getAs() writeAsync:writeChunk.data() length:UInt16(size)]; else status = [l2capChannel.getAs() 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(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(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(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