diff options
author | Sona Kurazyan <sona.kurazyan@qt.io> | 2019-02-11 17:35:15 +0100 |
---|---|---|
committer | Sona Kurazyan <sona.kurazyan@qt.io> | 2019-02-21 16:17:51 +0000 |
commit | c0c8dfbfebec7614cf52d21d2ea6523456912596 (patch) | |
tree | 2577597aaf3f65c3d15a2dbc8cc488c7f9e98ad9 /src | |
parent | 18cccdd7baa1939ab32eac86d4f6b88be59cec14 (diff) |
Add support for multicast CoAP requests
Change-Id: I9cf6d4f97c863c232b17bc8e560c6b62c3f39624
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/coap/qcoapclient.cpp | 22 | ||||
-rw-r--r-- | src/coap/qcoapclient.h | 2 | ||||
-rw-r--r-- | src/coap/qcoapinternalrequest.cpp | 62 | ||||
-rw-r--r-- | src/coap/qcoapinternalrequest_p.h | 5 | ||||
-rw-r--r-- | src/coap/qcoapprotocol.cpp | 78 | ||||
-rw-r--r-- | src/coap/qcoapprotocol.h | 1 | ||||
-rw-r--r-- | src/coap/qcoapprotocol_p.h | 5 |
7 files changed, 159 insertions, 16 deletions
diff --git a/src/coap/qcoapclient.cpp b/src/coap/qcoapclient.cpp index 4b36b80..60d13c0 100644 --- a/src/coap/qcoapclient.cpp +++ b/src/coap/qcoapclient.cpp @@ -269,6 +269,17 @@ QtCoap::Error QtCoap::responseCodeError(QtCoap::ResponseCode code) */ /*! + \fn void QCoapClient::responseToMulticastReceived(QCoapReply *reply, + const QCoapMessage& message) + + 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, and \a message contains the payload and the message details. + + \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 @@ -336,6 +347,8 @@ QCoapClient::QCoapClient(QCoapProtocol *protocol, QCoapConnection *connection, Q connect(d->protocol, &QCoapProtocol::finished, this, &QCoapClient::finished); + connect(d->protocol, &QCoapProtocol::responseToMulticastReceived, + this, &QCoapClient::responseToMulticastReceived); connect(d->protocol, &QCoapProtocol::error, this, &QCoapClient::error); } @@ -678,6 +691,15 @@ bool QCoapClientPrivate::send(QCoapReply *reply) return false; } + // According to https://tools.ietf.org/html/rfc7252#section-8.1, + // multicast requests MUST be Non-confirmable. + if (QHostAddress(reply->url().host()).isMulticast() + && reply->request().type() == QCoapMessage::Confirmable) { + qWarning("QCoapClient: Failed to send request, " + "multicast requests must be non-confirmable."); + return false; + } + QMetaObject::invokeMethod(protocol, "sendRequest", Qt::QueuedConnection, Q_ARG(QPointer<QCoapReply>, QPointer<QCoapReply>(reply)), Q_ARG(QCoapConnection *, connection)); diff --git a/src/coap/qcoapclient.h b/src/coap/qcoapclient.h index 9a49e5d..7b0c636 100644 --- a/src/coap/qcoapclient.h +++ b/src/coap/qcoapclient.h @@ -45,6 +45,7 @@ class QCoapRequest; class QCoapProtocol; class QCoapConnection; class QCoapSecurityConfiguration; +class QCoapMessage; class QIODevice; class QCoapClientPrivate; @@ -90,6 +91,7 @@ public: Q_SIGNALS: void finished(QCoapReply *reply); + void responseToMulticastReceived(QCoapReply *reply, const QCoapMessage& message); void error(QCoapReply *reply, QtCoap::Error error); protected: diff --git a/src/coap/qcoapinternalrequest.cpp b/src/coap/qcoapinternalrequest.cpp index 0d1d62b..a28c2d4 100644 --- a/src/coap/qcoapinternalrequest.cpp +++ b/src/coap/qcoapinternalrequest.cpp @@ -65,6 +65,10 @@ QCoapInternalRequest::QCoapInternalRequest(QObject *parent) : d->maxTransmitWaitTimer = new QTimer(this); connect(d->maxTransmitWaitTimer, &QTimer::timeout, [this]() { emit maxTransmissionSpanReached(this); }); + + d->multicastExpireTimer = new QTimer(this); + connect(d->multicastExpireTimer, &QTimer::timeout, this, + [this]() { emit multicastRequestExpired(this); }); } /*! @@ -485,16 +489,33 @@ void QCoapInternalRequest::restartTransmission() /*! \internal - Marks the transmission as not running, after a successful reception, or an - error. It resets the retranmission count and stops all timeout timers. + + Starts the timer for keeping the multicast request \e alive. +*/ +void QCoapInternalRequest::startMulticastTransmission() +{ + Q_ASSERT(isMulticast()); + + Q_D(QCoapInternalRequest); + d->multicastExpireTimer->start(); +} + +/*! + \internal + Marks the transmission as not running, after a successful reception or an + error. It resets the retransmission count if needed and stops all timeout timers. */ void QCoapInternalRequest::stopTransmission() { Q_D(QCoapInternalRequest); - d->transmissionInProgress = false; - d->retransmissionCounter = 0; - d->maxTransmitWaitTimer->stop(); - d->timeoutTimer->stop(); + if (isMulticast()) { + d->multicastExpireTimer->stop(); + } else { + d->transmissionInProgress = false; + d->retransmissionCounter = 0; + d->maxTransmitWaitTimer->stop(); + d->timeoutTimer->stop(); + } } /*! @@ -558,6 +579,17 @@ bool QCoapInternalRequest::isObserveCancelled() const /*! \internal + + Returns \c true if the request is multicast, returns \c false otherwise. +*/ +bool QCoapInternalRequest::isMulticast() const +{ + const QHostAddress hostAddress(targetUri().host()); + return hostAddress.isMulticast(); +} + +/*! + \internal Returns the value of the retransmission counter. */ int QCoapInternalRequest::retransmissionCounter() const @@ -640,6 +672,24 @@ void QCoapInternalRequest::setMaxTransmissionWait(int duration) /*! \internal + + Sets the timeout interval in milliseconds for keeping the multicast request + \e alive. + + In the unicast case, receiving a response means that the request is finished. + In the multicast case it is not known how many responses will be received, so + the response, along with its token, will be kept for + NON_LIFETIME + MAX_LATENCY + MAX_SERVER_RESPONSE_DELAY time, as suggested + in \l {RFC 7390 - Section 2.5}. +*/ +void QCoapInternalRequest::setMulticastTimeout(uint responseDelay) +{ + Q_D(QCoapInternalRequest); + d->multicastExpireTimer->setInterval(static_cast<int>(responseDelay)); +} + +/*! + \internal Decode the \a uri provided and returns a QCoapOption. */ QCoapOption QCoapInternalRequest::uriHostOption(const QUrl &uri) const diff --git a/src/coap/qcoapinternalrequest_p.h b/src/coap/qcoapinternalrequest_p.h index 0959f47..e08953e 100644 --- a/src/coap/qcoapinternalrequest_p.h +++ b/src/coap/qcoapinternalrequest_p.h @@ -85,6 +85,7 @@ public: QtCoap::Method method() const; bool isObserve() const; bool isObserveCancelled() const; + bool isMulticast() const; QCoapConnection *connection() const; int retransmissionCounter() const; void setMethod(QtCoap::Method method); @@ -94,12 +95,15 @@ public: void setTargetUri(QUrl targetUri); void setTimeout(uint timeout); void setMaxTransmissionWait(int timeout); + void setMulticastTimeout(uint responseDelay); void restartTransmission(); + void startMulticastTransmission(); void stopTransmission(); Q_SIGNALS: void timeout(QCoapInternalRequest*); void maxTransmissionSpanReached(QCoapInternalRequest*); + void multicastRequestExpired(QCoapInternalRequest*); protected: QCoapOption uriHostOption(const QUrl &uri) const; @@ -123,6 +127,7 @@ public: int retransmissionCounter = 0; QTimer *timeoutTimer = nullptr; QTimer *maxTransmitWaitTimer = nullptr; + QTimer *multicastExpireTimer = nullptr; bool observeCancelled = false; bool transmissionInProgress = false; diff --git a/src/coap/qcoapprotocol.cpp b/src/coap/qcoapprotocol.cpp index 2edbf69..f2e430f 100644 --- a/src/coap/qcoapprotocol.cpp +++ b/src/coap/qcoapprotocol.cpp @@ -96,6 +96,19 @@ void QCoapProtocol::sendRequest(QPointer<QCoapReply> reply, QCoapConnection *con internalRequest->setMaxTransmissionWait(maxTransmitWait()); connect(reply, &QCoapReply::finished, this, &QCoapProtocol::finished); + if (internalRequest->isMulticast()) { + connect(internalRequest.data(), &QCoapInternalRequest::multicastRequestExpired, this, + [this](QCoapInternalRequest *request) { + Q_D(QCoapProtocol); + d->onMulticastRequestExpired(request); + }); + // The timeout interval is chosen based on + // https://tools.ietf.org/html/rfc7390#section-2.5 + internalRequest->setMulticastTimeout(nonConfirmLifetime() + + static_cast<uint>(maxLatency()) + + maxServerResponseDelay()); + } + // Set a unique Message Id and Token QCoapMessage *requestMessage = internalRequest->message(); internalRequest->setMessageId(d->generateUniqueMessageId()); @@ -136,9 +149,11 @@ void QCoapProtocol::sendRequest(QPointer<QCoapReply> reply, QCoapConnection *con /*! \internal - Encodes and sends the given \a request to the server. + Encodes and sends the given \a request to the server. If \a host is not empty, + sends the request to \a host, instead of using the host address from the request. + The \a host parameter is relevant for multicast blockwise transfers. */ -void QCoapProtocolPrivate::sendRequest(QCoapInternalRequest *request) const +void QCoapProtocolPrivate::sendRequest(QCoapInternalRequest *request, const QString& host) const { Q_Q(const QCoapProtocol); Q_ASSERT(QThread::currentThread() == q->thread()); @@ -148,10 +163,15 @@ void QCoapProtocolPrivate::sendRequest(QCoapInternalRequest *request) const return; } - request->restartTransmission(); + if (request->isMulticast()) + request->startMulticastTransmission(); + else + request->restartTransmission(); + QByteArray requestFrame = request->toQByteArray(); QUrl uri = request->targetUri(); - request->connection()->sendRequest(requestFrame, uri.host(), static_cast<quint16>(uri.port())); + const auto& hostAddress = host.isEmpty() ? uri.host() : host; + request->connection()->sendRequest(requestFrame, hostAddress, static_cast<quint16>(uri.port())); } /*! @@ -194,6 +214,29 @@ void QCoapProtocolPrivate::onRequestMaxTransmissionSpanReached(QCoapInternalRequ /*! \internal + This slot is called when the multicast request expires, meaning that no + more responses are expected for the multicast \a request. As a result of this + call, the request token is \e {freed up} and the \l finished() signal is emitted. +*/ +void QCoapProtocolPrivate::onMulticastRequestExpired(QCoapInternalRequest *request) +{ + Q_ASSERT(request->isMulticast()); + + request->stopTransmission(); + QPointer<QCoapReply> userReply = userReplyForToken(request->token()); + if (userReply) { + QMetaObject::invokeMethod(userReply, "_q_setFinished", Qt::QueuedConnection, + Q_ARG(QtCoap::Error, QtCoap::NoError)); + } else { + qWarning().nospace() << "QtCoap: Reply for token '" << request->token() + << "' is not registered, reply is null."; + } + forgetExchange(request); +} + +/*! + \internal + Method triggered when a request fails. */ void QCoapProtocolPrivate::onRequestError(QCoapInternalRequest *request, QCoapInternalReply *reply) @@ -268,7 +311,8 @@ void QCoapProtocolPrivate::onFrameReceived(const QByteArray &data, const QHostAd return; } - request->stopTransmission(); + if (!request->isMulticast()) + request->stopTransmission(); addReply(request->token(), reply); if (QtCoap::isError(reply->responseCode())) { @@ -293,9 +337,13 @@ void QCoapProtocolPrivate::onFrameReceived(const QByteArray &data, const QHostAd } else if (reply->hasMoreBlocksToReceive()) { request->setToRequestBlock(reply->currentBlockNumber() + 1, reply->blockSize()); request->setMessageId(generateUniqueMessageId()); - sendRequest(request); + // In case of multicast blockwise transfers, according to + // https://tools.ietf.org/html/rfc7959#section-2.8, further blocks should be retrieved + // via unicast requests. So instead of using the multicast request address, we need + // to use the sender address for getting the next blocks. + sendRequest(request, sender.toString()); } else { - onLastMessageReceived(request); + onLastMessageReceived(request, sender); } } @@ -395,7 +443,8 @@ QCoapInternalRequest *QCoapProtocolPrivate::findRequestByMessageId(quint16 messa associated QCoapReply and emits the \l{QCoapProtocol::finished(QCoapReply*)}{finished(QCoapReply*)} signal. */ -void QCoapProtocolPrivate::onLastMessageReceived(QCoapInternalRequest *request) +void QCoapProtocolPrivate::onLastMessageReceived(QCoapInternalRequest *request, + const QHostAddress &sender) { Q_ASSERT(request); if (!request || !isRequestRegistered(request)) @@ -423,6 +472,16 @@ void QCoapProtocolPrivate::onLastMessageReceived(QCoapInternalRequest *request) // Merge payloads for blockwise transfers if (replies.size() > 1) { + + // In multicast case, multiple hosts will reply to the same multicast request. + // We are interested only in replies coming from the sender. + if (request->isMulticast()) { + replies.erase(std::remove_if(replies.begin(), replies.end(), + [sender](QSharedPointer<QCoapInternalReply> reply) { + return reply->senderAddress() != sender; + }), replies.end()); + } + std::stable_sort(std::begin(replies), std::end(replies), [](QSharedPointer<QCoapInternalReply> a, QSharedPointer<QCoapInternalReply> b) -> bool { return (a->currentBlockNumber() < b->currentBlockNumber()); @@ -452,6 +511,9 @@ void QCoapProtocolPrivate::onLastMessageReceived(QCoapInternalRequest *request) if (request->isObserve()) { QMetaObject::invokeMethod(userReply, "_q_setNotified", Qt::QueuedConnection); forgetExchangeReplies(request->token()); + } else if (request->isMulticast()) { + Q_Q(QCoapProtocol); + emit q->responseToMulticastReceived(userReply, *lastReply->message()); } else { QMetaObject::invokeMethod(userReply, "_q_setFinished", Qt::QueuedConnection, Q_ARG(QtCoap::Error, QtCoap::NoError)); diff --git a/src/coap/qcoapprotocol.h b/src/coap/qcoapprotocol.h index 38fc375..afe9930 100644 --- a/src/coap/qcoapprotocol.h +++ b/src/coap/qcoapprotocol.h @@ -70,6 +70,7 @@ public: Q_SIGNALS: void finished(QCoapReply *reply); + void responseToMulticastReceived(QCoapReply *reply, const QCoapMessage& message); void error(QCoapReply *reply, QtCoap::Error error); public Q_SLOTS: diff --git a/src/coap/qcoapprotocol_p.h b/src/coap/qcoapprotocol_p.h index 6651f37..24fa744 100644 --- a/src/coap/qcoapprotocol_p.h +++ b/src/coap/qcoapprotocol_p.h @@ -70,15 +70,16 @@ public: void sendAcknowledgment(QCoapInternalRequest *request) const; void sendReset(QCoapInternalRequest *request) const; - void sendRequest(QCoapInternalRequest *request) const; + void sendRequest(QCoapInternalRequest *request, const QString& host = QString()) const; - void onLastMessageReceived(QCoapInternalRequest *request); + void onLastMessageReceived(QCoapInternalRequest *request, const QHostAddress &sender); void onRequestError(QCoapInternalRequest *request, QCoapInternalReply *reply); void onRequestError(QCoapInternalRequest *request, QtCoap::Error error, QCoapInternalReply *reply = nullptr); void onRequestTimeout(QCoapInternalRequest *request); void onRequestMaxTransmissionSpanReached(QCoapInternalRequest *request); + void onMulticastRequestExpired(QCoapInternalRequest *request); void onFrameReceived(const QByteArray &data, const QHostAddress &sender); void onConnectionError(QAbstractSocket::SocketError error); void onRequestAborted(const QCoapToken &token); |