1// Copyright (C) 2017 Witekio.
2// Copyright (C) 2018 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
5#include "qcoapprotocol_p.h"
6#include "qcoapinternalrequest_p.h"
7#include "qcoapinternalreply_p.h"
8#include "qcoaprequest_p.h"
9#include "qcoapconnection_p.h"
10#include "qcoapnamespace_p.h"
11
12#include <QtCore/qrandom.h>
13#include <QtCore/qthread.h>
14#include <QtCore/qloggingcategory.h>
15#include <QtNetwork/qnetworkdatagram.h>
16
17QT_BEGIN_NAMESPACE
18
19Q_LOGGING_CATEGORY(lcCoapProtocol, "qt.coap.protocol")
20
21/*!
22 \internal
23
24 \class QCoapProtocol
25 \inmodule QtCoap
26
27 \brief The QCoapProtocol class handles the logical part of the CoAP
28 protocol.
29
30 \reentrant
31
32 The QCoapProtocol is used by the QCoapClient class to handle the logical
33 part of the protocol. It can encode requests and decode replies. It also
34 handles what to do when a message is received, along with retransmission of
35 lost messages.
36
37 \sa QCoapClient
38*/
39
40/*!
41 \internal
42
43 \fn void QCoapProtocol::finished(QCoapReply *reply)
44
45 This signal is emitted along with the \l QCoapReply::finished() signal
46 whenever a CoAP reply is received, after either a success or an error.
47 The \a reply parameter will contain a pointer to the reply that has just
48 been received.
49
50 \sa error(), QCoapReply::finished(), QCoapReply::error()
51*/
52
53/*!
54 \internal
55
56 \fn void QCoapProtocol::responseToMulticastReceived(QCoapReply *reply,
57 const QCoapMessage& message,
58 const QHostAddress &sender)
59
60 This signal is emitted when a unicast response to a multicast request
61 arrives. The \a reply parameter contains a pointer to the reply that has just
62 been received, \a message contains the payload and the message details,
63 and \a sender contains the sender address.
64
65 \sa error(), QCoapReply::finished(), QCoapReply::error()
66*/
67
68/*!
69 \internal
70
71 \fn void QCoapProtocol::error(QCoapReply *reply, QtCoap::Error error)
72
73 This signal is emitted whenever an error occurs. The \a reply parameter
74 can be \nullptr if the error is not related to a specific QCoapReply. The
75 \a error parameter contains the error code.
76
77 \sa finished(), QCoapReply::error(), QCoapReply::finished()
78*/
79
80/*!
81 \internal
82
83 Constructs a new QCoapProtocol and sets \a parent as the parent object.
84*/
85QCoapProtocol::QCoapProtocol(QObject *parent) :
86 QObject(*new QCoapProtocolPrivate, parent)
87{
88 qRegisterMetaType<QCoapInternalRequest *>();
89 qRegisterMetaType<QHostAddress>();
90}
91
92QCoapProtocol::~QCoapProtocol()
93{
94 Q_D(QCoapProtocol);
95
96 // Clear table to avoid double deletion from QObject parenting and QSharedPointer.
97 d->exchangeMap.clear();
98}
99
100/*!
101 \internal
102
103 Creates and sets up a new QCoapInternalRequest related to the request
104 associated to the \a reply. The request will then be sent to the server
105 using the given \a connection.
106*/
107void QCoapProtocol::sendRequest(QPointer<QCoapReply> reply, QCoapConnection *connection)
108{
109 Q_D(QCoapProtocol);
110 Q_ASSERT(QThread::currentThread() == thread());
111
112 if (reply.isNull() || reply->request().method() == QtCoap::Method::Invalid
113 || !QCoapRequestPrivate::isUrlValid(url: reply->request().url()))
114 return;
115
116 connect(sender: reply.data(), signal: &QCoapReply::aborted, context: this, slot: [this](const QCoapToken &token) {
117 Q_D(QCoapProtocol);
118 d->onRequestAborted(token);
119 });
120
121 auto internalRequest = QSharedPointer<QCoapInternalRequest>::create(arguments: reply->request(), arguments: this);
122 internalRequest->setMaxTransmissionWait(maximumTransmitWait());
123 connect(sender: reply.data(), signal: &QCoapReply::finished, context: this, slot: &QCoapProtocol::finished);
124
125 if (internalRequest->isMulticast()) {
126 connect(sender: internalRequest.data(), signal: &QCoapInternalRequest::multicastRequestExpired, context: this,
127 slot: [this](QCoapInternalRequest *request) {
128 Q_D(QCoapProtocol);
129 d->onMulticastRequestExpired(request);
130 });
131 // The timeout interval is chosen based on
132 // https://tools.ietf.org/html/rfc7390#section-2.5
133 internalRequest->setMulticastTimeout(nonConfirmLifetime()
134 + maximumLatency()
135 + maximumServerResponseDelay());
136 }
137
138 // Set a unique Message Id and Token
139 QCoapMessage *requestMessage = internalRequest->message();
140 internalRequest->setMessageId(d->generateUniqueMessageId());
141 if (internalRequest->token().isEmpty())
142 internalRequest->setToken(d->generateUniqueToken());
143 internalRequest->setConnection(connection);
144
145 d->registerExchange(token: requestMessage->token(), reply, request: internalRequest);
146 QMetaObject::invokeMethod(obj: reply, member: "_q_setRunning", c: Qt::QueuedConnection,
147 Q_ARG(QCoapToken, requestMessage->token()),
148 Q_ARG(QCoapMessageId, requestMessage->messageId()));
149
150 // Set block size for blockwise request/replies, if specified
151 if (d->blockSize > 0) {
152 internalRequest->setToRequestBlock(blockNumber: 0, blockSize: d->blockSize);
153 if (requestMessage->payload().size() > d->blockSize)
154 internalRequest->setToSendBlock(blockNumber: 0, blockSize: d->blockSize);
155 }
156
157 if (requestMessage->type() == QCoapMessage::Type::Confirmable) {
158 const auto minTimeout = minimumTimeout();
159 const auto maxTimeout = maximumTimeout();
160 Q_ASSERT(minTimeout <= maxTimeout);
161
162 internalRequest->setTimeout(minTimeout == maxTimeout
163 ? minTimeout
164 : QtCoap::randomGenerator().bounded(lowest: minTimeout, highest: maxTimeout));
165 } else {
166 internalRequest->setTimeout(maximumTimeout());
167 }
168
169 connect(sender: internalRequest.data(), signal: &QCoapInternalRequest::timeout,
170 slot: [this](QCoapInternalRequest *request) {
171 Q_D(QCoapProtocol);
172 d->onRequestTimeout(request);
173 });
174 connect(sender: internalRequest.data(), signal: &QCoapInternalRequest::maxTransmissionSpanReached,
175 slot: [this](QCoapInternalRequest *request) {
176 Q_D(QCoapProtocol);
177 d->onRequestMaxTransmissionSpanReached(request);
178 });
179 d->sendRequest(request: internalRequest.data());
180}
181
182/*!
183 \internal
184
185 Encodes and sends the given \a request to the server. If \a host is not empty,
186 sends the request to \a host, instead of using the host address from the request.
187 The \a host parameter is relevant for multicast blockwise transfers.
188*/
189void QCoapProtocolPrivate::sendRequest(QCoapInternalRequest *request, const QString& host) const
190{
191 Q_Q(const QCoapProtocol);
192 Q_ASSERT(QThread::currentThread() == q->thread());
193
194 if (!request || !request->connection()) {
195 qCWarning(lcCoapProtocol, "Request null or not bound to any connection: aborted.");
196 return;
197 }
198
199 if (request->isMulticast())
200 request->startMulticastTransmission();
201 else
202 request->restartTransmission();
203
204 QByteArray requestFrame = request->toQByteArray();
205 QUrl uri = request->targetUri();
206 const auto& hostAddress = host.isEmpty() ? uri.host() : host;
207 request->connection()->d_func()->sendRequest(request: requestFrame, host: hostAddress,
208 port: static_cast<quint16>(uri.port()));
209}
210
211/*!
212 \internal
213
214 This slot is used to send again the given \a request after a timeout or
215 aborts the request and transfers a timeout error to the reply.
216*/
217void QCoapProtocolPrivate::onRequestTimeout(QCoapInternalRequest *request)
218{
219 Q_Q(const QCoapProtocol);
220 Q_ASSERT(QThread::currentThread() == q->thread());
221
222 if (!isRequestRegistered(request))
223 return;
224
225 if (request->message()->type() == QCoapMessage::Type::Confirmable
226 && request->retransmissionCounter() < maximumRetransmitCount) {
227 sendRequest(request);
228 } else {
229 onRequestError(request, error: QtCoap::Error::TimeOut);
230 }
231}
232
233/*!
234 \internal
235
236 This slot is called when the maximum span for this transmission has been
237 reached, and triggers a timeout error if the request is still running.
238*/
239void QCoapProtocolPrivate::onRequestMaxTransmissionSpanReached(QCoapInternalRequest *request)
240{
241 Q_Q(const QCoapProtocol);
242 Q_ASSERT(QThread::currentThread() == q->thread());
243
244 if (isRequestRegistered(request))
245 onRequestError(request, error: QtCoap::Error::TimeOut);
246}
247
248/*!
249 \internal
250
251 This slot is called when the multicast request expires, meaning that no
252 more responses are expected for the multicast \a request. As a result of this
253 call, the request token is \e {freed up} and the \l finished() signal is emitted.
254*/
255void QCoapProtocolPrivate::onMulticastRequestExpired(QCoapInternalRequest *request)
256{
257 Q_ASSERT(request->isMulticast());
258
259 request->stopTransmission();
260 QPointer<QCoapReply> userReply = userReplyForToken(token: request->token());
261 if (userReply) {
262 QMetaObject::invokeMethod(obj: userReply, member: "_q_setFinished", c: Qt::QueuedConnection,
263 Q_ARG(QtCoap::Error, QtCoap::Error::Ok));
264 } else {
265 qCWarning(lcCoapProtocol).nospace() << "Reply for token '" << request->token()
266 << "' is not registered, reply is null.";
267 }
268 forgetExchange(request);
269}
270
271/*!
272 \internal
273
274 Method triggered when a request fails.
275*/
276void QCoapProtocolPrivate::onRequestError(QCoapInternalRequest *request, QCoapInternalReply *reply)
277{
278 QtCoap::Error error = QtCoap::errorForResponseCode(code: reply->responseCode());
279 onRequestError(request, error, reply);
280}
281
282/*!
283 \internal
284
285 Method triggered when a request fails.
286*/
287void QCoapProtocolPrivate::onRequestError(QCoapInternalRequest *request, QtCoap::Error error,
288 QCoapInternalReply *reply)
289{
290 Q_Q(QCoapProtocol);
291 Q_ASSERT(request);
292
293 auto userReply = userReplyForToken(token: request->token());
294
295 if (!userReply.isNull()) {
296 // Set error from content, or error enum
297 if (reply) {
298 QMetaObject::invokeMethod(obj: userReply.data(), member: "_q_setContent", c: Qt::QueuedConnection,
299 Q_ARG(QHostAddress, reply->senderAddress()),
300 Q_ARG(QCoapMessage, *reply->message()),
301 Q_ARG(QtCoap::ResponseCode, reply->responseCode()));
302 } else {
303 QMetaObject::invokeMethod(obj: userReply.data(), member: "_q_setError", c: Qt::QueuedConnection,
304 Q_ARG(QtCoap::Error, error));
305 }
306
307 QMetaObject::invokeMethod(obj: userReply.data(), member: "_q_setFinished", c: Qt::QueuedConnection,
308 Q_ARG(QtCoap::Error, QtCoap::Error::Ok));
309 }
310
311 forgetExchange(request);
312 emit q->error(reply: userReply.data(), error);
313}
314
315/*!
316 \internal
317
318 Decode and process the given \a data received from the \a sender.
319*/
320void QCoapProtocolPrivate::onFrameReceived(const QByteArray &data, const QHostAddress &sender)
321{
322 Q_Q(const QCoapProtocol);
323 Q_ASSERT(QThread::currentThread() == q->thread());
324
325 QSharedPointer<QCoapInternalReply> reply(decode(data, sender));
326 const QCoapMessage *messageReceived = reply->message();
327
328 QCoapInternalRequest *request = nullptr;
329 if (!messageReceived->token().isEmpty())
330 request = requestForToken(token: messageReceived->token());
331
332 if (!request) {
333 request = findRequestByMessageId(messageId: messageReceived->messageId());
334
335 // No matching request found, drop the frame.
336 if (!request)
337 return;
338 }
339
340 QHostAddress originalTarget(request->targetUri().host());
341 if (!originalTarget.isMulticast() && !originalTarget.isEqual(address: sender)) {
342 qCDebug(lcCoapProtocol).nospace() << "QtCoap: Answer received from incorrect host ("
343 << sender << " instead of "
344 << originalTarget << ")";
345 return;
346 }
347
348 if (!request->isMulticast())
349 request->stopTransmission();
350 addReply(token: request->token(), reply);
351
352 if (QtCoap::isError(code: reply->responseCode())) {
353 onRequestError(request, reply: reply.data());
354 return;
355 }
356
357 // Reply when the server asks for an ACK
358 if (request->isObserveCancelled()) {
359 // Remove option to ensure that it will stop
360 request->removeOption(name: QCoapOption::Observe);
361 sendReset(request);
362 } else if (messageReceived->type() == QCoapMessage::Type::Confirmable) {
363 sendAcknowledgment(request);
364 }
365
366 // Send next block, ask for next block, or process the final reply
367 if (reply->hasMoreBlocksToSend() && reply->nextBlockToSend() >= 0) {
368 request->setToSendBlock(blockNumber: static_cast<uint>(reply->nextBlockToSend()), blockSize);
369 request->setMessageId(generateUniqueMessageId());
370 sendRequest(request);
371 } else if (reply->hasMoreBlocksToReceive()) {
372 request->setToRequestBlock(blockNumber: reply->currentBlockNumber() + 1, blockSize: reply->blockSize());
373 request->setMessageId(generateUniqueMessageId());
374 // In case of multicast blockwise transfers, according to
375 // https://tools.ietf.org/html/rfc7959#section-2.8, further blocks should be retrieved
376 // via unicast requests. So instead of using the multicast request address, we need
377 // to use the sender address for getting the next blocks.
378 sendRequest(request, host: sender.toString());
379 } else {
380 onLastMessageReceived(request, sender);
381 }
382}
383
384/*!
385 \internal
386
387 Returns the internal request for the given \a token.
388*/
389QCoapInternalRequest *QCoapProtocolPrivate::requestForToken(const QCoapToken &token) const
390{
391 auto it = exchangeMap.find(key: token);
392 if (it != exchangeMap.constEnd())
393 return it->request.data();
394
395 return nullptr;
396}
397
398/*!
399 \internal
400
401 Returns the QCoapReply instance of the given \a token.
402*/
403QPointer<QCoapReply> QCoapProtocolPrivate::userReplyForToken(const QCoapToken &token) const
404{
405 auto it = exchangeMap.find(key: token);
406 if (it != exchangeMap.constEnd())
407 return it->userReply;
408
409 return nullptr;
410}
411
412/*!
413 \internal
414
415 Returns the replies for the exchange identified by \a token.
416*/
417QList<QSharedPointer<QCoapInternalReply>>
418QCoapProtocolPrivate::repliesForToken(const QCoapToken &token) const
419{
420 auto it = exchangeMap.find(key: token);
421 if (it != exchangeMap.constEnd())
422 return it->replies;
423
424 return {};
425}
426
427/*!
428 \internal
429
430 Returns the last reply for the exchange identified by \a token.
431*/
432QCoapInternalReply *QCoapProtocolPrivate::lastReplyForToken(const QCoapToken &token) const
433{
434 auto it = exchangeMap.find(key: token);
435 if (it != exchangeMap.constEnd())
436 return it->replies.last().data();
437
438 return nullptr;
439}
440
441/*!
442 \internal
443
444 Finds an internal request matching the given \a reply.
445*/
446QCoapInternalRequest *QCoapProtocolPrivate::findRequestByUserReply(const QCoapReply *reply) const
447{
448 for (auto it = exchangeMap.constBegin(); it != exchangeMap.constEnd(); ++it) {
449 if (it->userReply == reply)
450 return it->request.data();
451 }
452
453 return nullptr;
454}
455
456/*!
457 \internal
458
459 Finds an internal request containing the message id \a messageId.
460*/
461QCoapInternalRequest *QCoapProtocolPrivate::findRequestByMessageId(quint16 messageId) const
462{
463 for (auto it = exchangeMap.constBegin(); it != exchangeMap.constEnd(); ++it) {
464 if (it->request->message()->messageId() == messageId)
465 return it->request.data();
466 }
467
468 return nullptr;
469}
470
471/*!
472 \internal
473
474 Handles what to do when we received the last block of a reply.
475
476 Merges all blocks, removes the request from the map, updates the
477 associated QCoapReply and emits the
478 \l{QCoapProtocol::finished(QCoapReply*)}{finished(QCoapReply*)} signal.
479*/
480void QCoapProtocolPrivate::onLastMessageReceived(QCoapInternalRequest *request,
481 const QHostAddress &sender)
482{
483 Q_ASSERT(request);
484 if (!request || !isRequestRegistered(request))
485 return;
486
487 auto replies = repliesForToken(token: request->token());
488 Q_ASSERT(!replies.isEmpty());
489
490 //! TODO: Change QPointer<QCoapReply> into something independent from
491 //! User. QSharedPointer(s)?
492 QPointer<QCoapReply> userReply = userReplyForToken(token: request->token());
493 if (userReply.isNull() || replies.isEmpty()
494 || (request->isObserve() && request->isObserveCancelled())) {
495 forgetExchange(request);
496 return;
497 }
498
499 auto lastReply = replies.last();
500 // Ignore empty ACK messages
501 if (lastReply->message()->type() == QCoapMessage::Type::Acknowledgment
502 && lastReply->responseCode() == QtCoap::ResponseCode::EmptyMessage) {
503 exchangeMap[request->token()].replies.takeLast();
504 return;
505 }
506
507 // Merge payloads for blockwise transfers
508 if (replies.size() > 1) {
509
510 // In multicast case, multiple hosts will reply to the same multicast request.
511 // We are interested only in replies coming from the sender.
512 if (request->isMulticast()) {
513 replies.erase(abegin: std::remove_if(first: replies.begin(), last: replies.end(),
514 pred: [sender](QSharedPointer<QCoapInternalReply> reply) {
515 return reply->senderAddress() != sender;
516 }), aend: replies.end());
517 }
518
519 std::stable_sort(first: std::begin(cont&: replies), last: std::end(cont&: replies),
520 comp: [](QSharedPointer<QCoapInternalReply> a, QSharedPointer<QCoapInternalReply> b) -> bool {
521 return (a->currentBlockNumber() < b->currentBlockNumber());
522 });
523
524 QByteArray finalPayload;
525 int lastBlockNumber = -1;
526 for (auto reply : std::as_const(t&: replies)) {
527 int currentBlock = static_cast<int>(reply->currentBlockNumber());
528 QByteArray replyPayload = reply->message()->payload();
529 if (replyPayload.isEmpty() || currentBlock <= lastBlockNumber)
530 continue;
531
532 finalPayload.append(a: replyPayload);
533 lastBlockNumber = currentBlock;
534 }
535
536 lastReply->message()->setPayload(finalPayload);
537 }
538
539 // Forward the answer
540 QMetaObject::invokeMethod(obj: userReply, member: "_q_setContent", c: Qt::QueuedConnection,
541 Q_ARG(QHostAddress, lastReply->senderAddress()),
542 Q_ARG(QCoapMessage, *lastReply->message()),
543 Q_ARG(QtCoap::ResponseCode, lastReply->responseCode()));
544
545 if (request->isObserve()) {
546 QMetaObject::invokeMethod(obj: userReply, member: "_q_setNotified", c: Qt::QueuedConnection);
547 forgetExchangeReplies(token: request->token());
548 } else if (request->isMulticast()) {
549 Q_Q(QCoapProtocol);
550 emit q->responseToMulticastReceived(reply: userReply, message: *lastReply->message(), sender);
551 } else {
552 QMetaObject::invokeMethod(obj: userReply, member: "_q_setFinished", c: Qt::QueuedConnection,
553 Q_ARG(QtCoap::Error, QtCoap::Error::Ok));
554 forgetExchange(request);
555 }
556}
557
558/*!
559 \internal
560
561 Sends an internal request acknowledging the given \a request, reusing its
562 URI and connection.
563*/
564void QCoapProtocolPrivate::sendAcknowledgment(QCoapInternalRequest *request) const
565{
566 Q_Q(const QCoapProtocol);
567 Q_ASSERT(QThread::currentThread() == q->thread());
568
569 QCoapInternalRequest ackRequest;
570 ackRequest.setTargetUri(request->targetUri());
571
572 auto internalReply = lastReplyForToken(token: request->token());
573 ackRequest.initEmptyMessage(messageId: internalReply->message()->messageId(),
574 type: QCoapMessage::Type::Acknowledgment);
575 ackRequest.setConnection(request->connection());
576 sendRequest(request: &ackRequest);
577}
578
579/*!
580 \internal
581
582 Sends a Reset message (RST), reusing the details of the given
583 \a request. A Reset message indicates that a specific message has been
584 received, but cannot be properly processed.
585*/
586void QCoapProtocolPrivate::sendReset(QCoapInternalRequest *request) const
587{
588 Q_Q(const QCoapProtocol);
589 Q_ASSERT(QThread::currentThread() == q->thread());
590
591 QCoapInternalRequest resetRequest;
592 resetRequest.setTargetUri(request->targetUri());
593
594 auto lastReply = lastReplyForToken(token: request->token());
595 resetRequest.initEmptyMessage(messageId: lastReply->message()->messageId(), type: QCoapMessage::Type::Reset);
596 resetRequest.setConnection(request->connection());
597 sendRequest(request: &resetRequest);
598}
599
600/*!
601 \internal
602
603 Cancels resource observation. The QCoapReply::notified() signal will not
604 be emitted after cancellation.
605
606 A Reset (RST) message will be sent at the reception of the next message.
607*/
608void QCoapProtocol::cancelObserve(QPointer<QCoapReply> reply) const
609{
610 Q_D(const QCoapProtocol);
611
612 if (reply.isNull())
613 return;
614
615 QCoapInternalRequest *request = d->requestForToken(token: reply->request().token());
616 if (request) {
617 // Stop here if already cancelled
618 if (!request->isObserve() || request->isObserveCancelled())
619 return;
620
621 request->setObserveCancelled();
622 }
623
624 // Set as cancelled even if request is not tracked anymore
625 QMetaObject::invokeMethod(obj: reply, member: "_q_setObserveCancelled", c: Qt::QueuedConnection);
626}
627
628/*!
629 \internal
630
631 Cancels resource observation for the given \a url. The QCoapReply::notified()
632 signal will not be emitted after cancellation.
633
634 A Reset (RST) message will be sent at the reception of the next message.
635*/
636void QCoapProtocol::cancelObserve(const QUrl &url) const
637{
638 Q_D(const QCoapProtocol);
639
640 for (const auto &exchange : d->exchangeMap) {
641 Q_ASSERT(exchange.userReply);
642 if (exchange.userReply->url() == url)
643 cancelObserve(reply: exchange.userReply);
644 }
645}
646
647/*!
648 \internal
649
650 Returns a currently unused message Id.
651*/
652quint16 QCoapProtocolPrivate::generateUniqueMessageId() const
653{
654 // TODO: Optimize message id generation for large sets
655 // TODO: Store used message id for the period specified by CoAP spec
656 quint16 id = 0;
657 while (isMessageIdRegistered(id))
658 id = static_cast<quint16>(QtCoap::randomGenerator().bounded(highest: 0x10000));
659
660 return id;
661}
662
663/*!
664 \internal
665
666 Returns a currently unused token.
667*/
668QCoapToken QCoapProtocolPrivate::generateUniqueToken() const
669{
670 // TODO: Optimize token generation for large sets
671 // TODO: Store used token for the period specified by CoAP spec
672 QCoapToken token;
673 while (isTokenRegistered(token)) {
674 quint8 length = static_cast<quint8>(QtCoap::randomGenerator().bounded(lowest: minimumTokenSize, highest: 9));
675 token.resize(size: length);
676 quint8 *tokenData = reinterpret_cast<quint8 *>(token.data());
677 for (int i = 0; i < token.size(); ++i)
678 tokenData[i] = static_cast<quint8>(QtCoap::randomGenerator().bounded(highest: 256));
679 }
680
681 return token;
682}
683
684/*!
685 \internal
686
687 Returns a new unmanaged QCoapInternalReply based on \a data and \a sender.
688*/
689QCoapInternalReply *QCoapProtocolPrivate::decode(const QByteArray &data, const QHostAddress &sender)
690{
691 Q_Q(QCoapProtocol);
692 QCoapInternalReply *reply = QCoapInternalReply::createFromFrame(frame: data, parent: q);
693 reply->setSenderAddress(sender);
694
695 return reply;
696}
697
698/*!
699 \internal
700
701 Aborts the request corresponding to the given \a reply. It is triggered
702 by the destruction of the QCoapReply object or a call to
703 QCoapReply::abortRequest().
704*/
705void QCoapProtocolPrivate::onRequestAborted(const QCoapToken &token)
706{
707 QCoapInternalRequest *request = requestForToken(token);
708 if (!request)
709 return;
710
711 request->stopTransmission();
712 forgetExchange(request);
713}
714
715/*!
716 \internal
717
718 Triggered in case of a connection error.
719*/
720void QCoapProtocolPrivate::onConnectionError(QAbstractSocket::SocketError socketError)
721{
722 Q_Q(QCoapProtocol);
723
724 QtCoap::Error coapError;
725 switch (socketError) {
726 case QAbstractSocket::HostNotFoundError :
727 coapError = QtCoap::Error::HostNotFound;
728 break;
729 case QAbstractSocket::AddressInUseError :
730 coapError = QtCoap::Error::AddressInUse;
731 break;
732 default:
733 coapError = QtCoap::Error::Unknown;
734 break;
735 }
736
737 emit q->error(reply: nullptr, error: coapError);
738}
739
740/*!
741 \internal
742
743 Registers a new CoAP exchange using \a token.
744*/
745void QCoapProtocolPrivate::registerExchange(const QCoapToken &token, QCoapReply *reply,
746 QSharedPointer<QCoapInternalRequest> request)
747{
748 CoapExchangeData data = { .userReply: reply, .request: request,
749 .replies: QList<QSharedPointer<QCoapInternalReply> >()
750 };
751
752 exchangeMap.insert(key: token, value: data);
753}
754
755/*!
756 \internal
757
758 Adds \a reply to the list of replies of the exchange identified by
759 \a token.
760 Returns \c true if the reply was successfully added. This method will fail
761 and return \c false if no exchange is associated with the \a token
762 provided.
763*/
764bool QCoapProtocolPrivate::addReply(const QCoapToken &token,
765 QSharedPointer<QCoapInternalReply> reply)
766{
767 if (!isTokenRegistered(token) || !reply) {
768 qCWarning(lcCoapProtocol).nospace() << "Reply token '" << token
769 << "' not registered, or reply is null.";
770 return false;
771 }
772
773 exchangeMap[token].replies.push_back(t: reply);
774 return true;
775}
776
777/*!
778 \internal
779
780 Remove the exchange identified by its \a token. This is
781 typically done when finished or aborted.
782 It will delete the QCoapInternalRequest and QCoapInternalReplies
783 associated with the exchange.
784
785 Returns \c true if the exchange was found and removed, \c false otherwise.
786*/
787bool QCoapProtocolPrivate::forgetExchange(const QCoapToken &token)
788{
789 return exchangeMap.remove(key: token) > 0;
790}
791
792/*!
793 \internal
794
795 Remove the exchange using a request.
796
797 \sa forgetExchange(const QCoapToken &)
798*/
799bool QCoapProtocolPrivate::forgetExchange(const QCoapInternalRequest *request)
800{
801 return forgetExchange(token: request->token());
802}
803
804/*!
805 \internal
806
807 Remove all replies for the exchange corresponding to \a token.
808*/
809bool QCoapProtocolPrivate::forgetExchangeReplies(const QCoapToken &token)
810{
811 auto it = exchangeMap.find(key: token);
812 if (it == exchangeMap.end())
813 return false;
814
815 it->replies.clear();
816 return true;
817}
818
819/*!
820 \internal
821
822 Returns \c true if the \a token is reserved or in use; returns \c false if
823 this token can be used to identify a new exchange.
824*/
825bool QCoapProtocolPrivate::isTokenRegistered(const QCoapToken &token) const
826{
827 // Reserved for empty messages and uninitialized tokens
828 if (token == QByteArray())
829 return true;
830
831 return exchangeMap.contains(key: token);
832}
833
834/*!
835 \internal
836
837 Returns \c true if the \a request is present in a currently registered
838 exchange.
839*/
840bool QCoapProtocolPrivate::isRequestRegistered(const QCoapInternalRequest *request) const
841{
842 for (auto it = exchangeMap.constBegin(); it != exchangeMap.constEnd(); ++it) {
843 if (it->request.data() == request)
844 return true;
845 }
846
847 return false;
848}
849
850/*!
851 \internal
852
853 Returns \c true if a request has a message id equal to \a id, or if \a id
854 is reserved.
855*/
856bool QCoapProtocolPrivate::isMessageIdRegistered(quint16 id) const
857{
858 // Reserved for uninitialized message Id
859 if (id == 0)
860 return true;
861
862 for (auto it = exchangeMap.constBegin(); it != exchangeMap.constEnd(); ++it) {
863 if (it->request->message()->messageId() == id)
864 return true;
865 }
866
867 return false;
868}
869
870/*!
871 \internal
872
873 Returns the ACK_TIMEOUT value in milliseconds.
874 The default is 2000.
875
876 \sa minimumTimeout(), setAckTimeout()
877*/
878uint QCoapProtocol::ackTimeout() const
879{
880 Q_D(const QCoapProtocol);
881 return d->ackTimeout;
882}
883
884/*!
885 \internal
886
887 Returns the ACK_RANDOM_FACTOR value.
888 The default is 1.5.
889
890 \sa setAckRandomFactor()
891*/
892double QCoapProtocol::ackRandomFactor() const
893{
894 Q_D(const QCoapProtocol);
895 return d->ackRandomFactor;
896}
897
898/*!
899 \internal
900
901 Returns the MAX_RETRANSMIT value. This is the maximum number of
902 retransmissions of a message, before notifying a timeout error.
903 The default is 4.
904
905 \sa setMaximumRetransmitCount()
906*/
907uint QCoapProtocol::maximumRetransmitCount() const
908{
909 Q_D(const QCoapProtocol);
910 return d->maximumRetransmitCount;
911}
912
913/*!
914 \internal
915
916 Returns the maximum block size wanted.
917 The default is 0, which invites the server to choose the block size.
918
919 \sa setBlockSize()
920*/
921quint16 QCoapProtocol::blockSize() const
922{
923 Q_D(const QCoapProtocol);
924 return d->blockSize;
925}
926
927/*!
928 \internal
929
930 Returns the MAX_TRANSMIT_SPAN in milliseconds, as defined in
931 \l{https://tools.ietf.org/search/rfc7252#section-4.8.2}{RFC 7252}.
932
933 It is the maximum time from the first transmission of a Confirmable
934 message to its last retransmission.
935*/
936uint QCoapProtocol::maximumTransmitSpan() const
937{
938 return static_cast<uint>(ackTimeout()
939 * ((1u << maximumRetransmitCount()) - 1)
940 * ackRandomFactor());
941}
942
943/*!
944 \internal
945
946 Returns the MAX_TRANSMIT_WAIT in milliseconds, as defined in
947 \l{https://tools.ietf.org/search/rfc7252#section-4.8.2}{RFC 7252}.
948
949 It is the maximum time from the first transmission of a Confirmable
950 message to the time when the sender gives up on receiving an
951 acknowledgment or reset.
952*/
953uint QCoapProtocol::maximumTransmitWait() const
954{
955 return static_cast<uint>(ackTimeout() * ((1u << (maximumRetransmitCount() + 1)) - 1)
956 * ackRandomFactor());
957}
958
959/*!
960 \internal
961
962 Returns the MAX_LATENCY in milliseconds, as defined in
963 \l{https://tools.ietf.org/search/rfc7252#section-4.8.2}{RFC 7252}. This
964 value is arbitrarily set to 100 seconds by the standard.
965
966 It is the maximum time a datagram is expected to take from the start of
967 its transmission to the completion of its reception.
968*/
969uint QCoapProtocol::maximumLatency() const
970{
971 return 100 * 1000;
972}
973
974/*!
975 \internal
976
977 Returns the minimum duration for messages timeout. The timeout is defined
978 as a random value between minimumTimeout() and maximumTimeout(). This is a
979 convenience method identical to ackTimeout().
980
981 \sa ackTimeout(), setAckTimeout()
982*/
983uint QCoapProtocol::minimumTimeout() const
984{
985 Q_D(const QCoapProtocol);
986 return d->ackTimeout;
987}
988
989/*!
990 \internal
991
992 Returns the maximum duration for messages timeout in milliseconds.
993
994 \sa maximumTimeout(), setAckTimeout(), setAckRandomFactor()
995*/
996uint QCoapProtocol::maximumTimeout() const
997{
998 Q_D(const QCoapProtocol);
999 return static_cast<uint>(d->ackTimeout * d->ackRandomFactor);
1000}
1001
1002/*!
1003 \internal
1004
1005 Returns the \c NON_LIFETIME in milliseconds, as defined in
1006 \l{https://tools.ietf.org/search/rfc7252#section-4.8.2}{RFC 7252}.
1007
1008 It is the time from sending a non-confirmable message to the time its
1009 message ID can be safely reused.
1010*/
1011uint QCoapProtocol::nonConfirmLifetime() const
1012{
1013 return maximumTransmitSpan() + maximumLatency();
1014}
1015
1016/*!
1017 \internal
1018
1019 Returns the \c MAX_SERVER_RESPONSE_DELAY in milliseconds, as defined in
1020 \l {RFC 7390 - Section 2.5}.
1021
1022 It is the expected maximum response delay over all servers that the client
1023 can send a multicast request to.
1024
1025 \sa setMaximumServerResponseDelay()
1026*/
1027uint QCoapProtocol::maximumServerResponseDelay() const
1028{
1029 Q_D(const QCoapProtocol);
1030 return d->maximumServerResponseDelay;
1031}
1032
1033/*!
1034 \internal
1035
1036 Sets the ACK_TIMEOUT value to \a ackTimeout in milliseconds.
1037 The default is 2000 ms.
1038
1039 Timeout only applies to Confirmable message. The actual timeout for
1040 reliable transmissions is a random value between ackTimeout() and
1041 ackTimeout() * ackRandomFactor().
1042
1043 \sa ackTimeout(), setAckRandomFactor(), minimumTimeout(), maximumTimeout()
1044*/
1045void QCoapProtocol::setAckTimeout(uint ackTimeout)
1046{
1047 Q_D(QCoapProtocol);
1048 d->ackTimeout = ackTimeout;
1049}
1050
1051/*!
1052 \internal
1053
1054 Sets the ACK_RANDOM_FACTOR value to \a ackRandomFactor. This value
1055 should be greater than or equal to 1.
1056 The default is 1.5.
1057
1058 \sa ackRandomFactor(), setAckTimeout()
1059*/
1060void QCoapProtocol::setAckRandomFactor(double ackRandomFactor)
1061{
1062 Q_D(QCoapProtocol);
1063 if (ackRandomFactor < 1)
1064 qCWarning(lcCoapProtocol, "The acknowledgment random factor should be >= 1");
1065
1066 d->ackRandomFactor = qMax(a: 1., b: ackRandomFactor);
1067}
1068
1069/*!
1070 \internal
1071
1072 Sets the MAX_RETRANSMIT value to \a maximumRetransmitCount, but never
1073 to more than 25.
1074 The default is 4.
1075
1076 \sa maximumRetransmitCount()
1077*/
1078void QCoapProtocol::setMaximumRetransmitCount(uint maximumRetransmitCount)
1079{
1080 Q_D(QCoapProtocol);
1081
1082 if (maximumRetransmitCount > 25) {
1083 qCWarning(lcCoapProtocol, "Maximum retransmit count is capped at 25.");
1084 maximumRetransmitCount = 25;
1085 }
1086
1087 d->maximumRetransmitCount = maximumRetransmitCount;
1088}
1089
1090/*!
1091 \internal
1092
1093 Sets the maximum block size wanted to \a blockSize.
1094
1095 The \a blockSize should be zero, or range from 16 to 1024 and be a
1096 power of 2. A size of 0 invites the server to choose the block size.
1097
1098 \sa blockSize()
1099*/
1100void QCoapProtocol::setBlockSize(quint16 blockSize)
1101{
1102 Q_D(QCoapProtocol);
1103
1104 if ((blockSize & (blockSize - 1)) != 0) {
1105 qCWarning(lcCoapProtocol, "Block size should be a power of 2");
1106 return;
1107 }
1108
1109 if (blockSize != 0 && (blockSize < 16 || blockSize > 1024)) {
1110 qCWarning(lcCoapProtocol, "Block size should be set to zero,"
1111 "or to a power of 2 from 16 through 1024");
1112 return;
1113 }
1114
1115 d->blockSize = blockSize;
1116}
1117
1118/*!
1119 \internal
1120
1121 Sets the \c MAX_SERVER_RESPONSE_DELAY value to \a responseDelay in milliseconds.
1122 The default is 250 seconds.
1123
1124 As defined in \l {RFC 7390 - Section 2.5}, \c MAX_SERVER_RESPONSE_DELAY is the expected
1125 maximum response delay over all servers that the client can send a multicast request to.
1126
1127 \sa maximumServerResponseDelay()
1128*/
1129void QCoapProtocol::setMaximumServerResponseDelay(uint responseDelay)
1130{
1131 Q_D(QCoapProtocol);
1132 d->maximumServerResponseDelay = responseDelay;
1133}
1134
1135/*!
1136 \internal
1137
1138 Sets the minimum token size to \a tokenSize in bytes. For security reasons it is
1139 recommended to use tokens with a length of at least 4 bytes. The default value for
1140 this parameter is 4 bytes.
1141*/
1142void QCoapProtocol::setMinimumTokenSize(int tokenSize)
1143{
1144 Q_D(QCoapProtocol);
1145
1146 if (tokenSize > 0 && tokenSize <= 8) {
1147 d->minimumTokenSize = tokenSize;
1148 } else {
1149 qCWarning(lcCoapProtocol,
1150 "Failed to set the minimum token size,"
1151 "it should not be more than 8 bytes and cannot be 0.");
1152 }
1153}
1154
1155QT_END_NAMESPACE
1156

source code of qtcoap/src/coap/qcoapprotocol.cpp