1// Copyright (C) 2017 Witekio.
2// Copyright (C) 2018 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4// Qt-Security score:significant reason:default
5
6#include "qcoapclient_p.h"
7#include "qcoapprotocol_p.h"
8#include "qcoapreply.h"
9#include "qcoapresourcediscoveryreply.h"
10#include "qcoapnamespace.h"
11#include "qcoapsecurityconfiguration.h"
12#include "qcoapqudpconnection_p.h"
13#include "qcoaprequest_p.h"
14#include "qcoapreply_p.h"
15#include <QtCore/qiodevice.h>
16#include <QtCore/qurl.h>
17#include <QtCore/qloggingcategory.h>
18#include <QtNetwork/qudpsocket.h>
19
20QT_BEGIN_NAMESPACE
21
22Q_STATIC_LOGGING_CATEGORY(lcCoapClient, "qt.coap.client")
23
24QCoapClientPrivate::QCoapClientPrivate(QCoapProtocol *protocol, QCoapConnection *connection)
25 : protocol(protocol)
26 , connection(connection)
27 , workerThread(new QThread)
28{
29 protocol->moveToThread(thread: workerThread);
30 connection->moveToThread(thread: workerThread);
31 workerThread->start();
32}
33
34QCoapClientPrivate::~QCoapClientPrivate()
35{
36 workerThread->quit();
37 workerThread->wait();
38 delete workerThread;
39 delete protocol;
40 delete connection;
41}
42
43/*!
44 \class QCoapClient
45 \inmodule QtCoap
46
47 \brief The QCoapClient class allows the application to
48 send CoAP requests and receive replies.
49
50 \reentrant
51
52 The QCoapClient class contains signals that get triggered when the
53 reply of a sent request has arrived.
54
55 The application can use a QCoapClient to send requests over a CoAP
56 network. It provides functions for standard requests: each returns a QCoapReply object,
57 to which the response data shall be delivered; this can be read when the finished()
58 signal arrives.
59
60 A simple request can be sent with:
61 \code
62 QCoapClient *client = new QCoapClient(this);
63 connect(client, &QCoapClient::finished, this, &TestClass::slotFinished);
64 client->get(QCoapRequest(Qurl("coap://coap.me/test")));
65 \endcode
66
67 \note After processing of the request has finished, it is the responsibility
68 of the user to delete the QCoapReply object at an appropriate time. Do not
69 directly delete it inside the slot connected to finished(). You can use the
70 deleteLater() function.
71
72 You can also use an \e observe request. This can be used as above, or more
73 conveniently with the QCoapReply::notified() signal:
74 \code
75 QCoapRequest request = QCoapRequest(Qurl("coap://coap.me/obs"));
76 QCoapReply *reply = client->observe(request);
77 connect(reply, &QCoapReply::notified, this, &TestClass::slotNotified);
78 \endcode
79
80 And the observation can be cancelled with:
81 \code
82 client->cancelObserve(reply);
83 \endcode
84
85 When a reply arrives, the QCoapClient emits a finished() signal.
86
87 \note For a discovery request, the returned object is a QCoapResourceDiscoveryReply.
88 It can be used the same way as a QCoapReply but contains also a list of
89 resources.
90
91 \sa QCoapRequest, QCoapReply, QCoapResourceDiscoveryReply
92*/
93
94/*!
95 \fn void QCoapClient::finished(QCoapReply *reply)
96
97 This signal is emitted along with the \l QCoapReply::finished() signal
98 whenever a CoAP reply is received, after either a success or an error.
99 The \a reply parameter will contain a pointer to the reply that has just
100 been received.
101
102 \sa error(), QCoapReply::finished(), QCoapReply::error()
103*/
104
105/*!
106 \fn void QCoapClient::responseToMulticastReceived(QCoapReply *reply,
107 const QCoapMessage &message,
108 const QHostAddress &sender)
109
110 This signal is emitted when a unicast response to a multicast request
111 arrives. The \a reply parameter contains a pointer to the reply that has just
112 been received, \a message contains the payload and the message details,
113 and \a sender contains the sender address.
114
115 \sa error(), QCoapReply::finished(), QCoapReply::error()
116*/
117
118/*!
119 \fn void QCoapClient::error(QCoapReply *reply, QtCoap::Error error)
120
121 This signal is emitted whenever an error occurs. The \a reply parameter
122 can be \nullptr if the error is not related to a specific QCoapReply. The
123 \a error parameter contains the error code.
124
125 \sa finished(), QCoapReply::error(), QCoapReply::finished()
126*/
127
128/*!
129 Constructs a QCoapClient object for the given \a securityMode and
130 sets \a parent as the parent object.
131
132 The default for \a securityMode is QtCoap::NoSecurity, which
133 disables security.
134*/
135QCoapClient::QCoapClient(QtCoap::SecurityMode securityMode, QObject *parent) :
136 QObject(*new QCoapClientPrivate(new QCoapProtocol, new QCoapQUdpConnection(securityMode)),
137 parent)
138{
139 Q_D(QCoapClient);
140
141 qRegisterMetaType<QCoapReply *>();
142 qRegisterMetaType<QCoapMessage>();
143 qRegisterMetaType<QPointer<QCoapReply>>();
144 qRegisterMetaType<QPointer<QCoapResourceDiscoveryReply>>();
145 qRegisterMetaType<QCoapConnection *>();
146 qRegisterMetaType<QtCoap::Error>();
147 qRegisterMetaType<QtCoap::ResponseCode>();
148 qRegisterMetaType<QtCoap::Method>();
149 qRegisterMetaType<QtCoap::SecurityMode>();
150 qRegisterMetaType<QtCoap::MulticastGroup>();
151 // Requires a name, as this is a typedef
152 qRegisterMetaType<QCoapToken>(typeName: "QCoapToken");
153 qRegisterMetaType<QCoapMessageId>(typeName: "QCoapMessageId");
154 qRegisterMetaType<QAbstractSocket::SocketOption>();
155
156 connect(sender: d->connection, signal: &QCoapConnection::readyRead, context: d->protocol,
157 slot: [this](const QByteArray &data, const QHostAddress &sender) {
158 Q_D(QCoapClient);
159 d->protocol->d_func()->onFrameReceived(data, sender);
160 });
161 connect(sender: d->connection, signal: &QCoapConnection::error, context: d->protocol,
162 slot: [this](QAbstractSocket::SocketError socketError) {
163 Q_D(QCoapClient);
164 d->protocol->d_func()->onConnectionError(error: socketError);
165 });
166
167 connect(sender: d->protocol, signal: &QCoapProtocol::finished,
168 context: this, slot: &QCoapClient::finished);
169 connect(sender: d->protocol, signal: &QCoapProtocol::responseToMulticastReceived,
170 context: this, slot: &QCoapClient::responseToMulticastReceived);
171 connect(sender: d->protocol, signal: &QCoapProtocol::error,
172 context: this, slot: &QCoapClient::error);
173}
174
175/*!
176 \internal
177
178 Sets the client's connection to \a customConnection.
179*/
180void QCoapClientPrivate::setConnection(QCoapConnection *customConnection)
181{
182 Q_Q(QCoapClient);
183
184 delete connection;
185 connection = customConnection;
186
187 q->connect(sender: connection, signal: &QCoapConnection::readyRead, context: protocol,
188 slot: [this](const QByteArray &data, const QHostAddress &sender) {
189 protocol->d_func()->onFrameReceived(data, sender);
190 });
191 q->connect(sender: connection, signal: &QCoapConnection::error, context: protocol,
192 slot: [this](QAbstractSocket::SocketError socketError) {
193 protocol->d_func()->onConnectionError(error: socketError);
194 });
195}
196
197/*!
198 Destroys the QCoapClient object and frees up any
199 resources. Note that QCoapReply objects that are returned from
200 this class have the QCoapClient set as their parents, which means that
201 they will be deleted along with it.
202*/
203QCoapClient::~QCoapClient()
204{
205 qDeleteAll(c: findChildren<QCoapReply *>(aName: QString(), options: Qt::FindDirectChildrenOnly));
206}
207
208/*!
209 Sends the \a request using the GET method and returns a new QCoapReply object.
210
211 \sa post(), put(), deleteResource(), observe(), discover()
212*/
213QCoapReply *QCoapClient::get(const QCoapRequest &request)
214{
215 Q_D(QCoapClient);
216
217 QCoapRequest copyRequest = QCoapRequestPrivate::createRequest(other: request, method: QtCoap::Method::Get,
218 isSecure: d->connection->isSecure());
219 return d->sendRequest(request: copyRequest);
220}
221
222/*!
223 \overload
224
225 Sends a GET request to \a url and returns a new QCoapReply object.
226
227 \sa post(), put(), deleteResource(), observe(), discover()
228*/
229QCoapReply *QCoapClient::get(const QUrl &url)
230{
231 QCoapRequest request(url);
232 return get(request);
233}
234
235/*!
236 Sends the \a request using the PUT method and returns a new QCoapReply
237 object. Uses \a data as the payload for this request. If \a data is empty,
238 the payload of the \a request will be used.
239
240 \sa get(), post(), deleteResource(), observe(), discover()
241*/
242QCoapReply *QCoapClient::put(const QCoapRequest &request, const QByteArray &data)
243{
244 Q_D(QCoapClient);
245
246 QCoapRequest copyRequest = QCoapRequestPrivate::createRequest(other: request, method: QtCoap::Method::Put,
247 isSecure: d->connection->isSecure());
248 if (!data.isEmpty())
249 copyRequest.setPayload(data);
250 return d->sendRequest(request: copyRequest);
251}
252
253/*!
254 \overload
255
256 Sends the \a request using the PUT method and returns a new QCoapReply
257 object. Uses \a device content as the payload for this request.
258 A null device is treated as empty content, in which case the payload of the
259 \a request will be used.
260
261 \note The device has to be open and readable before calling this function.
262
263 \sa get(), post(), deleteResource(), observe(), discover()
264*/
265QCoapReply *QCoapClient::put(const QCoapRequest &request, QIODevice *device)
266{
267 return put(request, data: device ? device->readAll() : QByteArray());
268}
269
270/*!
271 \overload
272
273 Sends a PUT request to \a url and returns a new QCoapReply object.
274 Uses \a data as the payload for this request.
275
276 \sa get(), post(), deleteResource(), observe(), discover()
277*/
278QCoapReply *QCoapClient::put(const QUrl &url, const QByteArray &data)
279{
280 return put(request: QCoapRequest(url), data);
281}
282
283/*!
284 Sends the \a request using the POST method and returns a new QCoapReply
285 object. Uses \a data as the payload for this request. If \a data is empty,
286 the payload of the \a request will be used.
287
288 \sa get(), put(), deleteResource(), observe(), discover()
289*/
290QCoapReply *QCoapClient::post(const QCoapRequest &request, const QByteArray &data)
291{
292 Q_D(QCoapClient);
293
294 QCoapRequest copyRequest = QCoapRequestPrivate::createRequest(other: request, method: QtCoap::Method::Post,
295 isSecure: d->connection->isSecure());
296 if (!data.isEmpty())
297 copyRequest.setPayload(data);
298 return d->sendRequest(request: copyRequest);
299}
300
301/*!
302 \overload
303
304 Sends the \a request using the POST method and returns a new QCoapReply
305 object. Uses \a device content as the payload for this request.
306 A null device is treated as empty content.
307
308 \note The device has to be open and readable before calling this function.
309
310 \sa get(), put(), deleteResource(), observe(), discover()
311*/
312QCoapReply *QCoapClient::post(const QCoapRequest &request, QIODevice *device)
313{
314 if (!device)
315 return nullptr;
316
317 return post(request, data: device->readAll());
318}
319
320/*!
321 \overload
322
323 Sends a POST request to \a url and returns a new QCoapReply object.
324 Uses \a data as the payload for this request.
325
326 \sa get(), put(), deleteResource(), observe(), discover()
327*/
328QCoapReply *QCoapClient::post(const QUrl &url, const QByteArray &data)
329{
330 return post(request: QCoapRequest(url), data);
331}
332
333/*!
334 Sends the \a request using the DELETE method and returns a new QCoapReply
335 object.
336
337 \sa get(), put(), post(), observe(), discover()
338 */
339QCoapReply *QCoapClient::deleteResource(const QCoapRequest &request)
340{
341 Q_D(QCoapClient);
342
343 QCoapRequest copyRequest = QCoapRequestPrivate::createRequest(other: request, method: QtCoap::Method::Delete,
344 isSecure: d->connection->isSecure());
345 return d->sendRequest(request: copyRequest);
346}
347
348/*!
349 \overload
350
351 Sends a DELETE request to the target \a url.
352
353 \sa get(), put(), post(), observe(), discover()
354 */
355QCoapReply *QCoapClient::deleteResource(const QUrl &url)
356{
357 return deleteResource(request: QCoapRequest(url));
358}
359
360/*!
361 \overload
362
363 Discovers the resources available at the endpoints which have joined
364 the \a group at the given \a port. Returns a new QCoapResourceDiscoveryReply
365 object which emits the \l QCoapResourceDiscoveryReply::discovered() signal whenever
366 a response arrives. The \a group is one of the CoAP multicast group addresses
367 and defaults to QtCoap::AllCoapNodesIPv4.
368
369 Discovery path defaults to "/.well-known/core", but can be changed
370 by passing a different path to \a discoveryPath. Discovery is described in
371 \l{https://tools.ietf.org/html/rfc6690#section-1.2.1}{RFC 6690}.
372
373 \sa get(), post(), put(), deleteResource(), observe()
374*/
375QCoapResourceDiscoveryReply *QCoapClient::discover(QtCoap::MulticastGroup group, int port,
376 const QString &discoveryPath)
377{
378 Q_D(QCoapClient);
379
380 QString base;
381 switch (group) {
382 case QtCoap::MulticastGroup::AllCoapNodesIPv4:
383 base = QStringLiteral("224.0.1.187");
384 break;
385 case QtCoap::MulticastGroup::AllCoapNodesIPv6LinkLocal:
386 base = QStringLiteral("ff02::fd");
387 break;
388 case QtCoap::MulticastGroup::AllCoapNodesIPv6SiteLocal:
389 base = QStringLiteral("ff05::fd");
390 break;
391 }
392
393 QUrl discoveryUrl;
394 discoveryUrl.setHost(host: base);
395 discoveryUrl.setPath(path: discoveryPath);
396 discoveryUrl.setPort(port);
397
398 QCoapRequest request = QCoapRequestPrivate::createRequest(other: QCoapRequest(discoveryUrl),
399 method: QtCoap::Method::Get,
400 isSecure: d->connection->isSecure());
401
402 return d->sendDiscovery(request);
403}
404
405/*!
406 Discovers the resources available at the given \a url and returns
407 a new QCoapResourceDiscoveryReply object which emits the
408 \l QCoapResourceDiscoveryReply::discovered() signal whenever the response
409 arrives.
410
411 Discovery path defaults to "/.well-known/core", but can be changed
412 by passing a different path to \a discoveryPath. Discovery is described in
413 \l{https://tools.ietf.org/html/rfc6690#section-1.2.1}{RFC 6690}.
414
415 \sa get(), post(), put(), deleteResource(), observe()
416*/
417QCoapResourceDiscoveryReply *QCoapClient::discover(const QUrl &url, const QString &discoveryPath)
418{
419 Q_D(QCoapClient);
420
421 QUrl discoveryUrl(url);
422 discoveryUrl.setPath(path: url.path() + discoveryPath);
423
424 QCoapRequest request = QCoapRequestPrivate::createRequest(other: QCoapRequest(discoveryUrl),
425 method: QtCoap::Method::Get,
426 isSecure: d->connection->isSecure());
427 return d->sendDiscovery(request);
428}
429
430/*!
431 Sends a request to observe the target \a request and returns
432 a new QCoapReply object which emits the \l QCoapReply::notified()
433 signal whenever a new notification arrives.
434
435 \sa cancelObserve(), get(), post(), put(), deleteResource(), discover()
436*/
437QCoapReply *QCoapClient::observe(const QCoapRequest &request)
438{
439 Q_D(QCoapClient);
440
441 QCoapRequest copyRequest = QCoapRequestPrivate::createRequest(other: request, method: QtCoap::Method::Get,
442 isSecure: d->connection->isSecure());
443 copyRequest.enableObserve();
444
445 return get(request: copyRequest);
446}
447
448/*!
449 \overload
450
451 Sends a request to observe the target \a url and returns
452 a new QCoapReply object which emits the \l QCoapReply::notified()
453 signal whenever a new notification arrives.
454
455 \sa cancelObserve(), get(), post(), put(), deleteResource(), discover()
456*/
457QCoapReply *QCoapClient::observe(const QUrl &url)
458{
459 return observe(request: QCoapRequest(url));
460}
461
462/*!
463 \overload
464
465 Cancels the observation of a resource using the reply \a notifiedReply returned by
466 the observe() method.
467
468 \sa observe()
469*/
470void QCoapClient::cancelObserve(QCoapReply *notifiedReply)
471{
472 Q_D(QCoapClient);
473 QMetaObject::invokeMethod(obj: d->protocol, member: "cancelObserve",
474 Q_ARG(QPointer<QCoapReply>, QPointer<QCoapReply>(notifiedReply)));
475}
476
477/*!
478 \overload
479
480 Cancels the observation of a resource identified by the \a url.
481
482 \sa observe()
483*/
484void QCoapClient::cancelObserve(const QUrl &url)
485{
486 Q_D(QCoapClient);
487 const auto adjustedUrl = QCoapRequestPrivate::adjustedUrl(url, secure: d->connection->isSecure());
488 QMetaObject::invokeMethod(obj: d->protocol, member: "cancelObserve", Q_ARG(QUrl, adjustedUrl));
489}
490
491/*!
492 Closes the open sockets and connections to free the transport.
493
494 \note In the secure mode this needs to be called before changing
495 the security configuration or connecting to another server.
496
497 \sa setSecurityConfiguration()
498*/
499void QCoapClient::disconnect()
500{
501 Q_D(QCoapClient);
502 QMetaObject::invokeMethod(obj: d->connection, member: "disconnect", c: Qt::QueuedConnection);
503}
504
505/*!
506 \internal
507
508 Sends the CoAP \a request to its own URL and returns a new QCoapReply
509 object.
510*/
511QCoapReply *QCoapClientPrivate::sendRequest(const QCoapRequest &request)
512{
513 Q_Q(QCoapClient);
514
515 // Prepare the reply
516 QCoapReply *reply = QCoapReplyPrivate::createCoapReply(request, parent: q);
517
518 if (!send(reply)) {
519 delete reply;
520 return nullptr;
521 }
522
523 return reply;
524}
525
526/*!
527 \internal
528
529 Sends the CoAP \a request to its own URL and returns a
530 new QCoapResourceDiscoveryReply object.
531*/
532QCoapResourceDiscoveryReply *QCoapClientPrivate::sendDiscovery(const QCoapRequest &request)
533{
534 Q_Q(QCoapClient);
535
536 // Prepare the reply
537 QCoapResourceDiscoveryReply *reply = new QCoapResourceDiscoveryReply(request, q);
538
539 if (!send(reply)) {
540 delete reply;
541 return nullptr;
542 }
543
544 return reply;
545}
546
547/*!
548 \internal
549
550 Connect to the reply and use the protocol to send it.
551*/
552bool QCoapClientPrivate::send(QCoapReply *reply)
553{
554 const auto scheme = connection->isSecure() ? QLatin1String("coaps") : QLatin1String("coap");
555 if (reply->request().url().scheme() != scheme) {
556 qCWarning(lcCoapClient, "Failed to send request, URL has an incorrect scheme.");
557 return false;
558 }
559
560 if (!QCoapRequestPrivate::isUrlValid(url: reply->request().url())) {
561 qCWarning(lcCoapClient, "Failed to send request for an invalid URL.");
562 return false;
563 }
564
565 // According to https://tools.ietf.org/html/rfc7252#section-8.1,
566 // multicast requests MUST be Non-confirmable.
567 if (QHostAddress(reply->url().host()).isMulticast()
568 && reply->request().type() == QCoapMessage::Type::Confirmable) {
569 qCWarning(lcCoapClient, "Failed to send request, "
570 "multicast requests must be non-confirmable.");
571 return false;
572 }
573
574 QMetaObject::invokeMethod(obj: protocol, member: "sendRequest", c: Qt::QueuedConnection,
575 Q_ARG(QPointer<QCoapReply>, QPointer<QCoapReply>(reply)),
576 Q_ARG(QCoapConnection *, connection));
577
578 return true;
579}
580
581/*!
582 Sets the security configuration parameters from \a configuration.
583 Configuration will be ignored if the QtCoap::NoSecurity mode is used.
584
585 \note This method must be called before the handshake starts. If you need
586 to change the security configuration after establishing a secure connection
587 with the server, the client needs to be disconnected first.
588
589 \sa disconnect()
590*/
591void QCoapClient::setSecurityConfiguration(const QCoapSecurityConfiguration &configuration)
592{
593 Q_D(QCoapClient);
594
595 QMetaObject::invokeMethod(obj: d->connection, member: "setSecurityConfiguration", c: Qt::QueuedConnection,
596 Q_ARG(QCoapSecurityConfiguration, configuration));
597}
598
599/*!
600 Sets the maximum block size used by the protocol to \a blockSize
601 when sending requests and receiving replies. The block size must be
602 a power of two.
603*/
604void QCoapClient::setBlockSize(quint16 blockSize)
605{
606 Q_D(QCoapClient);
607
608 QMetaObject::invokeMethod(obj: d->protocol, member: "setBlockSize", c: Qt::QueuedConnection,
609 Q_ARG(quint16, blockSize));
610}
611
612/*!
613 Sets the QUdpSocket socket \a option to \a value.
614*/
615void QCoapClient::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value)
616{
617 Q_D(QCoapClient);
618
619 QMetaObject::invokeMethod(obj: d->connection, member: "setSocketOption", c: Qt::QueuedConnection,
620 Q_ARG(QAbstractSocket::SocketOption, option),
621 Q_ARG(QVariant, value));
622}
623
624/*!
625 Sets the \c MAX_SERVER_RESPONSE_DELAY value to \a responseDelay in milliseconds.
626 The default is 250 seconds.
627
628 As defined in \l {RFC 7390 - Section 2.5}, \c MAX_SERVER_RESPONSE_DELAY is the expected
629 maximum response delay over all servers that the client can send a multicast request to.
630*/
631void QCoapClient::setMaximumServerResponseDelay(uint responseDelay)
632{
633 Q_D(QCoapClient);
634 QMetaObject::invokeMethod(obj: d->protocol, member: "setMaximumServerResponseDelay", c: Qt::QueuedConnection,
635 Q_ARG(uint, responseDelay));
636}
637
638/*!
639 Sets the \c ACK_TIMEOUT value defined in \l {RFC 7252 - Section 4.2} to
640 \a ackTimeout in milliseconds. The default is 2000 ms.
641
642 This timeout only applies to confirmable messages. The actual timeout for
643 reliable transmissions is a random value between \c ACK_TIMEOUT and
644 \c {ACK_TIMEOUT * ACK_RANDOM_FACTOR}.
645
646 \sa setAckRandomFactor()
647*/
648void QCoapClient::setAckTimeout(uint ackTimeout)
649{
650 Q_D(QCoapClient);
651 QMetaObject::invokeMethod(obj: d->protocol, member: "setAckTimeout", c: Qt::QueuedConnection,
652 Q_ARG(uint, ackTimeout));
653}
654
655/*!
656 Sets the \c ACK_RANDOM_FACTOR value defined in \l {RFC 7252 - Section 4.2},
657 to \a ackRandomFactor. This value should be greater than or equal to 1.
658 The default is 1.5.
659
660 \sa setAckTimeout()
661*/
662void QCoapClient::setAckRandomFactor(double ackRandomFactor)
663{
664 Q_D(QCoapClient);
665 QMetaObject::invokeMethod(obj: d->protocol, member: "setAckRandomFactor", c: Qt::QueuedConnection,
666 Q_ARG(double, ackRandomFactor));
667}
668
669/*!
670 Sets the \c MAX_RETRANSMIT value defined in \l {RFC 7252 - Section 4.2}
671 to \a maximumRetransmitCount. This value should be less than or equal to 25.
672 The default is 4.
673*/
674void QCoapClient::setMaximumRetransmitCount(uint maximumRetransmitCount)
675{
676 Q_D(QCoapClient);
677 QMetaObject::invokeMethod(obj: d->protocol, member: "setMaximumRetransmitCount", c: Qt::QueuedConnection,
678 Q_ARG(uint, maximumRetransmitCount));
679}
680
681/*!
682 Sets the minimum token size to \a tokenSize in bytes. For security reasons it is
683 recommended to use tokens with a length of at least 4 bytes. The default value for
684 this parameter is 4 bytes.
685*/
686void QCoapClient::setMinimumTokenSize(int tokenSize)
687{
688 Q_D(QCoapClient);
689 QMetaObject::invokeMethod(obj: d->protocol, member: "setMinimumTokenSize", c: Qt::QueuedConnection,
690 Q_ARG(int, tokenSize));
691}
692
693QT_END_NAMESPACE
694

source code of qtcoap/src/coap/qcoapclient.cpp