Revision control

Copy as Markdown

// 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:critical reason:network-protocol
#include "qcoapprotocol_p.h"
#include "qcoapinternalrequest_p.h"
#include "qcoapinternalreply_p.h"
#include "qcoaprequest_p.h"
#include "qcoapconnection_p.h"
#include "qcoapnamespace_p.h"
#include <QtCore/qrandom.h>
#include <QtCore/qthread.h>
#include <QtCore/qloggingcategory.h>
#include <QtNetwork/qnetworkdatagram.h>
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcCoapProtocol, "qt.coap.protocol")
/*!
\internal
\class QCoapProtocol
\inmodule QtCoap
\brief The QCoapProtocol class handles the logical part of the CoAP
protocol.
\reentrant
The QCoapProtocol is used by the QCoapClient class to handle the logical
part of the protocol. It can encode requests and decode replies. It also
handles what to do when a message is received, along with retransmission of
lost messages.
\sa QCoapClient
*/
/*!
\internal
\fn void QCoapProtocol::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()
*/
/*!
\internal
\fn void QCoapProtocol::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.
\sa error(), QCoapReply::finished(), QCoapReply::error()
*/
/*!
\internal
\fn void QCoapProtocol::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()
*/
/*!
\internal
Constructs a new QCoapProtocol and sets \a parent as the parent object.
*/
QCoapProtocol::QCoapProtocol(QObject *parent) :
QObject(*new QCoapProtocolPrivate, parent)
{
qRegisterMetaType<QCoapInternalRequest *>();
qRegisterMetaType<QHostAddress>();
}
QCoapProtocol::~QCoapProtocol()
{
Q_D(QCoapProtocol);
// Clear table to avoid double deletion from QObject parenting and QSharedPointer.
d->exchangeMap.clear();
}
/*!
\internal
Creates and sets up a new QCoapInternalRequest related to the request
associated to the \a reply. The request will then be sent to the server
using the given \a connection.
*/
void QCoapProtocol::sendRequest(QPointer<QCoapReply> reply, QCoapConnection *connection)
{
Q_D(QCoapProtocol);
Q_ASSERT(QThread::currentThread() == thread());
if (reply.isNull() || reply->request().method() == QtCoap::Method::Invalid
|| !QCoapRequestPrivate::isUrlValid(reply->request().url()))
return;
connect(reply.data(), &QCoapReply::aborted, this, [this](const QCoapToken &token) {
Q_D(QCoapProtocol);
d->onRequestAborted(token);
});
auto internalRequest = QSharedPointer<QCoapInternalRequest>::create(reply->request(), this);
internalRequest->setMaxTransmissionWait(maximumTransmitWait());
connect(reply.data(), &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
internalRequest->setMulticastTimeout(nonConfirmLifetime()
+ maximumLatency()
+ maximumServerResponseDelay());
}
// Set a unique Message Id and Token
QCoapMessage *requestMessage = internalRequest->message();
internalRequest->setMessageId(d->generateUniqueMessageId());
if (internalRequest->token().isEmpty())
internalRequest->setToken(d->generateUniqueToken());
internalRequest->setConnection(connection);
d->registerExchange(requestMessage->token(), reply, internalRequest);
QMetaObject::invokeMethod(reply, "_q_setRunning", Qt::QueuedConnection,
Q_ARG(QCoapToken, requestMessage->token()),
Q_ARG(QCoapMessageId, requestMessage->messageId()));
// Set block size for blockwise request/replies, if specified
if (d->blockSize > 0) {
internalRequest->setToRequestBlock(0, d->blockSize);
if (requestMessage->payload().size() > d->blockSize)
internalRequest->setToSendBlock(0, d->blockSize);
}
if (requestMessage->type() == QCoapMessage::Type::Confirmable) {
const auto minTimeout = minimumTimeout();
const auto maxTimeout = maximumTimeout();
Q_ASSERT(minTimeout <= maxTimeout);
internalRequest->setTimeout(minTimeout == maxTimeout
? minTimeout
: QtCoap::randomGenerator().bounded(minTimeout, maxTimeout));
} else {
internalRequest->setTimeout(maximumTimeout());
}
connect(internalRequest.data(), &QCoapInternalRequest::timeout, this,
[this](QCoapInternalRequest *request) {
Q_D(QCoapProtocol);
d->onRequestTimeout(request);
});
connect(internalRequest.data(), &QCoapInternalRequest::maxTransmissionSpanReached, this,
[this](QCoapInternalRequest *request) {
Q_D(QCoapProtocol);
d->onRequestMaxTransmissionSpanReached(request);
});
d->sendRequest(internalRequest.data());
}
/*!
\internal
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 QString& host) const
{
Q_Q(const QCoapProtocol);
Q_ASSERT(QThread::currentThread() == q->thread());
if (!request || !request->connection()) {
qCWarning(lcCoapProtocol, "Request null or not bound to any connection: aborted.");
return;
}
if (request->isMulticast())
request->startMulticastTransmission();
else
request->restartTransmission();
QByteArray requestFrame = request->toQByteArray();
QUrl uri = request->targetUri();
const auto& hostAddress = host.isEmpty() ? uri.host() : host;
request->connection()->d_func()->sendRequest(requestFrame, hostAddress,
static_cast<quint16>(uri.port()));
}
/*!
\internal
This slot is used to send again the given \a request after a timeout or
aborts the request and transfers a timeout error to the reply.
*/
void QCoapProtocolPrivate::onRequestTimeout(QCoapInternalRequest *request)
{
Q_Q(const QCoapProtocol);
Q_ASSERT(QThread::currentThread() == q->thread());
if (!isRequestRegistered(request))
return;
if (request->message()->type() == QCoapMessage::Type::Confirmable
&& request->retransmissionCounter() < maximumRetransmitCount) {
sendRequest(request);
} else {
onRequestError(request, QtCoap::Error::TimeOut);
}
}
/*!
\internal
This slot is called when the maximum span for this transmission has been
reached, and triggers a timeout error if the request is still running.
*/
void QCoapProtocolPrivate::onRequestMaxTransmissionSpanReached(QCoapInternalRequest *request)
{
Q_Q(const QCoapProtocol);
Q_ASSERT(QThread::currentThread() == q->thread());
if (isRequestRegistered(request))
onRequestError(request, QtCoap::Error::TimeOut);
}
/*!
\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::Error::Ok));
} else {
qCWarning(lcCoapProtocol).nospace() << "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)
{
QtCoap::Error error = QtCoap::errorForResponseCode(reply->responseCode());
onRequestError(request, error, reply);
}
/*!
\internal
Method triggered when a request fails.
*/
void QCoapProtocolPrivate::onRequestError(QCoapInternalRequest *request, QtCoap::Error error,
QCoapInternalReply *reply)
{
Q_Q(QCoapProtocol);
Q_ASSERT(request);
auto userReply = userReplyForToken(request->token());
if (!userReply.isNull()) {
// Set error from content, or error enum
if (reply) {
QMetaObject::invokeMethod(userReply.data(), "_q_setContent", Qt::QueuedConnection,
Q_ARG(QHostAddress, reply->senderAddress()),
Q_ARG(QCoapMessage, *reply->message()),
Q_ARG(QtCoap::ResponseCode, reply->responseCode()));
} else {
QMetaObject::invokeMethod(userReply.data(), "_q_setError", Qt::QueuedConnection,
Q_ARG(QtCoap::Error, error));
}
QMetaObject::invokeMethod(userReply.data(), "_q_setFinished", Qt::QueuedConnection,
Q_ARG(QtCoap::Error, QtCoap::Error::Ok));
}
forgetExchange(request);
emit q->error(userReply.data(), error);
}
/*!
\internal
Decode and process the given \a data received from the \a sender.
*/
void QCoapProtocolPrivate::onFrameReceived(const QByteArray &data, const QHostAddress &sender)
{
Q_Q(const QCoapProtocol);
Q_ASSERT(QThread::currentThread() == q->thread());
QSharedPointer<QCoapInternalReply> reply(decode(data, sender));
if (!reply) {
qCDebug(lcCoapProtocol).nospace() << "QtCoap: Discarding malformed frame from "
<< sender << " (" << data.size()
<< " bytes): not a valid CoAP message";
return;
}
const QCoapMessage *messageReceived = reply->message();
QCoapInternalRequest *request = nullptr;
if (!messageReceived->token().isEmpty())
request = requestForToken(messageReceived->token());
if (!request) {
request = findRequestByMessageId(messageReceived->messageId());
// No matching request found, drop the frame.
if (!request)
return;
}
QHostAddress originalTarget(request->targetUri().host());
if (!originalTarget.isMulticast() && !originalTarget.isEqual(sender)) {
qCDebug(lcCoapProtocol).nospace() << "QtCoap: Answer received from incorrect host ("
<< sender << " instead of "
<< originalTarget << ")";
return;
}
if (!request->isMulticast())
request->stopTransmission();
addReply(request->token(), reply);
if (QtCoap::isError(reply->responseCode())) {
onRequestError(request, reply.data());
return;
}
// Reply when the server asks for an ACK
if (request->isObserveCancelled()) {
// Remove option to ensure that it will stop
request->removeOption(QCoapOption::Observe);
sendReset(request);
} else if (messageReceived->type() == QCoapMessage::Type::Confirmable) {
sendAcknowledgment(request);
}
// Send next block, ask for next block, or process the final reply
if (reply->hasMoreBlocksToSend() && reply->nextBlockToSend() >= 0) {
request->setToSendBlock(static_cast<uint>(reply->nextBlockToSend()), blockSize);
request->setMessageId(generateUniqueMessageId());
sendRequest(request);
} else if (reply->hasMoreBlocksToReceive()) {
request->setToRequestBlock(reply->currentBlockNumber() + 1, reply->blockSize());
request->setMessageId(generateUniqueMessageId());
// 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, sender);
}
}
/*!
\internal
Returns the internal request for the given \a token.
*/
QCoapInternalRequest *QCoapProtocolPrivate::requestForToken(const QCoapToken &token) const
{
auto it = exchangeMap.find(token);
if (it != exchangeMap.constEnd())
return it->request.data();
return nullptr;
}
/*!
\internal
Returns the QCoapReply instance of the given \a token.
*/
QPointer<QCoapReply> QCoapProtocolPrivate::userReplyForToken(const QCoapToken &token) const
{
auto it = exchangeMap.find(token);
if (it != exchangeMap.constEnd())
return it->userReply;
return nullptr;
}
/*!
\internal
Returns the replies for the exchange identified by \a token.
*/
QList<QSharedPointer<QCoapInternalReply>>
QCoapProtocolPrivate::repliesForToken(const QCoapToken &token) const
{
auto it = exchangeMap.find(token);
if (it != exchangeMap.constEnd())
return it->replies;
return {};
}
/*!
\internal
Returns the last reply for the exchange identified by \a token.
*/
QCoapInternalReply *QCoapProtocolPrivate::lastReplyForToken(const QCoapToken &token) const
{
auto it = exchangeMap.find(token);
if (it != exchangeMap.constEnd())
return it->replies.last().data();
return nullptr;
}
/*!
\internal
Finds an internal request matching the given \a reply.
*/
QCoapInternalRequest *QCoapProtocolPrivate::findRequestByUserReply(const QCoapReply *reply) const
{
for (auto it = exchangeMap.constBegin(); it != exchangeMap.constEnd(); ++it) {
if (it->userReply == reply)
return it->request.data();
}
return nullptr;
}
/*!
\internal
Finds an internal request containing the message id \a messageId.
*/
QCoapInternalRequest *QCoapProtocolPrivate::findRequestByMessageId(quint16 messageId) const
{
for (auto it = exchangeMap.constBegin(); it != exchangeMap.constEnd(); ++it) {
if (it->request->message()->messageId() == messageId)
return it->request.data();
}
return nullptr;
}
/*!
\internal
Handles what to do when we received the last block of a reply.
Merges all blocks, removes the request from the map, updates the
associated QCoapReply and emits the
\l{QCoapProtocol::finished(QCoapReply*)}{finished(QCoapReply*)} signal.
*/
void QCoapProtocolPrivate::onLastMessageReceived(QCoapInternalRequest *request,
const QHostAddress &sender)
{
Q_ASSERT(request);
if (!request || !isRequestRegistered(request))
return;
auto replies = repliesForToken(request->token());
Q_ASSERT(!replies.isEmpty());
//! TODO: Change QPointer<QCoapReply> into something independent from
//! User. QSharedPointer(s)?
QPointer<QCoapReply> userReply = userReplyForToken(request->token());
if (userReply.isNull() || replies.isEmpty()
|| (request->isObserve() && request->isObserveCancelled())) {
forgetExchange(request);
return;
}
auto lastReply = replies.last();
// Ignore empty ACK messages
if (lastReply->message()->type() == QCoapMessage::Type::Acknowledgment
&& lastReply->responseCode() == QtCoap::ResponseCode::EmptyMessage) {
exchangeMap[request->token()].replies.takeLast();
return;
}
// 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());
});
QByteArray finalPayload;
int lastBlockNumber = -1;
for (const auto &reply : std::as_const(replies)) {
int currentBlock = static_cast<int>(reply->currentBlockNumber());
QByteArray replyPayload = reply->message()->payload();
if (replyPayload.isEmpty() || currentBlock <= lastBlockNumber)
continue;
finalPayload.append(replyPayload);
lastBlockNumber = currentBlock;
}
lastReply->message()->setPayload(finalPayload);
}
// Forward the answer
QMetaObject::invokeMethod(userReply, "_q_setContent", Qt::QueuedConnection,
Q_ARG(QHostAddress, lastReply->senderAddress()),
Q_ARG(QCoapMessage, *lastReply->message()),
Q_ARG(QtCoap::ResponseCode, lastReply->responseCode()));
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(), sender);
} else {
QMetaObject::invokeMethod(userReply, "_q_setFinished", Qt::QueuedConnection,
Q_ARG(QtCoap::Error, QtCoap::Error::Ok));
forgetExchange(request);
}
}
/*!
\internal
Sends an internal request acknowledging the given \a request, reusing its
URI and connection.
*/
void QCoapProtocolPrivate::sendAcknowledgment(QCoapInternalRequest *request) const
{
Q_Q(const QCoapProtocol);
Q_ASSERT(QThread::currentThread() == q->thread());
QCoapInternalRequest ackRequest;
ackRequest.setTargetUri(request->targetUri());
auto internalReply = lastReplyForToken(request->token());
ackRequest.initEmptyMessage(internalReply->message()->messageId(),
QCoapMessage::Type::Acknowledgment);
ackRequest.setConnection(request->connection());
sendRequest(&ackRequest);
}
/*!
\internal
Sends a Reset message (RST), reusing the details of the given
\a request. A Reset message indicates that a specific message has been
received, but cannot be properly processed.
*/
void QCoapProtocolPrivate::sendReset(QCoapInternalRequest *request) const
{
Q_Q(const QCoapProtocol);
Q_ASSERT(QThread::currentThread() == q->thread());
QCoapInternalRequest resetRequest;
resetRequest.setTargetUri(request->targetUri());
auto lastReply = lastReplyForToken(request->token());
resetRequest.initEmptyMessage(lastReply->message()->messageId(), QCoapMessage::Type::Reset);
resetRequest.setConnection(request->connection());
sendRequest(&resetRequest);
}
/*!
\internal
Cancels resource observation. The QCoapReply::notified() signal will not
be emitted after cancellation.
A Reset (RST) message will be sent at the reception of the next message.
*/
void QCoapProtocol::cancelObserve(QPointer<QCoapReply> reply) const
{
Q_D(const QCoapProtocol);
if (reply.isNull())
return;
QCoapInternalRequest *request = d->requestForToken(reply->request().token());
if (request) {
// Stop here if already cancelled
if (!request->isObserve() || request->isObserveCancelled())
return;
request->setObserveCancelled();
}
// Set as cancelled even if request is not tracked anymore
QMetaObject::invokeMethod(reply, "_q_setObserveCancelled", Qt::QueuedConnection);
}
/*!
\internal
Cancels resource observation for the given \a url. The QCoapReply::notified()
signal will not be emitted after cancellation.
A Reset (RST) message will be sent at the reception of the next message.
*/
void QCoapProtocol::cancelObserve(const QUrl &url) const
{
Q_D(const QCoapProtocol);
for (const auto &exchange : d->exchangeMap) {
Q_ASSERT(exchange.userReply);
if (exchange.userReply->url() == url)
cancelObserve(exchange.userReply);
}
}
/*!
\internal
Returns a currently unused message Id.
*/
quint16 QCoapProtocolPrivate::generateUniqueMessageId() const
{
// TODO: Optimize message id generation for large sets
// TODO: Store used message id for the period specified by CoAP spec
quint16 id = 0;
while (isMessageIdRegistered(id))
id = static_cast<quint16>(QtCoap::randomGenerator().bounded(0x10000));
return id;
}
/*!
\internal
Returns a currently unused token.
*/
QCoapToken QCoapProtocolPrivate::generateUniqueToken() const
{
// TODO: Optimize token generation for large sets
// TODO: Store used token for the period specified by CoAP spec
QCoapToken token;
while (isTokenRegistered(token)) {
quint8 length = static_cast<quint8>(QtCoap::randomGenerator().bounded(minimumTokenSize, 9));
token.resize(length);
quint8 *tokenData = reinterpret_cast<quint8 *>(token.data());
for (int i = 0; i < token.size(); ++i)
tokenData[i] = static_cast<quint8>(QtCoap::randomGenerator().bounded(256));
}
return token;
}
/*!
\internal
Returns a new unmanaged QCoapInternalReply based on \a data and \a sender.
*/
QCoapInternalReply *QCoapProtocolPrivate::decode(const QByteArray &data, const QHostAddress &sender)
{
Q_Q(QCoapProtocol);
QCoapInternalReply *reply = QCoapInternalReply::createFromFrame(data, q);
if (!reply)
return nullptr;
reply->setSenderAddress(sender);
return reply;
}
/*!
\internal
Aborts the request corresponding to the given \a reply. It is triggered
by the destruction of the QCoapReply object or a call to
QCoapReply::abortRequest().
*/
void QCoapProtocolPrivate::onRequestAborted(const QCoapToken &token)
{
QCoapInternalRequest *request = requestForToken(token);
if (!request)
return;
request->stopTransmission();
forgetExchange(request);
}
/*!
\internal
Triggered in case of a connection error.
*/
void QCoapProtocolPrivate::onConnectionError(QAbstractSocket::SocketError socketError)
{
Q_Q(QCoapProtocol);
QtCoap::Error coapError;
switch (socketError) {
case QAbstractSocket::HostNotFoundError :
coapError = QtCoap::Error::HostNotFound;
break;
case QAbstractSocket::AddressInUseError :
coapError = QtCoap::Error::AddressInUse;
break;
default:
coapError = QtCoap::Error::Unknown;
break;
}
emit q->error(nullptr, coapError);
}
/*!
\internal
Registers a new CoAP exchange using \a token.
*/
void QCoapProtocolPrivate::registerExchange(const QCoapToken &token, QCoapReply *reply,
QSharedPointer<QCoapInternalRequest> request)
{
CoapExchangeData data = { reply, request,
QList<QSharedPointer<QCoapInternalReply> >()
};
exchangeMap.insert(token, data);
}
/*!
\internal
Adds \a reply to the list of replies of the exchange identified by
\a token.
Returns \c true if the reply was successfully added. This method will fail
and return \c false if no exchange is associated with the \a token
provided.
*/
bool QCoapProtocolPrivate::addReply(const QCoapToken &token,
QSharedPointer<QCoapInternalReply> reply)
{
if (!isTokenRegistered(token) || !reply) {
qCWarning(lcCoapProtocol).nospace() << "Reply token '" << token
<< "' not registered, or reply is null.";
return false;
}
exchangeMap[token].replies.push_back(reply);
return true;
}
/*!
\internal
Remove the exchange identified by its \a token. This is
typically done when finished or aborted.
It will delete the QCoapInternalRequest and QCoapInternalReplies
associated with the exchange.
Returns \c true if the exchange was found and removed, \c false otherwise.
*/
bool QCoapProtocolPrivate::forgetExchange(const QCoapToken &token)
{
return exchangeMap.remove(token) > 0;
}
/*!
\internal
Remove the exchange using a request.
\sa forgetExchange(const QCoapToken &)
*/
bool QCoapProtocolPrivate::forgetExchange(const QCoapInternalRequest *request)
{
return forgetExchange(request->token());
}
/*!
\internal
Remove all replies for the exchange corresponding to \a token.
*/
bool QCoapProtocolPrivate::forgetExchangeReplies(const QCoapToken &token)
{
auto it = exchangeMap.find(token);
if (it == exchangeMap.end())
return false;
it->replies.clear();
return true;
}
/*!
\internal
Returns \c true if the \a token is reserved or in use; returns \c false if
this token can be used to identify a new exchange.
*/
bool QCoapProtocolPrivate::isTokenRegistered(const QCoapToken &token) const
{
// Reserved for empty messages and uninitialized tokens
if (token == QByteArray())
return true;
return exchangeMap.contains(token);
}
/*!
\internal
Returns \c true if the \a request is present in a currently registered
exchange.
*/
bool QCoapProtocolPrivate::isRequestRegistered(const QCoapInternalRequest *request) const
{
for (auto it = exchangeMap.constBegin(); it != exchangeMap.constEnd(); ++it) {
if (it->request.data() == request)
return true;
}
return false;
}
/*!
\internal
Returns \c true if a request has a message id equal to \a id, or if \a id
is reserved.
*/
bool QCoapProtocolPrivate::isMessageIdRegistered(quint16 id) const
{
// Reserved for uninitialized message Id
if (id == 0)
return true;
for (auto it = exchangeMap.constBegin(); it != exchangeMap.constEnd(); ++it) {
if (it->request->message()->messageId() == id)
return true;
}
return false;
}
/*!
\internal
Returns the ACK_TIMEOUT value in milliseconds.
The default is 2000.
\sa minimumTimeout(), setAckTimeout()
*/
uint QCoapProtocol::ackTimeout() const
{
Q_D(const QCoapProtocol);
return d->ackTimeout;
}
/*!
\internal
Returns the ACK_RANDOM_FACTOR value.
The default is 1.5.
\sa setAckRandomFactor()
*/
double QCoapProtocol::ackRandomFactor() const
{
Q_D(const QCoapProtocol);
return d->ackRandomFactor;
}
/*!
\internal
Returns the MAX_RETRANSMIT value. This is the maximum number of
retransmissions of a message, before notifying a timeout error.
The default is 4.
\sa setMaximumRetransmitCount()
*/
uint QCoapProtocol::maximumRetransmitCount() const
{
Q_D(const QCoapProtocol);
return d->maximumRetransmitCount;
}
/*!
\internal
Returns the maximum block size wanted.
The default is 0, which invites the server to choose the block size.
\sa setBlockSize()
*/
quint16 QCoapProtocol::blockSize() const
{
Q_D(const QCoapProtocol);
return d->blockSize;
}
/*!
\internal
Returns the MAX_TRANSMIT_SPAN in milliseconds, as defined in
It is the maximum time from the first transmission of a Confirmable
message to its last retransmission.
*/
uint QCoapProtocol::maximumTransmitSpan() const
{
return static_cast<uint>(ackTimeout()
* ((1u << maximumRetransmitCount()) - 1)
* ackRandomFactor());
}
/*!
\internal
Returns the MAX_TRANSMIT_WAIT in milliseconds, as defined in
It is the maximum time from the first transmission of a Confirmable
message to the time when the sender gives up on receiving an
acknowledgment or reset.
*/
uint QCoapProtocol::maximumTransmitWait() const
{
return static_cast<uint>(ackTimeout() * ((1u << (maximumRetransmitCount() + 1)) - 1)
* ackRandomFactor());
}
/*!
\internal
Returns the MAX_LATENCY in milliseconds, as defined in
value is arbitrarily set to 100 seconds by the standard.
It is the maximum time a datagram is expected to take from the start of
its transmission to the completion of its reception.
*/
uint QCoapProtocol::maximumLatency() const
{
return 100 * 1000;
}
/*!
\internal
Returns the minimum duration for messages timeout. The timeout is defined
as a random value between minimumTimeout() and maximumTimeout(). This is a
convenience method identical to ackTimeout().
\sa ackTimeout(), setAckTimeout()
*/
uint QCoapProtocol::minimumTimeout() const
{
Q_D(const QCoapProtocol);
return d->ackTimeout;
}
/*!
\internal
Returns the maximum duration for messages timeout in milliseconds.
\sa maximumTimeout(), setAckTimeout(), setAckRandomFactor()
*/
uint QCoapProtocol::maximumTimeout() const
{
Q_D(const QCoapProtocol);
return static_cast<uint>(d->ackTimeout * d->ackRandomFactor);
}
/*!
\internal
Returns the \c NON_LIFETIME in milliseconds, as defined in
It is the time from sending a non-confirmable message to the time its
message ID can be safely reused.
*/
uint QCoapProtocol::nonConfirmLifetime() const
{
return maximumTransmitSpan() + maximumLatency();
}
/*!
\internal
Returns the \c MAX_SERVER_RESPONSE_DELAY in milliseconds, as defined in
\l {RFC 7390 - Section 2.5}.
It is the expected maximum response delay over all servers that the client
can send a multicast request to.
\sa setMaximumServerResponseDelay()
*/
uint QCoapProtocol::maximumServerResponseDelay() const
{
Q_D(const QCoapProtocol);
return d->maximumServerResponseDelay;
}
/*!
\internal
Sets the ACK_TIMEOUT value to \a ackTimeout in milliseconds.
The default is 2000 ms.
Timeout only applies to Confirmable message. The actual timeout for
reliable transmissions is a random value between ackTimeout() and
ackTimeout() * ackRandomFactor().
\sa ackTimeout(), setAckRandomFactor(), minimumTimeout(), maximumTimeout()
*/
void QCoapProtocol::setAckTimeout(uint ackTimeout)
{
Q_D(QCoapProtocol);
d->ackTimeout = ackTimeout;
}
/*!
\internal
Sets the ACK_RANDOM_FACTOR value to \a ackRandomFactor. This value
should be greater than or equal to 1.
The default is 1.5.
\sa ackRandomFactor(), setAckTimeout()
*/
void QCoapProtocol::setAckRandomFactor(double ackRandomFactor)
{
Q_D(QCoapProtocol);
if (ackRandomFactor < 1)
qCWarning(lcCoapProtocol, "The acknowledgment random factor should be >= 1");
d->ackRandomFactor = qMax(1., ackRandomFactor);
}
/*!
\internal
Sets the MAX_RETRANSMIT value to \a maximumRetransmitCount, but never
to more than 25.
The default is 4.
\sa maximumRetransmitCount()
*/
void QCoapProtocol::setMaximumRetransmitCount(uint maximumRetransmitCount)
{
Q_D(QCoapProtocol);
if (maximumRetransmitCount > 25) {
qCWarning(lcCoapProtocol, "Maximum retransmit count is capped at 25.");
maximumRetransmitCount = 25;
}
d->maximumRetransmitCount = maximumRetransmitCount;
}
/*!
\internal
Sets the maximum block size wanted to \a blockSize.
The \a blockSize should be zero, or range from 16 to 1024 and be a
power of 2. A size of 0 invites the server to choose the block size.
\sa blockSize()
*/
void QCoapProtocol::setBlockSize(quint16 blockSize)
{
Q_D(QCoapProtocol);
if ((blockSize & (blockSize - 1)) != 0) {
qCWarning(lcCoapProtocol, "Block size should be a power of 2");
return;
}
if (blockSize != 0 && (blockSize < 16 || blockSize > 1024)) {
qCWarning(lcCoapProtocol, "Block size should be set to zero,"
"or to a power of 2 from 16 through 1024");
return;
}
d->blockSize = blockSize;
}
/*!
\internal
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.
\sa maximumServerResponseDelay()
*/
void QCoapProtocol::setMaximumServerResponseDelay(uint responseDelay)
{
Q_D(QCoapProtocol);
d->maximumServerResponseDelay = responseDelay;
}
/*!
\internal
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 QCoapProtocol::setMinimumTokenSize(int tokenSize)
{
Q_D(QCoapProtocol);
if (tokenSize > 0 && tokenSize <= 8) {
d->minimumTokenSize = tokenSize;
} else {
qCWarning(lcCoapProtocol,
"Failed to set the minimum token size,"
"it should not be more than 8 bytes and cannot be 0.");
}
}
QT_END_NAMESPACE