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 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | Q_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 | */ |
85 | QCoapProtocol::QCoapProtocol(QObject *parent) : |
86 | QObject(*new QCoapProtocolPrivate, parent) |
87 | { |
88 | qRegisterMetaType<QCoapInternalRequest *>(); |
89 | qRegisterMetaType<QHostAddress>(); |
90 | } |
91 | |
92 | QCoapProtocol::~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 | */ |
107 | void 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 | */ |
189 | void 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 | */ |
217 | void 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 | */ |
239 | void 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 | */ |
255 | void 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 | */ |
276 | void 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 | */ |
287 | void 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 | */ |
320 | void 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 | */ |
389 | QCoapInternalRequest *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 | */ |
403 | QPointer<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 | */ |
417 | QList<QSharedPointer<QCoapInternalReply>> |
418 | QCoapProtocolPrivate::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 | */ |
432 | QCoapInternalReply *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 | */ |
446 | QCoapInternalRequest *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 | */ |
461 | QCoapInternalRequest *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 | */ |
480 | void 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 | */ |
564 | void 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 | */ |
586 | void 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 | */ |
608 | void 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 | */ |
636 | void 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 | */ |
652 | quint16 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 | */ |
668 | QCoapToken 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 | */ |
689 | QCoapInternalReply *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 | */ |
705 | void 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 | */ |
720 | void 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 | */ |
745 | void 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 | */ |
764 | bool 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 | */ |
787 | bool 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 | */ |
799 | bool 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 | */ |
809 | bool 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 | */ |
825 | bool 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 | */ |
840 | bool 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 | */ |
856 | bool 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 | */ |
878 | uint 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 | */ |
892 | double 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 | */ |
907 | uint 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 | */ |
921 | quint16 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 | */ |
936 | uint 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 | */ |
953 | uint 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 | */ |
969 | uint 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 | */ |
983 | uint 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 | */ |
996 | uint 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 | */ |
1011 | uint 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 | */ |
1027 | uint 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 | */ |
1045 | void 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 | */ |
1060 | void 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 | */ |
1078 | void 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 | */ |
1100 | void 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 | */ |
1129 | void 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 | */ |
1142 | void 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 | |
1155 | QT_END_NAMESPACE |
1156 | |