Revision control

++ // Copyright (C) 2017 Witekio.
++ // Copyright (C) 2018 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qcoapclient_p.h"
++ #include "qcoapprotocol_p.h"
++ #include "qcoapreply.h"
++ #include "qcoapresourcediscoveryreply.h"
++ #include "qcoapnamespace.h"
++ #include "qcoapsecurityconfiguration.h"
++ #include "qcoapqudpconnection_p.h"
++ #include "qcoaprequest_p.h"
++ #include "qcoapreply_p.h"
++ #include <QtCore/qiodevice.h>
++ #include <QtCore/qurl.h>
++ #include <QtCore/qloggingcategory.h>
++ #include <QtNetwork/qudpsocket.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_STATIC_LOGGING_CATEGORY(lcCoapClient, "qt.coap.client")
++
++ QCoapClientPrivate::QCoapClientPrivate(QCoapProtocol *protocol, QCoapConnection *connection)
++ : protocol(protocol)
++ , connection(connection)
++ , workerThread(new QThread)
++ {
++ protocol->moveToThread(workerThread);
++ connection->moveToThread(workerThread);
++ workerThread->start();
++ }
++
++ QCoapClientPrivate::~QCoapClientPrivate()
++ {
++ // protocol and connection (and their children, such as the retransmission
++ // timers and the UDP socket) live in workerThread. They must be destroyed in
++ // that thread, not here.
++ protocol->deleteLater();
++ connection->deleteLater();
++ workerThread->quit();
++ workerThread->wait();
++ delete workerThread;
++ }
++
++ /*!
++ \class QCoapClient
++ \inmodule QtCoap
++
++ \brief The QCoapClient class allows the application to
++ send CoAP requests and receive replies.
++
++ \reentrant
++
++ The QCoapClient class contains signals that get triggered when the
++ reply of a sent request has arrived.
++
++ The application can use a QCoapClient to send requests over a CoAP
++ network. It provides functions for standard requests: each returns a QCoapReply object,
++ to which the response data shall be delivered; this can be read when the finished()
++ signal arrives.
++
++ A simple request can be sent with:
++ \code
++ QCoapClient *client = new QCoapClient(this);
++ connect(client, &QCoapClient::finished, this, &TestClass::slotFinished);
++ client->get(QCoapRequest(Qurl("coap://coap.me/test")));
++ \endcode
++
++ \note After processing of the request has finished, it is the responsibility
++ of the user to delete the QCoapReply object at an appropriate time. Do not
++ directly delete it inside the slot connected to finished(). You can use the
++ deleteLater() function.
++
++ You can also use an \e observe request. This can be used as above, or more
++ conveniently with the QCoapReply::notified() signal:
++ \code
++ QCoapRequest request = QCoapRequest(Qurl("coap://coap.me/obs"));
++ QCoapReply *reply = client->observe(request);
++ connect(reply, &QCoapReply::notified, this, &TestClass::slotNotified);
++ \endcode
++
++ And the observation can be cancelled with:
++ \code
++ client->cancelObserve(reply);
++ \endcode
++
++ When a reply arrives, the QCoapClient emits a finished() signal.
++
++ \note For a discovery request, the returned object is a QCoapResourceDiscoveryReply.
++ It can be used the same way as a QCoapReply but contains also a list of
++ resources.
++
++ \section1 Security Considerations
++
++ During a block-wise transfer, the response payload is accumulated
++ in memory until the transfer completes, and QtCoap does not impose an upper
++ bound on the accumulated size. A malicious or malfunctioning server that
++ keeps sending blocks without ever signalling the final one can therefore
++ drive the client to exhaust memory. The accumulated size is not observable while
++ the transfer is in progress, so an application cannot bound it directly. Perform
++ block-wise exchanges only with trusted servers, and preferably on a trusted or
++ isolated network.
++
++ \section2 Multicast
++
++ DTLS protects a connection between two endpoints and therefore cannot apply to
++ multicast, where more than 2 endpoints may be present. Because of this, a
++ multicast request and its replies are neither encrypted nor authenticated.
++
++ Every host in the group may answer a multicast request. In CoAP these hosts
++ are all legitimate owners of the resource and are expected to serve
++ equivalent replies. When a reply is sent block by block, the remaining
++ blocks are retrieved by unicast from whichever host answered.
++
++ CoAP multicast is designed with the assumption that the network is secured
++ and every node in the group is trusted. Use multicast only in scenarios that
++ fulfill this assumption.
++
++ \sa QCoapRequest, QCoapReply, QCoapResourceDiscoveryReply
++ */
++
++ /*!
++ \fn void QCoapClient::finished(QCoapReply *reply)
++
++ This signal is emitted along with the \l QCoapReply::finished() signal
++ whenever a CoAP reply is received, after either a success or an error.
++ The \a reply parameter will contain a pointer to the reply that has just
++ been received.
++
++ \sa error(), QCoapReply::finished(), QCoapReply::error()
++ */
++
++ /*!
++ \fn void QCoapClient::responseToMulticastReceived(QCoapReply *reply,
++ const QCoapMessage &message,
++ const QHostAddress &sender)
++
++ This signal is emitted when a unicast response to a multicast request
++ arrives. The \a reply parameter contains a pointer to the reply that has just
++ been received, \a message contains the payload and the message details,
++ and \a sender contains the sender address.
++
++ \note Multicast requests are unauthenticated; see \l {Multicast}
++ {multicast security considerations} for the trust model this relies on.
++
++ \sa error(), QCoapReply::finished(), QCoapReply::error()
++ */
++
++ /*!
++ \fn void QCoapClient::error(QCoapReply *reply, QtCoap::Error error)
++
++ This signal is emitted whenever an error occurs. The \a reply parameter
++ can be \nullptr if the error is not related to a specific QCoapReply. The
++ \a error parameter contains the error code.
++
++ \sa finished(), QCoapReply::error(), QCoapReply::finished()
++ */
++
++ /*!
++ Constructs a QCoapClient object for the given \a securityMode and
++ sets \a parent as the parent object.
++
++ The default for \a securityMode is QtCoap::NoSecurity, which
++ disables security.
++ */
++ QCoapClient::QCoapClient(QtCoap::SecurityMode securityMode, QObject *parent) :
++ QObject(*new QCoapClientPrivate(new QCoapProtocol, new QCoapQUdpConnection(securityMode)),
++ parent)
++ {
++ Q_D(QCoapClient);
++
++ qRegisterMetaType<QCoapReply *>();
++ qRegisterMetaType<QCoapMessage>();
++ qRegisterMetaType<QPointer<QCoapReply>>();
++ qRegisterMetaType<QPointer<QCoapResourceDiscoveryReply>>();
++ qRegisterMetaType<QCoapConnection *>();
++ qRegisterMetaType<QtCoap::Error>();
++ qRegisterMetaType<QtCoap::ResponseCode>();
++ qRegisterMetaType<QtCoap::Method>();
++ qRegisterMetaType<QtCoap::SecurityMode>();
++ qRegisterMetaType<QtCoap::MulticastGroup>();
++ // Requires a name, as this is a typedef
++ qRegisterMetaType<QCoapToken>("QCoapToken");
++ qRegisterMetaType<QCoapMessageId>("QCoapMessageId");
++ qRegisterMetaType<QAbstractSocket::SocketOption>();
++
++ connect(d->connection, &QCoapConnection::readyRead, d->protocol,
++ [this](const QByteArray &data, const QHostAddress &sender) {
++ Q_D(QCoapClient);
++ d->protocol->d_func()->onFrameReceived(data, sender);
++ });
++ connect(d->connection, &QCoapConnection::error, d->protocol,
++ [this](QAbstractSocket::SocketError socketError) {
++ Q_D(QCoapClient);
++ d->protocol->d_func()->onConnectionError(socketError);
++ });
++
++ connect(d->protocol, &QCoapProtocol::finished,
++ this, &QCoapClient::finished);
++ connect(d->protocol, &QCoapProtocol::responseToMulticastReceived,
++ this, &QCoapClient::responseToMulticastReceived);
++ connect(d->protocol, &QCoapProtocol::error,
++ this, &QCoapClient::error);
++ }
++
++ /*!
++ \internal
++
++ Sets the client's connection to \a customConnection.
++ */
++ void QCoapClientPrivate::setConnection(QCoapConnection *customConnection)
++ {
++ Q_Q(QCoapClient);
++
++ if (connection) {
++ // The connection lives in workerThread, let that thread delete it.
++ connection->disconnect();
++ connection->deleteLater();
++ }
++ connection = customConnection;
++
++ q->connect(connection, &QCoapConnection::readyRead, protocol,
++ [this](const QByteArray &data, const QHostAddress &sender) {
++ protocol->d_func()->onFrameReceived(data, sender);
++ });
++ q->connect(connection, &QCoapConnection::error, protocol,
++ [this](QAbstractSocket::SocketError socketError) {
++ protocol->d_func()->onConnectionError(socketError);
++ });
++ }
++
++ /*!
++ Destroys the QCoapClient object and frees up any
++ resources. Note that QCoapReply objects that are returned from
++ this class have the QCoapClient set as their parents, which means that
++ they will be deleted along with it.
++ */
++ QCoapClient::~QCoapClient()
++ {
++ qDeleteAll(findChildren<QCoapReply *>(QString(), Qt::FindDirectChildrenOnly));
++ }
++
++ /*!
++ Sends the \a request using the GET method and returns a new QCoapReply object.
++
++ \sa post(), put(), deleteResource(), observe(), discover()
++ */
++ QCoapReply *QCoapClient::get(const QCoapRequest &request)
++ {
++ Q_D(QCoapClient);
++
++ QCoapRequest copyRequest = QCoapRequestPrivate::createRequest(request, QtCoap::Method::Get,
++ d->connection->isSecure());
++ return d->sendRequest(copyRequest);
++ }
++
++ /*!
++ \overload
++
++ Sends a GET request to \a url and returns a new QCoapReply object.
++
++ \sa post(), put(), deleteResource(), observe(), discover()
++ */
++ QCoapReply *QCoapClient::get(const QUrl &url)
++ {
++ QCoapRequest request(url);
++ return get(request);
++ }
++
++ /*!
++ Sends the \a request using the PUT method and returns a new QCoapReply
++ object. Uses \a data as the payload for this request. If \a data is empty,
++ the payload of the \a request will be used.
++
++ \sa get(), post(), deleteResource(), observe(), discover()
++ */
++ QCoapReply *QCoapClient::put(const QCoapRequest &request, const QByteArray &data)
++ {
++ Q_D(QCoapClient);
++
++ QCoapRequest copyRequest = QCoapRequestPrivate::createRequest(request, QtCoap::Method::Put,
++ d->connection->isSecure());
++ if (!data.isEmpty())
++ copyRequest.setPayload(data);
++ return d->sendRequest(copyRequest);
++ }
++
++ /*!
++ \overload
++
++ Sends the \a request using the PUT method and returns a new QCoapReply
++ object. Uses \a device content as the payload for this request.
++ A null device is treated as empty content, in which case the payload of the
++ \a request will be used.
++
++ \note The device has to be open and readable before calling this function.
++
++ \sa get(), post(), deleteResource(), observe(), discover()
++ */
++ QCoapReply *QCoapClient::put(const QCoapRequest &request, QIODevice *device)
++ {
++ return put(request, device ? device->readAll() : QByteArray());
++ }
++
++ /*!
++ \overload
++
++ Sends a PUT request to \a url and returns a new QCoapReply object.
++ Uses \a data as the payload for this request.
++
++ \sa get(), post(), deleteResource(), observe(), discover()
++ */
++ QCoapReply *QCoapClient::put(const QUrl &url, const QByteArray &data)
++ {
++ return put(QCoapRequest(url), data);
++ }
++
++ /*!
++ Sends the \a request using the POST method and returns a new QCoapReply
++ object. Uses \a data as the payload for this request. If \a data is empty,
++ the payload of the \a request will be used.
++
++ \sa get(), put(), deleteResource(), observe(), discover()
++ */
++ QCoapReply *QCoapClient::post(const QCoapRequest &request, const QByteArray &data)
++ {
++ Q_D(QCoapClient);
++
++ QCoapRequest copyRequest = QCoapRequestPrivate::createRequest(request, QtCoap::Method::Post,
++ d->connection->isSecure());
++ if (!data.isEmpty())
++ copyRequest.setPayload(data);
++ return d->sendRequest(copyRequest);
++ }
++
++ /*!
++ \overload
++
++ Sends the \a request using the POST method and returns a new QCoapReply
++ object. Uses \a device content as the payload for this request.
++ A null device is treated as empty content, in which case the payload of the
++ \a request will be used.
++
++ \note The device has to be open and readable before calling this function.
++
++ \sa get(), put(), deleteResource(), observe(), discover()
++ */
++ QCoapReply *QCoapClient::post(const QCoapRequest &request, QIODevice *device)
++ {
++ return post(request, device ? device->readAll() : QByteArray());
++ }
++
++ /*!
++ \overload
++
++ Sends a POST request to \a url and returns a new QCoapReply object.
++ Uses \a data as the payload for this request.
++
++ \sa get(), put(), deleteResource(), observe(), discover()
++ */
++ QCoapReply *QCoapClient::post(const QUrl &url, const QByteArray &data)
++ {
++ return post(QCoapRequest(url), data);
++ }
++
++ /*!
++ Sends the \a request using the DELETE method and returns a new QCoapReply
++ object.
++
++ \sa get(), put(), post(), observe(), discover()
++ */
++ QCoapReply *QCoapClient::deleteResource(const QCoapRequest &request)
++ {
++ Q_D(QCoapClient);
++
++ QCoapRequest copyRequest = QCoapRequestPrivate::createRequest(request, QtCoap::Method::Delete,
++ d->connection->isSecure());
++ return d->sendRequest(copyRequest);
++ }
++
++ /*!
++ \overload
++
++ Sends a DELETE request to the target \a url.
++
++ \sa get(), put(), post(), observe(), discover()
++ */
++ QCoapReply *QCoapClient::deleteResource(const QUrl &url)
++ {
++ return deleteResource(QCoapRequest(url));
++ }
++
++ /*!
++ \overload
++
++ Discovers the resources available at the endpoints which have joined
++ the \a group at the given \a port. Returns a new QCoapResourceDiscoveryReply
++ object which emits the \l QCoapResourceDiscoveryReply::discovered() signal whenever
++ a response arrives. The \a group is one of the CoAP multicast group addresses
++ and defaults to QtCoap::AllCoapNodesIPv4.
++
++ Discovery path defaults to "/.well-known/core", but can be changed
++ by passing a different path to \a discoveryPath. Discovery is described in
++
++ \sa get(), post(), put(), deleteResource(), observe()
++ */
++ QCoapResourceDiscoveryReply *QCoapClient::discover(QtCoap::MulticastGroup group, int port,
++ const QString &discoveryPath)
++ {
++ Q_D(QCoapClient);
++
++ QString base;
++ switch (group) {
++ case QtCoap::MulticastGroup::AllCoapNodesIPv4:
++ base = QStringLiteral("224.0.1.187");
++ break;
++ case QtCoap::MulticastGroup::AllCoapNodesIPv6LinkLocal:
++ base = QStringLiteral("ff02::fd");
++ break;
++ case QtCoap::MulticastGroup::AllCoapNodesIPv6SiteLocal:
++ base = QStringLiteral("ff05::fd");
++ break;
++ }
++
++ QUrl discoveryUrl;
++ discoveryUrl.setHost(base);
++ discoveryUrl.setPath(discoveryPath);
++ discoveryUrl.setPort(port);
++
++ QCoapRequest request = QCoapRequestPrivate::createRequest(QCoapRequest(discoveryUrl),
++ QtCoap::Method::Get,
++ d->connection->isSecure());
++
++ return d->sendDiscovery(request);
++ }
++
++ /*!
++ Discovers the resources available at the given \a url and returns
++ a new QCoapResourceDiscoveryReply object which emits the
++ \l QCoapResourceDiscoveryReply::discovered() signal whenever the response
++ arrives.
++
++ Discovery path defaults to "/.well-known/core", but can be changed
++ by passing a different path to \a discoveryPath. Discovery is described in
++
++ \sa get(), post(), put(), deleteResource(), observe()
++ */
++ QCoapResourceDiscoveryReply *QCoapClient::discover(const QUrl &url, const QString &discoveryPath)
++ {
++ Q_D(QCoapClient);
++
++ QUrl discoveryUrl(url);
++ discoveryUrl.setPath(url.path() + discoveryPath);
++
++ QCoapRequest request = QCoapRequestPrivate::createRequest(QCoapRequest(discoveryUrl),
++ QtCoap::Method::Get,
++ d->connection->isSecure());
++ return d->sendDiscovery(request);
++ }
++
++ /*!
++ Sends a request to observe the target \a request and returns
++ a new QCoapReply object which emits the \l QCoapReply::notified()
++ signal whenever a new notification arrives.
++
++ \sa cancelObserve(), get(), post(), put(), deleteResource(), discover()
++ */
++ QCoapReply *QCoapClient::observe(const QCoapRequest &request)
++ {
++ Q_D(QCoapClient);
++
++ QCoapRequest copyRequest = QCoapRequestPrivate::createRequest(request, QtCoap::Method::Get,
++ d->connection->isSecure());
++ copyRequest.enableObserve();
++
++ return d->sendRequest(copyRequest);
++ }
++
++ /*!
++ \overload
++
++ Sends a request to observe the target \a url and returns
++ a new QCoapReply object which emits the \l QCoapReply::notified()
++ signal whenever a new notification arrives.
++
++ \sa cancelObserve(), get(), post(), put(), deleteResource(), discover()
++ */
++ QCoapReply *QCoapClient::observe(const QUrl &url)
++ {
++ return observe(QCoapRequest(url));
++ }
++
++ /*!
++ \overload
++
++ Cancels the observation of a resource using the reply \a notifiedReply returned by
++ the observe() method.
++
++ \sa observe()
++ */
++ void QCoapClient::cancelObserve(QCoapReply *notifiedReply)
++ {
++ Q_D(QCoapClient);
++ using P = QPointer<QCoapReply>;
++ QMetaObject::invokeMethod(d->protocol, qOverload<P>(&QCoapProtocol::cancelObserve),
++ Qt::QueuedConnection, P(notifiedReply));
++ }
++
++ /*!
++ \overload
++
++ Cancels the observation of a resource identified by the \a url.
++
++ \sa observe()
++ */
++ void QCoapClient::cancelObserve(const QUrl &url)
++ {
++ Q_D(QCoapClient);
++ const auto adjustedUrl = QCoapRequestPrivate::adjustedUrl(url, d->connection->isSecure());
++ QMetaObject::invokeMethod(d->protocol, qOverload<const QUrl &>(&QCoapProtocol::cancelObserve),
++ Qt::QueuedConnection, adjustedUrl);
++ }
++
++ /*!
++ Closes the open sockets and connections to free the transport.
++
++ \note In the secure mode this needs to be called before changing
++ the security configuration or connecting to another server.
++
++ \sa setSecurityConfiguration()
++ */
++ void QCoapClient::disconnect()
++ {
++ Q_D(QCoapClient);
++ QMetaObject::invokeMethod(d->connection, &QCoapConnection::disconnect, Qt::QueuedConnection);
++ }
++
++ /*!
++ \internal
++
++ Sends the CoAP \a request to its own URL and returns a new QCoapReply
++ object.
++ */
++ QCoapReply *QCoapClientPrivate::sendRequest(const QCoapRequest &request)
++ {
++ Q_Q(QCoapClient);
++
++ // Prepare the reply
++ QCoapReply *reply = QCoapReplyPrivate::createCoapReply(request, q);
++
++ if (!send(reply)) {
++ delete reply;
++ return nullptr;
++ }
++
++ return reply;
++ }
++
++ /*!
++ \internal
++
++ Sends the CoAP \a request to its own URL and returns a
++ new QCoapResourceDiscoveryReply object.
++ */
++ QCoapResourceDiscoveryReply *QCoapClientPrivate::sendDiscovery(const QCoapRequest &request)
++ {
++ Q_Q(QCoapClient);
++
++ // Prepare the reply
++ QCoapResourceDiscoveryReply *reply = new QCoapResourceDiscoveryReply(request, q);
++
++ if (!send(reply)) {
++ delete reply;
++ return nullptr;
++ }
++
++ return reply;
++ }
++
++ /*!
++ \internal
++
++ Connect to the reply and use the protocol to send it.
++ */
++ bool QCoapClientPrivate::send(QCoapReply *reply)
++ {
++ const auto scheme = connection->isSecure() ? QLatin1String("coaps") : QLatin1String("coap");
++ if (reply->request().url().scheme() != scheme) {
++ qCWarning(lcCoapClient, "Failed to send request, URL has an incorrect scheme.");
++ return false;
++ }
++
++ if (!QCoapRequestPrivate::isUrlValid(reply->request().url())) {
++ qCWarning(lcCoapClient, "Failed to send request for an invalid URL.");
++ return false;
++ }
++
++ // multicast requests MUST be Non-confirmable.
++ if (QHostAddress(reply->url().host()).isMulticast()
++ && reply->request().type() == QCoapMessage::Type::Confirmable) {
++ qCWarning(lcCoapClient, "Failed to send request, "
++ "multicast requests must be non-confirmable.");
++ return false;
++ }
++
++ QMetaObject::invokeMethod(protocol, &QCoapProtocol::sendRequest, Qt::QueuedConnection,
++ QPointer<QCoapReply>(reply), connection);
++
++ return true;
++ }
++
++ /*!
++ Sets the security configuration parameters from \a configuration.
++ Configuration will be ignored if the QtCoap::NoSecurity mode is used.
++
++ \note This method must be called before the handshake starts. If you need
++ to change the security configuration after establishing a secure connection
++ with the server, the client needs to be disconnected first.
++
++ \sa disconnect()
++ */
++ void QCoapClient::setSecurityConfiguration(const QCoapSecurityConfiguration &configuration)
++ {
++ Q_D(QCoapClient);
++
++ QMetaObject::invokeMethod(d->connection, &QCoapConnection::setSecurityConfiguration,
++ Qt::QueuedConnection, configuration);
++ }
++
++ /*!
++ Sets the maximum block size used by the protocol to \a blockSize
++ when sending requests and receiving replies. The block size must be
++ a power of two.
++ */
++ void QCoapClient::setBlockSize(quint16 blockSize)
++ {
++ Q_D(QCoapClient);
++
++ QMetaObject::invokeMethod(d->protocol, &QCoapProtocol::setBlockSize, Qt::QueuedConnection,
++ blockSize);
++ }
++
++ /*!
++ Sets the QUdpSocket socket \a option to \a value.
++ */
++ void QCoapClient::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value)
++ {
++ Q_D(QCoapClient);
++
++ auto conn = qobject_cast<QCoapQUdpConnection *>(d->connection);
++ if (!conn)
++ return;
++ QMetaObject::invokeMethod(conn, &QCoapQUdpConnection::setSocketOption, Qt::QueuedConnection,
++ option, value);
++ }
++
++ /*!
++ Sets the \c MAX_SERVER_RESPONSE_DELAY value to \a responseDelay in milliseconds.
++ The default is 250 seconds.
++
++ As defined in \l {RFC 7390 - Section 2.5}, \c MAX_SERVER_RESPONSE_DELAY is the expected
++ maximum response delay over all servers that the client can send a multicast request to.
++ */
++ void QCoapClient::setMaximumServerResponseDelay(uint responseDelay)
++ {
++ Q_D(QCoapClient);
++ QMetaObject::invokeMethod(d->protocol, &QCoapProtocol::setMaximumServerResponseDelay,
++ Qt::QueuedConnection, responseDelay);
++ }
++
++ /*!
++ Sets the \c ACK_TIMEOUT value defined in \l {RFC 7252 - Section 4.2} to
++ \a ackTimeout in milliseconds. The default is 2000 ms.
++
++ This timeout only applies to confirmable messages. The actual timeout for
++ reliable transmissions is a random value between \c ACK_TIMEOUT and
++ \c {ACK_TIMEOUT * ACK_RANDOM_FACTOR}.
++
++ \sa setAckRandomFactor()
++ */
++ void QCoapClient::setAckTimeout(uint ackTimeout)
++ {
++ Q_D(QCoapClient);
++ QMetaObject::invokeMethod(d->protocol, &QCoapProtocol::setAckTimeout,
++ Qt::QueuedConnection, ackTimeout);
++ }
++
++ /*!
++ Sets the \c ACK_RANDOM_FACTOR value defined in \l {RFC 7252 - Section 4.2},
++ to \a ackRandomFactor. This value should be greater than or equal to 1.
++ The default is 1.5.
++
++ \sa setAckTimeout()
++ */
++ void QCoapClient::setAckRandomFactor(double ackRandomFactor)
++ {
++ Q_D(QCoapClient);
++ QMetaObject::invokeMethod(d->protocol, &QCoapProtocol::setAckRandomFactor,
++ Qt::QueuedConnection, ackRandomFactor);
++ }
++
++ /*!
++ Sets the \c MAX_RETRANSMIT value defined in \l {RFC 7252 - Section 4.2}
++ to \a maximumRetransmitCount. This value should be less than or equal to 25.
++ The default is 4.
++ */
++ void QCoapClient::setMaximumRetransmitCount(uint maximumRetransmitCount)
++ {
++ Q_D(QCoapClient);
++ QMetaObject::invokeMethod(d->protocol, &QCoapProtocol::setMaximumRetransmitCount,
++ Qt::QueuedConnection, maximumRetransmitCount);
++ }
++
++ /*!
++ Sets the minimum token size to \a tokenSize in bytes. For security reasons it is
++ recommended to use tokens with a length of at least 4 bytes. The default value for
++ this parameter is 4 bytes.
++ */
++ void QCoapClient::setMinimumTokenSize(int tokenSize)
++ {
++ Q_D(QCoapClient);
++ QMetaObject::invokeMethod(d->protocol, &QCoapProtocol::setMinimumTokenSize,
++ Qt::QueuedConnection, tokenSize);
++ }
++
++ #if QT_CONFIG(networkinterface)
++ /*!
++ \property QCoapClient::bindInterface
++ \brief the network interface to be used by the socket
++ \since 6.11
++
++ The default value is an \l {QNetworkInterface::isValid()}{invalid}
++ QNetworkInterface object, meaning that incoming packets will be accepted
++ from all network interfaces. Similarly, all network interfaces can be used
++ to send outgoing packets.
++
++ When a valid network interface is specified, incoming packets will only be
++ accepted from that interface. Similarly, outgoing packets will only be sent
++ using that interface.
++
++ Changing the property only has an effect the next time the client binds to
++ the socket, so make sure to call \l disconnect() if there was any prior
++ communication.
++ */
++ void QCoapClient::setBindInterface(const QNetworkInterface &iface)
++ {
++ Q_D(QCoapClient);
++ // QNI does not have operator==(). We use index() to distinguish the
++ // interfaces, because this is the value provided by the OS, and it is
++ // unlikely to change.
++ // We also need to check isValid(), because an invalid interface has
++ // index() == 0, which might clash with an existing interface index.
++ if (iface.isValid() == d->interface.isValid()
++ && iface.index() == d->interface.index()) {
++ return;
++ }
++ d->interface = iface;
++ QMetaObject::invokeMethod(d->connection, &QCoapConnection::setUdpNetworkInterface,
++ Qt::QueuedConnection, iface);
++ Q_EMIT bindInterfaceChanged(iface);
++ }
++
++ QNetworkInterface QCoapClient::bindInterface() const
++ {
++ Q_D(const QCoapClient);
++ return d->interface;
++ }
++ #endif
++
++ QT_END_NAMESPACE