mirror of https://github.com/qt/qtcoap.git
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>
This commit is contained in:
parent
18cccdd7ba
commit
c0c8dfbfeb
|
@ -39,6 +39,8 @@ CoapHandler::CoapHandler(QObject *parent) : QObject(parent)
|
|||
{
|
||||
connect(&m_coapClient, &QCoapClient::finished, this, &CoapHandler::onFinished);
|
||||
connect(&m_coapClient, &QCoapClient::error, this, &CoapHandler::onError);
|
||||
connect(&m_coapClient, &QCoapClient::responseToMulticastReceived,
|
||||
this, &CoapHandler::onResponseToMulticast);
|
||||
}
|
||||
|
||||
bool CoapHandler::runGet(const QUrl &url)
|
||||
|
@ -108,6 +110,14 @@ void CoapHandler::onDiscovered(QCoapDiscoveryReply *reply, QVector<QCoapResource
|
|||
qDebug() << "Discovered resource: " << res.path() << res.title();
|
||||
}
|
||||
|
||||
void CoapHandler::onResponseToMulticast(QCoapReply *reply, const QCoapMessage& message)
|
||||
{
|
||||
if (reply->errorReceived() == QtCoap::NoError)
|
||||
qDebug() << "Got a response for multicast request: " << message.payload();
|
||||
else
|
||||
qDebug() << "Multicast request failed";
|
||||
}
|
||||
|
||||
void CoapHandler::onError(QCoapReply *reply, QtCoap::Error error)
|
||||
{
|
||||
if (reply)
|
||||
|
|
|
@ -62,6 +62,7 @@ public Q_SLOTS:
|
|||
void onFinished(QCoapReply *reply);
|
||||
void onNotified(QCoapReply *reply, QCoapMessage message);
|
||||
void onDiscovered(QCoapDiscoveryReply *reply, QVector<QCoapResource> resources);
|
||||
void onResponseToMulticast(QCoapReply *reply, const QCoapMessage& message);
|
||||
void onError(QCoapReply *reply, QtCoap::Error error);
|
||||
|
||||
private:
|
||||
|
|
|
@ -268,6 +268,17 @@ QtCoap::Error QtCoap::responseCodeError(QtCoap::ResponseCode code)
|
|||
\sa error(), QCoapReply::finished(), QCoapReply::error()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\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)
|
||||
|
||||
|
@ -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));
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -556,6 +577,17 @@ bool QCoapInternalRequest::isObserveCancelled() const
|
|||
return d->observeCancelled;
|
||||
}
|
||||
|
||||
/*!
|
||||
\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.
|
||||
|
@ -638,6 +670,24 @@ void QCoapInternalRequest::setMaxTransmissionWait(int duration)
|
|||
d->maxTransmitWaitTimer->setInterval(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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -191,6 +211,29 @@ void QCoapProtocolPrivate::onRequestMaxTransmissionSpanReached(QCoapInternalRequ
|
|||
onRequestError(request, QtCoap::TimeOutError);
|
||||
}
|
||||
|
||||
/*!
|
||||
\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
|
||||
|
||||
|
@ -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));
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -72,6 +72,9 @@ private Q_SLOTS:
|
|||
void discover();
|
||||
void observe_data();
|
||||
void observe();
|
||||
void confirmableMulticast();
|
||||
void multicast();
|
||||
void multicast_blockwise();
|
||||
};
|
||||
|
||||
class QCoapQUdpConnectionSocketTestsPrivate : public QCoapQUdpConnectionPrivate
|
||||
|
@ -132,6 +135,42 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class QCoapConnectionMulticastTests : public QCoapConnection
|
||||
{
|
||||
public:
|
||||
~QCoapConnectionMulticastTests() override = default;
|
||||
|
||||
void bind(const QString &host, quint16 port) override
|
||||
{
|
||||
Q_UNUSED(host);
|
||||
Q_UNUSED(port);
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
void writeData(const QByteArray &data, const QString &host, quint16 port) override
|
||||
{
|
||||
Q_UNUSED(data);
|
||||
Q_UNUSED(host);
|
||||
Q_UNUSED(port);
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
class QCoapClientForMulticastTests : public QCoapClient
|
||||
{
|
||||
public:
|
||||
QCoapClientForMulticastTests() :
|
||||
QCoapClient(new QCoapProtocol, new QCoapConnectionMulticastTests)
|
||||
{}
|
||||
|
||||
QCoapConnection *connection()
|
||||
{
|
||||
QCoapClientPrivate *privateClient = static_cast<QCoapClientPrivate *>(d_func());
|
||||
return privateClient->connection;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Helper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -733,6 +772,62 @@ void tst_QCoapClient::observe()
|
|||
}
|
||||
}
|
||||
|
||||
void tst_QCoapClient::confirmableMulticast()
|
||||
{
|
||||
QCoapClient client;
|
||||
const auto reply = client.get(QCoapRequest("224.0.1.187", QCoapMessage::Confirmable));
|
||||
QVERIFY2(!reply, "Confirmable multicast request didn't fail as expected.");
|
||||
}
|
||||
|
||||
void tst_QCoapClient::multicast()
|
||||
{
|
||||
QCoapClientForMulticastTests client;
|
||||
QCoapRequest request = QCoapRequest(QUrl("224.0.1.187"));
|
||||
request.setToken("abc");
|
||||
QCoapReply *reply = client.get(request);
|
||||
QVERIFY(reply);
|
||||
|
||||
// Simulate sending unicast responses to the multicast request
|
||||
emit client.connection()->readyRead("SE\xAD/abc\xC0\xFFReply1", QHostAddress("10.20.30.40"));
|
||||
emit client.connection()->readyRead("SE\xAD/abc\xC0\xFFReply2", QHostAddress("10.20.30.41"));
|
||||
|
||||
QSignalSpy spyMulticastResponse(&client, &QCoapClient::responseToMulticastReceived);
|
||||
QTRY_COMPARE(spyMulticastResponse.count(), 2);
|
||||
|
||||
QCoapMessage message1 = qvariant_cast<QCoapMessage>(spyMulticastResponse.at(0).at(1));
|
||||
QCOMPARE(message1.payload(), "Reply1");
|
||||
|
||||
QCoapMessage message2 = qvariant_cast<QCoapMessage>(spyMulticastResponse.at(1).at(1));
|
||||
QCOMPARE(message2.payload(), "Reply2");
|
||||
}
|
||||
|
||||
void tst_QCoapClient::multicast_blockwise()
|
||||
{
|
||||
QCoapClientForMulticastTests client;
|
||||
QCoapRequest request = QCoapRequest(QUrl("224.0.1.187"));
|
||||
request.setToken("abc");
|
||||
QCoapReply *reply = client.get(request);
|
||||
QVERIFY(reply);
|
||||
|
||||
QHostAddress host1("10.20.30.40");
|
||||
QHostAddress host2("10.20.30.41");
|
||||
|
||||
// Simulate blockwise transfer responses coming from two different hosts
|
||||
emit client.connection()->readyRead("SE#}abc\xC0\xB1\x1D\xFFReply1", host1);
|
||||
emit client.connection()->readyRead("SE#}abc\xC0\xB1\x1D\xFFReply3", host2);
|
||||
emit client.connection()->readyRead("SE#~abc\xC0\xB1%\xFFReply2", host1);
|
||||
emit client.connection()->readyRead("SE#~abc\xC0\xB1%\xFFReply4", host2);
|
||||
|
||||
QSignalSpy spyMulticastResponse(&client, &QCoapClient::responseToMulticastReceived);
|
||||
QTRY_COMPARE(spyMulticastResponse.count(), 2);
|
||||
|
||||
QCoapMessage message1 = qvariant_cast<QCoapMessage>(spyMulticastResponse.at(0).at(1));
|
||||
QCOMPARE(message1.payload(), "Reply1Reply2");
|
||||
|
||||
QCoapMessage message2 = qvariant_cast<QCoapMessage>(spyMulticastResponse.at(1).at(1));
|
||||
QCOMPARE(message2.payload(), "Reply3Reply4");
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QCoapClient)
|
||||
|
||||
#include "tst_qcoapclient.moc"
|
||||
|
|
|
@ -49,6 +49,8 @@ private Q_SLOTS:
|
|||
void urlOptions();
|
||||
void invalidUrls_data();
|
||||
void invalidUrls();
|
||||
void isMulticast_data();
|
||||
void isMulticast();
|
||||
};
|
||||
|
||||
void tst_QCoapInternalRequest::requestToFrame_data()
|
||||
|
@ -290,6 +292,28 @@ void tst_QCoapInternalRequest::invalidUrls()
|
|||
QVERIFY(internalRequest.message()->options().empty());
|
||||
}
|
||||
|
||||
void tst_QCoapInternalRequest::isMulticast_data()
|
||||
{
|
||||
QTest::addColumn<QString>("url");
|
||||
QTest::addColumn<bool>("result");
|
||||
|
||||
QTest::newRow("ipv4_multicast") << QString("coap://224.0.1.187") << true;
|
||||
QTest::newRow("ipv4_multicast_resource") << QString("coap://224.0.1.187/path") << true;
|
||||
QTest::newRow("ipv6_multicast_link_local") << "coap://[ff02::fd]" << true;
|
||||
QTest::newRow("ipv6_multicast_site_local") << "coap://[ff05::fd]" << true;
|
||||
QTest::newRow("not_multicast") << QString("coap://127.0.0.1") << false;
|
||||
}
|
||||
|
||||
void tst_QCoapInternalRequest::isMulticast()
|
||||
{
|
||||
QFETCH(QString, url);
|
||||
QFETCH(bool, result);
|
||||
|
||||
const QCoapRequest request(url);
|
||||
const QCoapInternalRequest internalRequest(request);
|
||||
QCOMPARE(internalRequest.isMulticast(), result);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
class tst_QCoapInternalRequest : public QObject
|
||||
|
|
Loading…
Reference in New Issue