aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSona Kurazyan <sona.kurazyan@qt.io>2019-02-11 17:35:15 +0100
committerSona Kurazyan <sona.kurazyan@qt.io>2019-02-21 16:17:51 +0000
commitc0c8dfbfebec7614cf52d21d2ea6523456912596 (patch)
tree2577597aaf3f65c3d15a2dbc8cc488c7f9e98ad9 /src
parent18cccdd7baa1939ab32eac86d4f6b88be59cec14 (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.cpp22
-rw-r--r--src/coap/qcoapclient.h2
-rw-r--r--src/coap/qcoapinternalrequest.cpp62
-rw-r--r--src/coap/qcoapinternalrequest_p.h5
-rw-r--r--src/coap/qcoapprotocol.cpp78
-rw-r--r--src/coap/qcoapprotocol.h1
-rw-r--r--src/coap/qcoapprotocol_p.h5
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);