Revision control

Copy as Markdown

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#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 <QtCore/qloggingcategory.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/q26numeric.h>
#include <algorithm>
#include <limits>
QT_BEGIN_NAMESPACE
namespace {
using DarwinBluetooth::RetainPolicy;
} // unnamed namespace
QBluetoothSocketPrivateDarwin::QBluetoothSocketPrivateDarwin(QBluetoothSocket *qq)
: QBluetoothSocketBasePrivate{qq},
writeChunk(std::numeric_limits<UInt16>::max())
{
}
QBluetoothSocketPrivateDarwin::~QBluetoothSocketPrivateDarwin()
{
}
bool QBluetoothSocketPrivateDarwin::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 QBluetoothSocketPrivateDarwin::localName() const
{
const QBluetoothLocalDevice device;
return device.name();
}
QBluetoothAddress QBluetoothSocketPrivateDarwin::localAddress() const
{
const QBluetoothLocalDevice device;
return device.address();
}
quint16 QBluetoothSocketPrivateDarwin::localPort() const
{
return 0;
}
QString QBluetoothSocketPrivateDarwin::peerName() const
{
QT_BT_MAC_AUTORELEASEPOOL;
NSString *nsName = nil;
if (socketType == QBluetoothServiceInfo::RfcommProtocol) {
if (rfcommChannel)
nsName = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() peerName];
} else if (socketType == QBluetoothServiceInfo::L2capProtocol) {
if (l2capChannel)
nsName = [l2capChannel.getAs<DarwinBTL2CAPChannel>() peerName];
}
if (nsName)
return QString::fromNSString(nsName);
return QString();
}
QBluetoothAddress QBluetoothSocketPrivateDarwin::peerAddress() const
{
BluetoothDeviceAddress addr = {};
if (socketType == QBluetoothServiceInfo::RfcommProtocol) {
if (rfcommChannel)
addr = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() peerAddress];
} else if (socketType == QBluetoothServiceInfo::L2capProtocol) {
if (l2capChannel)
addr = [l2capChannel.getAs<DarwinBTL2CAPChannel>() peerAddress];
}
return DarwinBluetooth::qt_address(&addr);
}
quint16 QBluetoothSocketPrivateDarwin::peerPort() const
{
if (socketType == QBluetoothServiceInfo::RfcommProtocol) {
if (rfcommChannel)
return [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() getChannelID];
} else if (socketType == QBluetoothServiceInfo::L2capProtocol) {
if (l2capChannel)
return [l2capChannel.getAs<DarwinBTL2CAPChannel>() getPSM];
}
return 0;
}
void QBluetoothSocketPrivateDarwin::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::SocketState::UnconnectedState);
emit q_ptr->readChannelFinished();
}
void QBluetoothSocketPrivateDarwin::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 QBluetoothSocketPrivateDarwin::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::SocketState::ConnectedState) {
errorString = QCoreApplication::translate(SOCKET, SOC_NOWRITE);
q_ptr->setSocketError(QBluetoothSocket::SocketError::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);
const int adjustedMaxSize = q26::saturate_cast<int>(maxSize);
char *dst = txBuffer.reserve(adjustedMaxSize);
std::copy(data, data + adjustedMaxSize, dst);
return adjustedMaxSize;
}
qint64 QBluetoothSocketPrivateDarwin::readData(char *data, qint64 maxSize)
{
if (!data)
return 0;
if (state != QBluetoothSocket::SocketState::ConnectedState) {
errorString = QCoreApplication::translate(SOCKET, SOC_NOREAD);
q_ptr->setSocketError(QBluetoothSocket::SocketError::OperationError);
return -1;
}
if (!rxBuffer.isEmpty())
return rxBuffer.read(data, int(maxSize));
return 0;
}
qint64 QBluetoothSocketPrivateDarwin::bytesAvailable() const
{
return rxBuffer.size();
}
bool QBluetoothSocketPrivateDarwin::canReadLine() const
{
return rxBuffer.canReadLine();
}
qint64 QBluetoothSocketPrivateDarwin::bytesToWrite() const
{
return txBuffer.size();
}
bool QBluetoothSocketPrivateDarwin::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 false;
}
void QBluetoothSocketPrivateDarwin::connectToServiceHelper(const QBluetoothAddress &address, quint16 port,
QIODevice::OpenMode openMode)
{
Q_UNUSED(address);
Q_UNUSED(port);
Q_UNUSED(openMode);
}
void QBluetoothSocketPrivateDarwin::connectToService(const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode)
{
Q_ASSERT(q_ptr);
DarwinBluetooth::qt_test_iobluetooth_runloop();
if (state!= QBluetoothSocket::SocketState::UnconnectedState && state != QBluetoothSocket::SocketState::ServiceLookupState) {
qCWarning(QT_BT_DARWIN) << "called on a busy socket";
errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS);
q_ptr->setSocketError(QBluetoothSocket::SocketError::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::SocketError::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 QBluetoothSocketPrivateDarwin::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::SocketError::UnsupportedProtocolError);
return;
}
if (state != QBluetoothSocket::SocketState::UnconnectedState) {
qCWarning(QT_BT_DARWIN) << "called on a busy socket";
errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS);
q_ptr->setSocketError(QBluetoothSocket::SocketError::OperationError);
return;
}
QBluetoothDeviceInfo device(address, QString(), QBluetoothDeviceInfo::MiscellaneousDevice);
QBluetoothServiceInfo service;
service.setDevice(device);
service.setServiceUuid(uuid);
q_ptr->doDeviceDiscovery(service, openMode);
}
void QBluetoothSocketPrivateDarwin::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::SocketError::UnsupportedProtocolError);
return;
}
Q_ASSERT_X(state == QBluetoothSocket::SocketState::ServiceLookupState || state == QBluetoothSocket::SocketState::UnconnectedState,
Q_FUNC_INFO, "invalid state");
q_ptr->setOpenMode(mode);
socketError = QBluetoothSocket::SocketError::NoSocketError;
errorString.clear();
rxBuffer.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::SocketState::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([[DarwinBTRFCOMMChannel alloc] initWithDelegate:this], RetainPolicy::noInitialRetain);
if (rfcommChannel)
status = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() connectAsyncToDevice:address withChannelID:port];
else
status = kIOReturnNoMemory;
} else if (socketType == QBluetoothServiceInfo::L2capProtocol) {
l2capChannel.reset([[DarwinBTL2CAPChannel alloc] initWithDelegate:this], RetainPolicy::noInitialRetain);
if (l2capChannel)
status = [l2capChannel.getAs<DarwinBTL2CAPChannel>() 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::SocketError::NoSocketError) {
if (state == QBluetoothSocket::SocketState::ConnectedState) {
// Callback 'channelOpenComplete' fired before
// connectToService finished:
state = oldState;
// Connected, setOpenMode on a QBluetoothSocket.
q_ptr->setOpenMode(openMode);
q_ptr->setSocketState(QBluetoothSocket::SocketState::ConnectedState);
if (rxBuffer.size()) // We also have some data already ...
emit q_ptr->readyRead();
} else if (state == QBluetoothSocket::SocketState::UnconnectedState) {
// Even if we have some data, we can not read it if
// state != ConnectedState.
rxBuffer.clear();
state = oldState;
q_ptr->setSocketError(QBluetoothSocket::SocketError::UnknownSocketError);
} else {
// No error and we're connecting ...
state = oldState;
q_ptr->setSocketState(QBluetoothSocket::SocketState::ConnectingState);
}
} else {
state = oldState;
if (status != kIOReturnSuccess)
errorString = DarwinBluetooth::qt_error_string(status);
q_ptr->setSocketError(QBluetoothSocket::SocketError::UnknownSocketError);
}
}
void QBluetoothSocketPrivateDarwin::_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<DarwinBTRFCOMMChannel>() getMTU]);
const auto size = txBuffer.read(writeChunk.data(), writeChunk.size());
IOReturn status = kIOReturnError;
if (!isL2CAP)
status = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() writeAsync:writeChunk.data() length:UInt16(size)];
else
status = [l2capChannel.getAs<DarwinBTL2CAPChannel>() writeAsync:writeChunk.data() length:UInt16(size)];
if (status != kIOReturnSuccess) {
errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR);
q_ptr->setSocketError(QBluetoothSocket::SocketError::NetworkError);
return;
} else {
emit q_ptr->bytesWritten(size);
}
}
if (!txBuffer.size() && state == QBluetoothSocket::SocketState::ClosingState)
close();
}
bool QBluetoothSocketPrivateDarwin::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::SocketError::NoSocketError
&& state == QBluetoothSocket::SocketState::UnconnectedState && !rfcommChannel && !l2capChannel,
Q_FUNC_INFO, "unexpected socket state");
openMode = QIODevice::ReadWrite;
rfcommChannel.reset([[DarwinBTRFCOMMChannel 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::SocketState::ConnectedState;
socketType = QBluetoothServiceInfo::RfcommProtocol;
}
return rfcommChannel;
}
bool QBluetoothSocketPrivateDarwin::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::SocketError::NoSocketError
&& state == QBluetoothSocket::SocketState::UnconnectedState && !l2capChannel && !rfcommChannel,
Q_FUNC_INFO, "unexpected socket state");
openMode = QIODevice::ReadWrite;
l2capChannel.reset([[DarwinBTL2CAPChannel 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::SocketState::ConnectedState;
socketType = QBluetoothServiceInfo::L2capProtocol;
}
return l2capChannel;
}
void QBluetoothSocketPrivateDarwin::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::SocketError::UnknownSocketError;
} else {
q_ptr->setSocketError(QBluetoothSocket::SocketError::UnknownSocketError);
}
}
void QBluetoothSocketPrivateDarwin::channelOpenComplete()
{
Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
if (!isConnecting) {
q_ptr->setOpenMode(openMode);
q_ptr->setSocketState(QBluetoothSocket::SocketState::ConnectedState);
} else {
state = QBluetoothSocket::SocketState::ConnectedState;
// We are still in connectToService, it'll care
// about signals!
}
}
void QBluetoothSocketPrivateDarwin::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) {
if (state == QBluetoothSocket::SocketState::ConnectedState)
q_ptr->setSocketError(QBluetoothSocket::SocketError::RemoteHostClosedError);
q_ptr->setOpenMode(QIODevice::NotOpen);
q_ptr->setSocketState(QBluetoothSocket::SocketState::UnconnectedState);
emit q_ptr->readChannelFinished();
} else {
state = QBluetoothSocket::SocketState::UnconnectedState;
// We are still in connectToService and do not want
// to emit any signals yet.
}
}
void QBluetoothSocketPrivateDarwin::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);
if (size > std::numeric_limits<int>::max())
qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "Truncating the incoming data to INT_MAX";
const int adjustedSize = q26::saturate_cast<int>(size);
char *dst = rxBuffer.reserve(adjustedSize);
std::copy(src, src + adjustedSize, dst);
if (!isConnecting) {
// If we're still in connectToService, do not emit.
emit q_ptr->readyRead();
} // else connectToService must check and emit readyRead!
}
void QBluetoothSocketPrivateDarwin::writeComplete()
{
_q_writeNotify();
}
QT_END_NAMESPACE