Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qbluetoothsocket_macos.mm
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5// The order is important (the first header contains
6// the base class for a private socket) - workaround for
7// dependencies problem.
10
11#include "darwin/btrfcommchannel_p.h"
12#include "darwin/btl2capchannel_p.h"
15#include "darwin/btutility_p.h"
16#include "darwin/uistrings_p.h"
18
19#include <QtCore/qloggingcategory.h>
20#include <QtCore/qcoreapplication.h>
21#include <QtCore/qmetaobject.h>
22#include <QtCore/q26numeric.h>
23
24#include <algorithm>
25#include <limits>
26
27QT_BEGIN_NAMESPACE
28
29namespace {
30
31using DarwinBluetooth::RetainPolicy;
32
33} // unnamed namespace
34
35QBluetoothSocketPrivateDarwin::QBluetoothSocketPrivateDarwin()
36 : writeChunk(std::numeric_limits<UInt16>::max())
37{
38 q_ptr = nullptr;
39}
40
41QBluetoothSocketPrivateDarwin::~QBluetoothSocketPrivateDarwin()
42{
43}
44
45bool QBluetoothSocketPrivateDarwin::ensureNativeSocket(QBluetoothServiceInfo::Protocol type)
46{
47 // For now - very simplistic, we don't call it in this file, public class
48 // only calls it in a ctor, setting the protocol RFCOMM (in case of Android)
49 // or, indeed, doing, socket-related initialization in BlueZ backend.
50 Q_ASSERT(socketType == QBluetoothServiceInfo::UnknownProtocol);
51 socketType = type;
52 return type != QBluetoothServiceInfo::UnknownProtocol;
53}
54
55QString QBluetoothSocketPrivateDarwin::localName() const
56{
57 const QBluetoothLocalDevice device;
58 return device.name();
59}
60
61QBluetoothAddress QBluetoothSocketPrivateDarwin::localAddress() const
62{
63 const QBluetoothLocalDevice device;
64 return device.address();
65}
66
67quint16 QBluetoothSocketPrivateDarwin::localPort() const
68{
69 return 0;
70}
71
72QString QBluetoothSocketPrivateDarwin::peerName() const
73{
75
76 NSString *nsName = nil;
77 if (socketType == QBluetoothServiceInfo::RfcommProtocol) {
78 if (rfcommChannel)
79 nsName = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() peerName];
80 } else if (socketType == QBluetoothServiceInfo::L2capProtocol) {
81 if (l2capChannel)
82 nsName = [l2capChannel.getAs<DarwinBTL2CAPChannel>() peerName];
83 }
84
85 if (nsName)
86 return QString::fromNSString(nsName);
87
88 return QString();
89}
90
91QBluetoothAddress QBluetoothSocketPrivateDarwin::peerAddress() const
92{
93 BluetoothDeviceAddress addr = {};
94 if (socketType == QBluetoothServiceInfo::RfcommProtocol) {
95 if (rfcommChannel)
96 addr = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() peerAddress];
97 } else if (socketType == QBluetoothServiceInfo::L2capProtocol) {
98 if (l2capChannel)
99 addr = [l2capChannel.getAs<DarwinBTL2CAPChannel>() peerAddress];
100 }
101
102 return DarwinBluetooth::qt_address(&addr);
103}
104
105quint16 QBluetoothSocketPrivateDarwin::peerPort() const
106{
107 if (socketType == QBluetoothServiceInfo::RfcommProtocol) {
108 if (rfcommChannel)
109 return [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() getChannelID];
110 } else if (socketType == QBluetoothServiceInfo::L2capProtocol) {
111 if (l2capChannel)
112 return [l2capChannel.getAs<DarwinBTL2CAPChannel>() getPSM];
113 }
114
115 return 0;
116}
117
118void QBluetoothSocketPrivateDarwin::abort()
119{
120 // Can never be called while we're in connectToService:
121 Q_ASSERT_X(!isConnecting, Q_FUNC_INFO, "internal inconsistency - "
122 "still in connectToService()");
123
124 if (socketType == QBluetoothServiceInfo::RfcommProtocol)
125 rfcommChannel.reset();
126 else if (socketType == QBluetoothServiceInfo::L2capProtocol)
127 l2capChannel.reset();
128
129 Q_ASSERT(q_ptr);
130
131 q_ptr->setSocketState(QBluetoothSocket::SocketState::UnconnectedState);
132 emit q_ptr->readChannelFinished();
133
134}
135
136void QBluetoothSocketPrivateDarwin::close()
137{
138 // Can never be called while we're in connectToService:
139 Q_ASSERT_X(!isConnecting, Q_FUNC_INFO, "internal inconsistency - "
140 "still in connectToService()");
141
142 if (!txBuffer.size())
143 abort();
144}
145
146
147qint64 QBluetoothSocketPrivateDarwin::writeData(const char *data, qint64 maxSize)
148{
149 Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)");
150 Q_ASSERT_X(maxSize > 0, Q_FUNC_INFO, "invalid data size");
151
152 if (state != QBluetoothSocket::SocketState::ConnectedState) {
153 errorString = QCoreApplication::translate(SOCKET, SOC_NOWRITE);
154 q_ptr->setSocketError(QBluetoothSocket::SocketError::OperationError);
155 return -1;
156 }
157
158 // We do not have a real socket API under the hood,
159 // IOBluetoothL2CAPChannel is buffered (writeAsync).
160
161 if (!txBuffer.size())
162 QMetaObject::invokeMethod(this, [this](){_q_writeNotify();}, Qt::QueuedConnection);
163
164 const int adjustedMaxSize = q26::saturate_cast<int>(maxSize);
165 char *dst = txBuffer.reserve(adjustedMaxSize);
166 std::copy(data, data + adjustedMaxSize, dst);
167
168 return adjustedMaxSize;
169}
170
171qint64 QBluetoothSocketPrivateDarwin::readData(char *data, qint64 maxSize)
172{
173 if (!data)
174 return 0;
175
176 if (state != QBluetoothSocket::SocketState::ConnectedState) {
177 errorString = QCoreApplication::translate(SOCKET, SOC_NOREAD);
178 q_ptr->setSocketError(QBluetoothSocket::SocketError::OperationError);
179 return -1;
180 }
181
182 if (!rxBuffer.isEmpty())
183 return rxBuffer.read(data, int(maxSize));
184
185 return 0;
186}
187
188qint64 QBluetoothSocketPrivateDarwin::bytesAvailable() const
189{
190 return rxBuffer.size();
191}
192
193bool QBluetoothSocketPrivateDarwin::canReadLine() const
194{
195 return rxBuffer.canReadLine();
196}
197
198qint64 QBluetoothSocketPrivateDarwin::bytesToWrite() const
199{
200 return txBuffer.size();
201}
202
203bool QBluetoothSocketPrivateDarwin::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType,
204 QBluetoothSocket::SocketState socketState, QIODevice::OpenMode openMode)
205{
206 Q_UNUSED(socketDescriptor);
207 Q_UNUSED(socketType);
208 Q_UNUSED(socketState);
209 Q_UNUSED(openMode);
210
211 qCWarning(QT_BT_DARWIN) << "setting a socket descriptor is not supported by IOBluetooth";
212 // Noop on macOS.
213 return false;
214}
215
216void QBluetoothSocketPrivateDarwin::connectToServiceHelper(const QBluetoothAddress &address, quint16 port,
217 QIODevice::OpenMode openMode)
218{
219 Q_UNUSED(address);
220 Q_UNUSED(port);
221 Q_UNUSED(openMode);
222}
223
224void QBluetoothSocketPrivateDarwin::connectToService(const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode)
225{
226 Q_ASSERT(q_ptr);
227
228 DarwinBluetooth::qt_test_iobluetooth_runloop();
229
230 if (state!= QBluetoothSocket::SocketState::UnconnectedState && state != QBluetoothSocket::SocketState::ServiceLookupState) {
231 qCWarning(QT_BT_DARWIN) << "called on a busy socket";
232 errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS);
233 q_ptr->setSocketError(QBluetoothSocket::SocketError::OperationError);
234 return;
235 }
236
237 // Report this problem early, potentially avoid device discovery:
238 if (service.socketProtocol() == QBluetoothServiceInfo::UnknownProtocol) {
239 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type";
240 errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR);
241 q_ptr->setSocketError(QBluetoothSocket::SocketError::UnsupportedProtocolError);
242 return;
243 }
244
245 socketType = service.socketProtocol();
246
247 if (service.protocolServiceMultiplexer() > 0) {
248 connectToService(service.device().address(),
249 quint16(service.protocolServiceMultiplexer()),
250 openMode);
251 } else if (service.serverChannel() > 0) {
252 connectToService(service.device().address(),
253 quint16(service.serverChannel()),
254 openMode);
255 } else {
256 // Try service discovery.
257 if (service.serviceUuid().isNull()) {
258 qCWarning(QT_BT_DARWIN) << "No port, no PSM, and no "
259 "UUID provided, unable to connect";
260 return;
261 }
262
263 q_ptr->doDeviceDiscovery(service, openMode);
264 }
265}
266
267void QBluetoothSocketPrivateDarwin::connectToService(const QBluetoothAddress &address, const QBluetoothUuid &uuid,
268 QIODevice::OpenMode openMode)
269{
270 Q_ASSERT(q_ptr);
271
272 DarwinBluetooth::qt_test_iobluetooth_runloop();
273
274 // Report this problem early, avoid device discovery:
275 if (socketType == QBluetoothServiceInfo::UnknownProtocol) {
276 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type";
277 errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR);
278 q_ptr->setSocketError(QBluetoothSocket::SocketError::UnsupportedProtocolError);
279 return;
280 }
281
282 if (state != QBluetoothSocket::SocketState::UnconnectedState) {
283 qCWarning(QT_BT_DARWIN) << "called on a busy socket";
284 errorString = QCoreApplication::translate(SOCKET, SOC_CONNECT_IN_PROGRESS);
285 q_ptr->setSocketError(QBluetoothSocket::SocketError::OperationError);
286 return;
287 }
288
289 QBluetoothDeviceInfo device(address, QString(), QBluetoothDeviceInfo::MiscellaneousDevice);
290 QBluetoothServiceInfo service;
291 service.setDevice(device);
292 service.setServiceUuid(uuid);
293 q_ptr->doDeviceDiscovery(service, openMode);
294}
295
296void QBluetoothSocketPrivateDarwin::connectToService(const QBluetoothAddress &address, quint16 port,
297 QIODevice::OpenMode mode)
298{
299 Q_ASSERT(q_ptr);
300
301 DarwinBluetooth::qt_test_iobluetooth_runloop();
302
303 if (socketType == QBluetoothServiceInfo::UnknownProtocol) {
304 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type";
305 errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR);
306 q_ptr->setSocketError(QBluetoothSocket::SocketError::UnsupportedProtocolError);
307 return;
308 }
309
310 Q_ASSERT_X(state == QBluetoothSocket::SocketState::ServiceLookupState || state == QBluetoothSocket::SocketState::UnconnectedState,
311 Q_FUNC_INFO, "invalid state");
312
313 q_ptr->setOpenMode(mode);
314
315 socketError = QBluetoothSocket::SocketError::NoSocketError;
316 errorString.clear();
317 rxBuffer.clear();
318 txBuffer.clear();
319
320 IOReturn status = kIOReturnError;
321 // Setting socket state on q_ptr will emit a signal,
322 // we'd like to avoid any signal until this function completes.
323 const QBluetoothSocket::SocketState oldState = state;
324 // To prevent other connectToService calls (from QBluetoothSocket):
325 // and also avoid signals in delegate callbacks.
326 state = QBluetoothSocket::SocketState::ConnectingState;
327 // We're still inside this function:
328 isConnecting = true;
329
330 // We'll later (or now) have to set an open mode on QBluetoothSocket.
331 openMode = mode;
332
333 if (socketType == QBluetoothServiceInfo::RfcommProtocol) {
334 rfcommChannel.reset([[DarwinBTRFCOMMChannel alloc] initWithDelegate:this], RetainPolicy::noInitialRetain);
335 if (rfcommChannel)
336 status = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() connectAsyncToDevice:address withChannelID:port];
337 else
338 status = kIOReturnNoMemory;
339 } else if (socketType == QBluetoothServiceInfo::L2capProtocol) {
340 l2capChannel.reset([[DarwinBTL2CAPChannel alloc] initWithDelegate:this], RetainPolicy::noInitialRetain);
341 if (l2capChannel)
342 status = [l2capChannel.getAs<DarwinBTL2CAPChannel>() connectAsyncToDevice:address withPSM:port];
343 else
344 status = kIOReturnNoMemory;
345 }
346
347 // We're probably still connecting, but at least are leaving this function:
348 isConnecting = false;
349
350 // QBluetoothSocket will change the state and also emit
351 // a signal later if required.
352 if (status == kIOReturnSuccess && socketError == QBluetoothSocket::SocketError::NoSocketError) {
353 if (state == QBluetoothSocket::SocketState::ConnectedState) {
354 // Callback 'channelOpenComplete' fired before
355 // connectToService finished:
356 state = oldState;
357 // Connected, setOpenMode on a QBluetoothSocket.
358 q_ptr->setOpenMode(openMode);
359 q_ptr->setSocketState(QBluetoothSocket::SocketState::ConnectedState);
360 if (rxBuffer.size()) // We also have some data already ...
361 emit q_ptr->readyRead();
362 } else if (state == QBluetoothSocket::SocketState::UnconnectedState) {
363 // Even if we have some data, we can not read it if
364 // state != ConnectedState.
365 rxBuffer.clear();
366 state = oldState;
367 q_ptr->setSocketError(QBluetoothSocket::SocketError::UnknownSocketError);
368 } else {
369 // No error and we're connecting ...
370 state = oldState;
371 q_ptr->setSocketState(QBluetoothSocket::SocketState::ConnectingState);
372 }
373 } else {
374 state = oldState;
375 if (status != kIOReturnSuccess)
376 errorString = DarwinBluetooth::qt_error_string(status);
377 q_ptr->setSocketError(QBluetoothSocket::SocketError::UnknownSocketError);
378 }
379}
380
381void QBluetoothSocketPrivateDarwin::_q_writeNotify()
382{
383 Q_ASSERT_X(socketType == QBluetoothServiceInfo::L2capProtocol
384 || socketType == QBluetoothServiceInfo::RfcommProtocol,
385 Q_FUNC_INFO, "invalid socket type");
386 Q_ASSERT_X(l2capChannel || rfcommChannel, Q_FUNC_INFO,
387 "invalid socket (no open channel)");
388 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
389
390 if (txBuffer.size()) {
391 const bool isL2CAP = socketType == QBluetoothServiceInfo::L2capProtocol;
392 writeChunk.resize(isL2CAP ? std::numeric_limits<UInt16>::max() :
393 [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() getMTU]);
394
395 const auto size = txBuffer.read(writeChunk.data(), writeChunk.size());
396 IOReturn status = kIOReturnError;
397 if (!isL2CAP)
398 status = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() writeAsync:writeChunk.data() length:UInt16(size)];
399 else
400 status = [l2capChannel.getAs<DarwinBTL2CAPChannel>() writeAsync:writeChunk.data() length:UInt16(size)];
401
402 if (status != kIOReturnSuccess) {
403 errorString = QCoreApplication::translate(SOCKET, SOC_NETWORK_ERROR);
404 q_ptr->setSocketError(QBluetoothSocket::SocketError::NetworkError);
405 return;
406 } else {
407 emit q_ptr->bytesWritten(size);
408 }
409 }
410
411 if (!txBuffer.size() && state == QBluetoothSocket::SocketState::ClosingState)
412 close();
413}
414
415bool QBluetoothSocketPrivateDarwin::setRFCOMChannel(void *generic)
416{
417 // A special case "constructor": on OS X we do not have a real listening socket,
418 // instead a bluetooth server "listens" for channel open notifications and
419 // creates (if asked by a user later) a "socket" object
420 // for this connection. This function initializes
421 // a "socket" from such an external channel (reported by a notification).
422 auto channel = static_cast<IOBluetoothRFCOMMChannel *>(generic);
423 // It must be a newborn socket!
424 Q_ASSERT_X(socketError == QBluetoothSocket::SocketError::NoSocketError
425 && state == QBluetoothSocket::SocketState::UnconnectedState && !rfcommChannel && !l2capChannel,
426 Q_FUNC_INFO, "unexpected socket state");
427
428 openMode = QIODevice::ReadWrite;
429 rfcommChannel.reset([[DarwinBTRFCOMMChannel alloc] initWithDelegate:this channel:channel],
430 RetainPolicy::noInitialRetain);
431 if (rfcommChannel) {// We do not handle errors, up to an external user.
432 q_ptr->setOpenMode(QIODevice::ReadWrite);
433 state = QBluetoothSocket::SocketState::ConnectedState;
434 socketType = QBluetoothServiceInfo::RfcommProtocol;
435 }
436
437 return rfcommChannel;
438}
439
440bool QBluetoothSocketPrivateDarwin::setL2CAPChannel(void *generic)
441{
442 // A special case "constructor": on OS X we do not have a real listening socket,
443 // instead a bluetooth server "listens" for channel open notifications and
444 // creates (if asked by a user later) a "socket" object
445 // for this connection. This function initializes
446 // a "socket" from such an external channel (reported by a notification).
447 auto channel = static_cast<IOBluetoothL2CAPChannel *>(generic);
448
449 // It must be a newborn socket!
450 Q_ASSERT_X(socketError == QBluetoothSocket::SocketError::NoSocketError
451 && state == QBluetoothSocket::SocketState::UnconnectedState && !l2capChannel && !rfcommChannel,
452 Q_FUNC_INFO, "unexpected socket state");
453
454 openMode = QIODevice::ReadWrite;
455 l2capChannel.reset([[DarwinBTL2CAPChannel alloc] initWithDelegate:this channel:channel], RetainPolicy::noInitialRetain);
456 if (l2capChannel) {// We do not handle errors, up to an external user.
457 q_ptr->setOpenMode(QIODevice::ReadWrite);
458 state = QBluetoothSocket::SocketState::ConnectedState;
459 socketType = QBluetoothServiceInfo::L2capProtocol;
460 }
461
462 return l2capChannel;
463}
464
465void QBluetoothSocketPrivateDarwin::setChannelError(IOReturn errorCode)
466{
467 Q_UNUSED(errorCode);
468
469 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
470
471 if (isConnecting) {
472 // The delegate's method was called while we are still in
473 // connectToService ... will emit a moment later.
474 socketError = QBluetoothSocket::SocketError::UnknownSocketError;
475 } else {
476 q_ptr->setSocketError(QBluetoothSocket::SocketError::UnknownSocketError);
477 }
478}
479
480void QBluetoothSocketPrivateDarwin::channelOpenComplete()
481{
482 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
483
484 if (!isConnecting) {
485 q_ptr->setOpenMode(openMode);
486 q_ptr->setSocketState(QBluetoothSocket::SocketState::ConnectedState);
487 } else {
488 state = QBluetoothSocket::SocketState::ConnectedState;
489 // We are still in connectToService, it'll care
490 // about signals!
491 }
492}
493
494void QBluetoothSocketPrivateDarwin::channelClosed()
495{
496 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
497
498 // Channel was closed by IOBluetooth and we can not write any data
499 // (thus close/abort probably will not work).
500
501 if (!isConnecting) {
502 if (state == QBluetoothSocket::SocketState::ConnectedState)
503 q_ptr->setSocketError(QBluetoothSocket::SocketError::RemoteHostClosedError);
504 q_ptr->setOpenMode(QIODevice::NotOpen);
505 q_ptr->setSocketState(QBluetoothSocket::SocketState::UnconnectedState);
506 emit q_ptr->readChannelFinished();
507 } else {
508 state = QBluetoothSocket::SocketState::UnconnectedState;
509 // We are still in connectToService and do not want
510 // to emit any signals yet.
511 }
512}
513
514void QBluetoothSocketPrivateDarwin::readChannelData(void *data, std::size_t size)
515{
516 Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)");
517 Q_ASSERT_X(size, Q_FUNC_INFO, "invalid data size (0)");
518 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
519
520 const char *src = static_cast<char *>(data);
521
522 if (size > std::numeric_limits<int>::max())
523 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "Truncating the incoming data to INT_MAX";
524
525 const int adjustedSize = q26::saturate_cast<int>(size);
526 char *dst = rxBuffer.reserve(adjustedSize);
527 std::copy(src, src + adjustedSize, dst);
528
529 if (!isConnecting) {
530 // If we're still in connectToService, do not emit.
531 emit q_ptr->readyRead();
532 } // else connectToService must check and emit readyRead!
533}
534
535void QBluetoothSocketPrivateDarwin::writeComplete()
536{
537 _q_writeNotify();
538}
539
540QT_END_NAMESPACE
#define QT_BT_MAC_AUTORELEASEPOOL
Definition btutility_p.h:78