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
qohosbluetoothsocket.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 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
6#include <QtBluetooth/private/qohosbluetoothaccess_p.h>
7#include <QtBluetooth/private/qohosbluetoothcommon_p.h>
8#include <QtBluetooth/private/qohosbluetoothenums_p.h>
9#include <QtBluetooth/private/qohosbluetoothremotedevice_p.h>
10
11#include <QtCore/q26numeric.h>
12#include <QtCore/qloggingcategory.h>
13#include <QtCore/private/qcore_ohos_p.h>
14#include <QtCore/private/qnapi_p.h>
15#include <QtCore/private/qohosjstools_p.h>
16#include <QtCore/private/qohoslogger_p.h>
17
18#include <cstddef>
19#include <cstring>
20#include <memory>
21#include <optional>
22#include <string>
23
25
27
28namespace QtOhosBluetooth {
29
30namespace {
31
32std::shared_ptr<int> makeSppSocketHandle(int socketDescriptor, const char *closeCallExpression)
33{
34 return QtOhos::makeSharedPtrWithAttachedExtraData(
35 std::make_shared<int>(socketDescriptor),
36 QtOhos::makeDestroyNotifier(
37 [socketDescriptor, closeCallExpression]() {
38 QOhosJsThreadGateway::invoke(
39 [socketDescriptor, closeCallExpression](QOhosJsState &jsState) {
40 try {
41 jsState.eval(closeCallExpression, {socketDescriptor});
42 } catch (const Napi::Error &error) {
43 const auto errorCode =
44 tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)
45 .value_or(QOhosBluetoothErrorCode::OperationFailed);
46 qOhosPrintfWarning(
47 "%s: cannot close the socket %d with %s, error code: %u",
48 Q_FUNC_INFO, socketDescriptor, closeCallExpression,
49 qToUnderlying(errorCode));
50 }
51 });
52 }));
53}
54
55}
56
57std::shared_ptr<int> makeSppClientSocketHandle(int socketDescriptor)
58{
59 return makeSppSocketHandle(socketDescriptor, "@ohos.bluetooth.socket.sppCloseClientSocket(*)");
60}
61
62std::shared_ptr<int> makeSppServerSocketHandle(int socketDescriptor)
63{
64 return makeSppSocketHandle(socketDescriptor, "@ohos.bluetooth.socket.sppCloseServerSocket(*)");
65}
66
67namespace {
68
69using SppType = QtOhosBluetooth::enums::ohos::bluetooth::socket::SppType;
70
71std::shared_ptr<void> registerSocketSppReadListener(
72 QOhosJsState &jsState, int socketDescriptor, QOhosConsumer<QByteArray> sppReadListener,
73 QOhosConsumer<QOhosBluetoothErrorCode> errorCodeConsumer)
74{
75 return registerQOhosOnOffMethodsBasedEventHandler(
76 jsState.eval<QNapi::Object>("@ohos.bluetooth.socket"), "sppRead",
77 [sppReadListener = std::move(sppReadListener)](const QOhosCallbackInfo &cbInfo) {
78 auto arrayBuffer = cbInfo.getFirstArg<QNapi::ArrayBuffer>(Q_FUNC_INFO);
79 sppReadListener(
80 QByteArray(
81 static_cast<const char *>(arrayBuffer.Data()),
82 static_cast<qsizetype>(arrayBuffer.ByteLength())));
83 },
84 {
85 .extraOnArg = std::make_optional(QNapi::ValueWrapper(socketDescriptor)),
86 .extraOffArg = std::make_optional(QNapi::ValueWrapper(socketDescriptor)),
87 .optOnCallExceptionHandler =
88 [errorCodeConsumer = std::move(errorCodeConsumer)](const Napi::Error &error) {
89 errorCodeConsumer(
90 tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)
91 .value_or(QOhosBluetoothErrorCode::OperationFailed));
92 },
93 });
94}
95
96std::shared_ptr<void> registerSocketSppReadHandler(
97 int socketDescriptor, QOhosConsumer<QByteArray> receivedDataConsumer,
98 QOhosConsumer<QOhosBluetoothErrorCode> errorCodeConsumer)
99{
100 auto sharedReceivedDataConsumer = QtOhos::moveToSharedPtr(std::move(receivedDataConsumer));
101 auto weakReceivedDataConsumer = QtOhos::makeWeakPtr(sharedReceivedDataConsumer);
102
103 auto socketSppReadHandler = QOhosJsThreadGateway::eval(
104 [&](QOhosJsState &jsState) {
105 return registerSocketSppReadListener(
106 jsState, socketDescriptor,
107 [weakReceivedDataConsumer](QByteArray receivedData) {
108 QtOhos::invokeInQtThread(
109 [weakReceivedDataConsumer, receivedData = std::move(receivedData)]() {
110 auto receivedDataConsumer = weakReceivedDataConsumer.lock();
111 if (receivedDataConsumer)
112 (*receivedDataConsumer)(std::move(receivedData));
113 });
114 },
115 std::move(errorCodeConsumer));
116 },
117 Q_FUNC_INFO);
118
119 if (!socketSppReadHandler)
120 return nullptr;
121
122 return QtOhos::makeSharedPtrWithAttachedExtraData(
123 socketSppReadHandler, sharedReceivedDataConsumer);
124}
125
126std::optional<bool> tryReadSocketConnectedState(QOhosJsState &jsState, int socketDescriptor)
127{
128 try {
129 return jsState
130 .eval<QNapi::Boolean>(
131 "@ohos.bluetooth.socket.isConnected(*)", {socketDescriptor})
132 .Value();
133 } catch (const Napi::Error &error) {
134 const auto errorCode =
135 tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)
136 .value_or(QOhosBluetoothErrorCode::OperationFailed);
137 qOhosPrintfWarning(
138 "%s: cannot read the socket connected state, error code: %u", Q_FUNC_INFO,
139 qToUnderlying(errorCode));
140 return std::nullopt;
141 }
142}
143
144std::optional<std::string> tryGetDeviceIdFromSocket(int socket)
145{
146 if (socket == invalidSocketDescriptor)
147 return std::nullopt;
148
149 return QOhosJsThreadGateway::eval(
150 [&](QOhosJsState &jsState) -> std::optional<std::string> {
151 try {
152 return jsState.eval<QNapi::String>(
153 "@ohos.bluetooth.socket.getDeviceId(*)", {socket});
154 } catch (const Napi::Error &error) {
155 if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
156 qOhosPrintfWarning(
157 "%s: cannot read the device id of the socket %d, error code: %u",
158 Q_FUNC_INFO, socket, qToUnderlying(*optErrorCode));
159 }
160 return std::nullopt;
161 }
162 });
163}
164
165}
166
167QOhosBluetoothSocketProxy::QOhosBluetoothSocketProxy()
168{
169 QObject::connect(
170 QOhosBluetoothAccessProxy::instance().get(),
171 &QOhosBluetoothAccessProxy::stateChanged,
172 this,
173 [this](QOhosBluetoothAccessProxy::BluetoothState bluetoothState) {
174 if (bluetoothState == QOhosBluetoothAccessProxy::BluetoothState::STATE_ON)
175 return;
176
177 if (m_pendingConnectRequestId) {
178 qCWarning(
179 QT_BT_OHOS, "%s: bluetooth is turned off. Aborting the connect request ...",
180 Q_FUNC_INFO);
181 onConnectFailed(
182 *m_pendingConnectRequestId, QOhosBluetoothErrorCode::BluetoothDisabled);
183 return;
184 }
185
186 if (!m_jsScopeData)
187 return;
188
189 qCWarning(
190 QT_BT_OHOS, "%s: bluetooth is turned off. Closing the socket ...", Q_FUNC_INFO);
191 reportError(QOhosBluetoothErrorCode::BluetoothDisabled);
192 close();
193 });
194}
195
196std::unordered_map<int, std::shared_ptr<int>> &QOhosBluetoothSocketProxy::pendingSocketHandles()
197{
198 static std::unordered_map<int, std::shared_ptr<int>> handles;
199 return handles;
200}
201
203{
204 if (!socketHandle) {
205 qCCritical(QT_BT_OHOS, "%s: no socket handle to register", Q_FUNC_INFO);
206 return false;
207 }
208
209 const auto socketDescriptor = *socketHandle;
210
211 auto &handles = pendingSocketHandles();
212 if (handles.find(socketDescriptor) != handles.end()) {
213 qCCritical(
214 QT_BT_OHOS,
215 "%s: socket descriptor %d is already registered as pending. Rejecting the socket ...",
216 Q_FUNC_INFO, socketDescriptor);
217 return false;
218 }
219
220 handles[socketDescriptor] = std::move(socketHandle);
221
222 return true;
223}
224
226{
227 pendingSocketHandles().erase(socketDescriptor);
228}
229
230std::shared_ptr<int> QOhosBluetoothSocketProxy::takePendingSocketHandle(int socketDescriptor)
231{
232 auto &handles = pendingSocketHandles();
233 auto found = handles.find(socketDescriptor);
234 if (found == handles.end())
235 return nullptr;
236
237 auto socketHandle = std::move(found->second);
238 handles.erase(found);
239 return socketHandle;
240}
241
242bool QOhosBluetoothSocketProxy::connect(const QString &deviceId, const QString &uuid, bool secure)
243{
244 if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO)) {
245 reportError(QOhosBluetoothErrorCode::PermissionDenied);
246 return false;
247 }
248
249 const auto connectRequestId = ++m_connectRequestsCounter;
250 m_pendingConnectRequestId = connectRequestId;
251
252 auto selfRef = QtOhos::makeQThreadSafeRef(this);
253 auto optConnectErrorCode = QOhosJsThreadGateway::eval(
254 [&](QOhosJsState &jsState) -> std::optional<QOhosBluetoothErrorCode> {
255 auto sppOptions = QNapi::makeObject(
256 jsState.env(),
257 {
258 {"uuid", uuid.toStdString()},
259 {"secure", secure},
260 {"type", jsState.mapOhosEnumToJs(SppType::SPP_RFCOMM)},
261 });
262
263 try {
264 jsState.eval(
265 "@ohos.bluetooth.socket.sppConnect(*)",
266 {
267 deviceId.toStdString(),
268 sppOptions,
269 [selfRef, connectRequestId](const QNapi::CallbackInfo &callbackInfo) {
270 QNapi::Value error;
271 QNapi::Value data;
272 callbackInfo.getLeadingArgs(Q_FUNC_INFO, error, data);
273 auto socketId = data.IsNumber()
274 ? QNapi::checkedCast<QNapi::Number>(data).Int32Value()
275 : invalidSocketDescriptor;
276
277 if (socketId == invalidSocketDescriptor) {
278 const auto errorCode =
279 tryGetKnownErrorCode(error, Q_FUNC_INFO)
280 .value_or(QOhosBluetoothErrorCode::OperationFailed);
281 qOhosPrintfError(
282 "%s: sppConnect() failed with error code: %u", Q_FUNC_INFO,
283 qToUnderlying(errorCode));
284 selfRef.visitInQtThreadIfAlive(
285 [connectRequestId, errorCode](auto &self) {
286 self.onConnectFailed(connectRequestId, errorCode);
287 });
288 return;
289 }
290
291 auto socketHandle = makeSppClientSocketHandle(socketId);
292
293 selfRef.visitInQtThreadIfAlive(
294 [connectRequestId, socketHandle](auto &self) {
295 self.onSocketConnected(connectRequestId, socketHandle);
296 });
297 }
298 });
299 return std::nullopt;
300 } catch (const Napi::Error &error) {
301 return tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO);
302 }
303 });
304
305 if (optConnectErrorCode) {
306 qCCritical(
307 QT_BT_OHOS, "%s: sppConnect() failed with error code: %u", Q_FUNC_INFO,
308 qToUnderlying(*optConnectErrorCode));
309 onConnectFailed(connectRequestId, *optConnectErrorCode);
310 return false;
311 }
312
313 m_deviceId = deviceId.toStdString();
314
315 return true;
316}
317
318void QOhosBluetoothSocketProxy::onSocketConnected(
319 std::uint64_t connectRequestId, std::shared_ptr<int> socketHandle)
320{
321 if (m_pendingConnectRequestId != connectRequestId) {
322 qCDebug(
323 QT_BT_OHOS,
324 "%s: the connection has been closed before it was established. Letting the socket "
325 "handle close the socket ...",
326 Q_FUNC_INFO);
327 return;
328 }
329
330 m_pendingConnectRequestId.reset();
331
332 if (!takeOverSocket(std::move(socketHandle))) {
333 m_deviceId.reset();
334 reportError(QOhosBluetoothErrorCode::OperationFailed);
335 }
336}
337
338void QOhosBluetoothSocketProxy::onConnectFailed(
339 std::uint64_t connectRequestId, QOhosBluetoothErrorCode errorCode)
340{
341 if (m_pendingConnectRequestId != connectRequestId)
342 return;
343
344 m_pendingConnectRequestId.reset();
345 m_deviceId.reset();
346
347 reportError(errorCode);
348}
349
350bool QOhosBluetoothSocketProxy::takeOverSocket(std::shared_ptr<int> socketHandle)
351{
352 if (!socketHandle) {
353 qCCritical(QT_BT_OHOS, "%s: no socket handle to take over", Q_FUNC_INFO);
354 return false;
355 }
356
357 if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
358 return false;
359
360 auto selfRef = QtOhos::makeQThreadSafeRef(this);
361 auto optSubscriptionErrorCode = std::make_shared<std::optional<QOhosBluetoothErrorCode>>();
362
363 auto sppReadHandlerHandle = registerSocketSppReadHandler(
364 *socketHandle,
365 [selfRef](QByteArray receivedData) {
366 if (receivedData.isEmpty())
367 return;
368
369 selfRef.visitInQtThreadIfAlive(
370 [receivedData = std::move(receivedData)](auto &self) {
371 Q_EMIT self.dataReceived(receivedData);
372 });
373 },
374 [optSubscriptionErrorCode](QOhosBluetoothErrorCode errorCode) {
375 *optSubscriptionErrorCode = errorCode;
376 });
377
378 if (!sppReadHandlerHandle) {
379 qCCritical(
380 QT_BT_OHOS,
381 "%s: cannot register the SPP read handler, error code: %u. Socket is not connected.",
382 Q_FUNC_INFO,
383 qToUnderlying(
384 optSubscriptionErrorCode->value_or(QOhosBluetoothErrorCode::OperationFailed)));
385 return false;
386 }
387
388 m_jsScopeData = QtOhos::makeProxyWithJsThreadDeleter(
389 QtOhos::moveToSharedPtr(
390 JsScopeData {
391 .socketHandle = socketHandle,
392 .sppReadHandlerHandle = std::move(sppReadHandlerHandle),
393 }));
394
395 const auto socketDescriptor = *socketHandle;
396 QMetaObject::invokeMethod(
397 this,
398 [this, socketDescriptor]() {
399 Q_EMIT connected(socketDescriptor);
400 },
401 Qt::QueuedConnection);
402
403 return true;
404}
405
406bool QOhosBluetoothSocketProxy::write(const char *data, qint64 maxSize)
407{
408 if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO)) {
409 reportError(QOhosBluetoothErrorCode::PermissionDenied);
410 return false;
411 }
412
413 if (!m_jsScopeData) {
414 qCWarning(QT_BT_OHOS, "%s: the socket is already closed", Q_FUNC_INFO);
415 reportError(QOhosBluetoothErrorCode::OperationFailed);
416 return false;
417 }
418
419 const auto socketDescriptor = *m_jsScopeData->socketHandle;
420
421 struct WriteOutcome
422 {
423 std::optional<QOhosBluetoothErrorCode> optErrorCode = std::nullopt;
424 bool socketStillConnected = true;
425 };
426
427 const auto writeSize = q26::saturate_cast<std::size_t>(maxSize);
428 const auto writeOutcome = QOhosJsThreadGateway::eval(
429 [&](QOhosJsState &jsState) -> WriteOutcome {
430 try {
431 auto arrayBuffer = QNapi::ArrayBuffer::New(jsState.env(), writeSize);
432 std::memcpy(arrayBuffer.Data(), data, writeSize);
433 jsState.eval("@ohos.bluetooth.socket.sppWrite(*)", {socketDescriptor, arrayBuffer});
434 return {};
435 } catch (const Napi::Error &error) {
436 return {
437 .optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO),
438 .socketStillConnected =
439 tryReadSocketConnectedState(jsState, socketDescriptor).value_or(true),
440 };
441 }
442 });
443
444 if (!writeOutcome.optErrorCode)
445 return true;
446
447 reportError(*writeOutcome.optErrorCode);
448
449 if (!writeOutcome.socketStillConnected) {
450 qCWarning(
451 QT_BT_OHOS, "%s: the peer is no longer connected. Closing the socket ...", Q_FUNC_INFO);
452 close();
453 }
454
455 return false;
456}
457
459{
460 if (!m_jsScopeData && !m_pendingConnectRequestId)
461 return;
462
463 m_jsScopeData.reset();
464 m_pendingConnectRequestId.reset();
465 m_deviceId.reset();
466
467 QMetaObject::invokeMethod(this, &QOhosBluetoothSocketProxy::disconnected, Qt::QueuedConnection);
468}
469
471{
472 const auto peerDeviceIdFromSocket = m_jsScopeData
473 ? tryGetDeviceIdFromSocket(*m_jsScopeData->socketHandle)
474 : std::optional<std::string>();
475 const auto &optPeerDeviceId = peerDeviceIdFromSocket
476 ? peerDeviceIdFromSocket
477 : m_deviceId;
478
479 return optPeerDeviceId
480 ? std::optional(QString::fromStdString(*optPeerDeviceId))
481 : std::nullopt;
482}
483
485{
486 const auto deviceId = tryGetPeerDeviceAddress();
487 if (!deviceId)
488 return std::nullopt;
489
490 auto remoteDeviceProxy = QOhosBluetoothRemoteDeviceProxy::instance();
491 return remoteDeviceProxy->tryGetRemoteDeviceName(*deviceId);
492}
493
494void QOhosBluetoothSocketProxy::reportError(QOhosBluetoothErrorCode errorCode)
495{
496 QMetaObject::invokeMethod(
497 this,
498 [this, errorCode]() {
499 Q_EMIT errorOccurred(errorCode);
500 },
501 Qt::QueuedConnection);
502}
503
504}
505
506QT_END_NAMESPACE
507
508#include "moc_qohosbluetoothsocket_p.cpp"
bool connect(const QString &deviceId, const QString &uuid, bool secure)
std::optional< QString > tryGetPeerDeviceAddress() const
bool write(const char *data, qint64 maxSize)
static void dropPendingSocketHandle(int socketDescriptor)
static std::shared_ptr< int > takePendingSocketHandle(int socketDescriptor)
std::optional< QString > tryGetPeerDeviceName() const
bool takeOverSocket(std::shared_ptr< int > socketHandle)
static bool registerPendingSocketHandle(std::shared_ptr< int > socketHandle)
constexpr int invalidSocketDescriptor
std::shared_ptr< int > makeSppClientSocketHandle(int socketDescriptor)
std::shared_ptr< int > makeSppServerSocketHandle(int socketDescriptor)
QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcQIORing)