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// Qt-Security score:critical reason:network-protocol
5
6#include "qcoaprequest.h"
7#include "qcoapinternalrequest_p.h"
8
9#include <QtCore/qmath.h>
10#include <QtCore/qrandom.h>
11#include <QtCore/qregularexpression.h>
12#include <QtCore/qloggingcategory.h>
13#include <QtNetwork/QHostAddress>
14
15QT_BEGIN_NAMESPACE
16
17/*!
18 \internal
19
20 \class QCoapInternalRequest
21 \brief The QCoapInternalRequest class contains data related to
22 a message that needs to be sent.
23
24 \reentrant
25
26 \sa QCoapInternalMessage, QCoapInternalReply
27*/
28
29/*!
30 \internal
31 Constructs a new QCoapInternalRequest object and sets \a parent as
32 the parent object.
33*/
34QCoapInternalRequest::QCoapInternalRequest(QObject *parent) :
35 QCoapInternalMessage(*new QCoapInternalRequestPrivate, parent)
36{
37 Q_D(QCoapInternalRequest);
38 d->timeoutTimer = new QTimer(this);
39 connect(sender: d->timeoutTimer, signal: &QTimer::timeout, context: this, slot: [this]() { emit timeout(this); });
40
41 d->maxTransmitWaitTimer = new QTimer(this);
42 connect(sender: d->maxTransmitWaitTimer, signal: &QTimer::timeout, context: this,
43 slot: [this]() { emit maxTransmissionSpanReached(this); });
44
45 d->multicastExpireTimer = new QTimer(this);
46 connect(sender: d->multicastExpireTimer, signal: &QTimer::timeout, context: this,
47 slot: [this]() { emit multicastRequestExpired(this); });
48}
49
50/*!
51 \internal
52 Constructs a new QCoapInternalRequest object with the information of
53 \a request and sets \a parent as the parent object.
54*/
55QCoapInternalRequest::QCoapInternalRequest(const QCoapRequest &request, QObject *parent) :
56 QCoapInternalRequest(parent)
57{
58 Q_D(QCoapInternalRequest);
59 d->message = request;
60 d->method = request.method();
61 d->fullPayload = request.payload();
62
63 addUriOptions(uri: request.url(), proxyUri: request.proxyUrl());
64}
65
66/*!
67 \internal
68 Returns \c true if the request is considered valid.
69*/
70bool QCoapInternalRequest::isValid() const
71{
72 Q_D(const QCoapInternalRequest);
73 return isUrlValid(url: d->targetUri) && d->method != QtCoap::Method::Invalid;
74}
75
76/*!
77 \internal
78 Initialize parameters to transform the QCoapInternalRequest into an
79 empty message (RST or ACK) with the message id \a messageId.
80
81 An empty message should contain only the \a messageId.
82*/
83void QCoapInternalRequest::initEmptyMessage(quint16 messageId, QCoapMessage::Type type)
84{
85 Q_D(QCoapInternalRequest);
86
87 Q_ASSERT(type == QCoapMessage::Type::Acknowledgment || type == QCoapMessage::Type::Reset);
88
89 setMethod(QtCoap::Method::Invalid);
90 d->message.setType(type);
91 d->message.setMessageId(messageId);
92 d->message.setToken(QByteArray());
93 d->message.setPayload(QByteArray());
94 d->message.clearOptions();
95}
96
97/*!
98 \internal
99 Explicitly casts \a value to a char and appends it to the \a buffer.
100*/
101template<typename T>
102static void appendByte(QByteArray *buffer, T value) {
103 buffer->append(c: static_cast<char>(value));
104}
105
106/*!
107 \internal
108 Returns the CoAP frame corresponding to the QCoapInternalRequest into
109 a QByteArray object.
110
111 For more details, refer to section
112 \l{https://tools.ietf.org/html/rfc7252#section-3}{'Message format' of RFC 7252}.
113*/
114//! 0 1 2 3
115//! 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
116//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
117//! |Ver| T | TKL | Code | Message ID |
118//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
119//! | Token (if any, TKL bytes) ...
120//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
121//! | Options (if any) ...
122//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
123//! |1 1 1 1 1 1 1 1| Payload (if any) ...
124//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
125QByteArray QCoapInternalRequest::toQByteArray() const
126{
127 Q_D(const QCoapInternalRequest);
128 QByteArray pdu;
129
130 // Insert header
131 appendByte(buffer: &pdu, value: (d->message.version() << 6) // CoAP version
132 | (static_cast<quint8>(d->message.type()) << 4) // Message type
133 | d->message.token().size()); // Token Length
134 appendByte(buffer: &pdu, value: static_cast<quint8>(d->method) & 0xFF); // Method code
135 appendByte(buffer: &pdu, value: (d->message.messageId() >> 8) & 0xFF); // Message ID
136 appendByte(buffer: &pdu, value: d->message.messageId() & 0xFF);
137
138 // Insert Token
139 pdu.append(a: d->message.token());
140
141 // Insert Options
142 if (!d->message.options().isEmpty()) {
143 const auto options = d->message.options();
144
145 // Options should be sorted in order of their option numbers
146 Q_ASSERT(std::is_sorted(d->message.options().cbegin(), d->message.options().cend(),
147 [](const QCoapOption &a, const QCoapOption &b) -> bool {
148 return a.name() < b.name();
149 }));
150
151 quint8 lastOptionNumber = 0;
152 for (const QCoapOption &option : std::as_const(t: options)) {
153
154 quint16 optionDelta = static_cast<quint16>(option.name()) - lastOptionNumber;
155 bool isOptionDeltaExtended = false;
156 quint8 optionDeltaExtended = 0;
157
158 // Delta value > 12 : special values
159 if (optionDelta > 268) {
160 optionDeltaExtended = static_cast<quint8>(optionDelta - 269);
161 optionDelta = 14;
162 isOptionDeltaExtended = true;
163 } else if (optionDelta > 12) {
164 optionDeltaExtended = static_cast<quint8>(optionDelta - 13);
165 optionDelta = 13;
166 isOptionDeltaExtended = true;
167 }
168
169 quint16 optionLength = static_cast<quint16>(option.length());
170 bool isOptionLengthExtended = false;
171 quint8 optionLengthExtended = 0;
172
173 // Length > 12 : special values
174 if (optionLength > 268) {
175 optionLengthExtended = static_cast<quint8>(optionLength - 269);
176 optionLength = 14;
177 isOptionLengthExtended = true;
178 } else if (optionLength > 12) {
179 optionLengthExtended = static_cast<quint8>(optionLength - 13);
180 optionLength = 13;
181 isOptionLengthExtended = true;
182 }
183
184 appendByte(buffer: &pdu, value: (optionDelta << 4) | (optionLength & 0x0F));
185
186 if (isOptionDeltaExtended)
187 appendByte(buffer: &pdu, value: optionDeltaExtended);
188 if (isOptionLengthExtended)
189 appendByte(buffer: &pdu, value: optionLengthExtended);
190
191 pdu.append(a: option.opaqueValue());
192
193 lastOptionNumber = option.name();
194 }
195 }
196
197 // Insert Payload
198 if (!d->message.payload().isEmpty()) {
199 appendByte(buffer: &pdu, value: 0xFF);
200 pdu.append(a: d->message.payload());
201 }
202
203 return pdu;
204}
205
206/*!
207 \internal
208 Initializes block parameters and creates the options needed to request the
209 block \a blockNumber with a size of \a blockSize.
210
211 \sa blockOption(), setToSendBlock()
212*/
213void QCoapInternalRequest::setToRequestBlock(uint blockNumber, uint blockSize)
214{
215 Q_D(QCoapInternalRequest);
216
217 if (!checkBlockNumber(blockNumber))
218 return;
219
220 d->message.removeOption(name: QCoapOption::Block1);
221 d->message.removeOption(name: QCoapOption::Block2);
222
223 addOption(option: blockOption(name: QCoapOption::Block2, blockNumber, blockSize));
224}
225
226/*!
227 \internal
228 Initialize blocks parameters and creates the options needed to send the block with
229 the number \a blockNumber and with a size of \a blockSize.
230
231 \sa blockOption(), setToRequestBlock()
232*/
233void QCoapInternalRequest::setToSendBlock(uint blockNumber, uint blockSize)
234{
235 Q_D(QCoapInternalRequest);
236
237 if (!checkBlockNumber(blockNumber))
238 return;
239
240 d->message.setPayload(d->fullPayload.mid(index: static_cast<int>(blockNumber * blockSize),
241 len: static_cast<int>(blockSize)));
242 d->message.removeOption(name: QCoapOption::Block1);
243
244 addOption(option: blockOption(name: QCoapOption::Block1, blockNumber, blockSize));
245}
246
247/*!
248 \internal
249 Returns \c true if the block number is valid, \c false otherwise.
250 If the block number is not valid, logs a warning message.
251*/
252bool QCoapInternalRequest::checkBlockNumber(uint blockNumber)
253{
254 if (blockNumber >> 20) {
255 qCWarning(lcCoapExchange) << "Block number" << blockNumber
256 << "is too large. It should fit in 20 bits.";
257 return false;
258 }
259
260 return true;
261}
262
263/*!
264 \internal
265 Builds and returns a Block option.
266
267 The \a blockSize should range from 16 to 1024 and be a power of 2,
268 computed as 2^(SZX + 4), with SZX ranging from 0 to 6. For more details,
269 refer to the \l{https://tools.ietf.org/html/rfc7959#section-2.2}{RFC 7959}.
270*/
271QCoapOption QCoapInternalRequest::blockOption(QCoapOption::OptionName name, uint blockNumber, uint blockSize) const
272{
273 Q_D(const QCoapInternalRequest);
274
275 Q_ASSERT((blockSize & (blockSize - 1)) == 0); // is a power of two
276 Q_ASSERT(!(blockSize >> 11)); // blockSize <= 1024
277
278 // NUM field: the relative number of the block within a sequence of blocks
279 // 4, 12 or 20 bits (as little as possible)
280 Q_ASSERT(!(blockNumber >> 20)); // Fits in 20 bits
281 quint32 optionData = (blockNumber << 4);
282
283 // SZX field: the size of the block
284 // 3 bits, set to "log2(blockSize) - 4"
285 optionData |= (blockSize >> 7)
286 ? ((blockSize >> 10) ? 6 : (3 + (blockSize >> 8)))
287 : (blockSize >> 5);
288
289 // M field: whether more blocks are following
290 // 1 bit
291 if (name == QCoapOption::Block1
292 && static_cast<int>((blockNumber + 1) * blockSize) < d->fullPayload.size()) {
293 optionData |= 8;
294 }
295
296 QByteArray optionValue;
297 Q_ASSERT(!(optionData >> 24));
298 if (optionData > 0xFFFF)
299 appendByte(buffer: &optionValue, value: optionData >> 16);
300 if (optionData > 0xFF)
301 appendByte(buffer: &optionValue, value: (optionData >> 8) & 0xFF);
302 appendByte(buffer: &optionValue, value: optionData & 0xFF);
303
304 return QCoapOption(name, optionValue);
305}
306
307/*!
308 \internal
309 Sets the request's message id.
310*/
311void QCoapInternalRequest::setMessageId(quint16 id)
312{
313 Q_D(QCoapInternalRequest);
314 d->message.setMessageId(id);
315}
316
317/*!
318 \internal
319 Sets the request's token.
320*/
321void QCoapInternalRequest::setToken(const QCoapToken &token)
322{
323 Q_D(QCoapInternalRequest);
324 d->message.setToken(token);
325}
326
327/*!
328 \internal
329 Adds the given CoAP \a option and sets block parameters if needed.
330*/
331void QCoapInternalRequest::addOption(const QCoapOption &option)
332{
333 if (option.name() == QCoapOption::Block1)
334 setFromDescriptiveBlockOption(option);
335
336 QCoapInternalMessage::addOption(option);
337}
338
339/*!
340 \internal
341 Adds the CoAP options related to the target and proxy with the given \a uri
342 and \a proxyUri. Returns \c true upon success, \c false if an error
343 occurred.
344
345 Numbers refer to step numbers from CoAP
346 \l{RFC 7252}{https://tools.ietf.org/html/rfc7252#section-6.4}.
347*/
348bool QCoapInternalRequest::addUriOptions(QUrl uri, const QUrl &proxyUri)
349{
350 Q_D(QCoapInternalRequest);
351 // Set to an invalid state
352 d->targetUri = QUrl();
353
354 // When using a proxy uri, we SHOULD NOT include Uri-Host/Port/Path/Query
355 // options.
356 if (!proxyUri.isEmpty()) {
357 if (!isUrlValid(url: proxyUri))
358 return false;
359
360 addOption(option: QCoapOption(QCoapOption::ProxyUri, proxyUri.toString()));
361 d->targetUri = proxyUri;
362 return true;
363 }
364
365 uri = uri.adjusted(options: QUrl::NormalizePathSegments);
366
367 // 1/3/4. Fails if URL is relative, has no 'coap' scheme or has a fragment
368 if (!isUrlValid(url: uri))
369 return false;
370
371 // 2. Ensure encoding matches CoAP standard (i.e. uri is in ASCII encoding)
372 const auto uriStr = uri.toString();
373 bool isAscii = std::all_of(first: uriStr.cbegin(), last: uriStr.cend(),
374 pred: [](const QChar &ch) {
375 return (ch.unicode() < 128);
376 });
377 if (!isAscii)
378 return false;
379
380 // 5. Add Uri-Host option if not a plain IP
381 QCoapOption uriHost = uriHostOption(uri);
382 if (uriHost.isValid())
383 addOption(option: uriHost);
384
385 // 6. Port should be set at this point
386 Q_ASSERT(uri.port() != -1);
387
388 // 7. Add port to options if it is not the default port
389 if (uri.port() != QtCoap::DefaultPort && uri.port() != QtCoap::DefaultSecurePort)
390 addOption(name: QCoapOption::UriPort, value: static_cast<quint32>(uri.port()));
391
392 // 8. Add path segments to options
393 const auto path = uri.path();
394 const auto listPath = QStringView{path}.split(sep: QLatin1Char('/'));
395 for (auto pathPart : listPath) {
396 if (!pathPart.isEmpty())
397 addOption(option: QCoapOption(QCoapOption::UriPath, pathPart.toString()));
398 }
399
400 // 9. Add queries to options
401 QString query = uri.query();
402 const auto listQuery = QStringView{query}.split(sep: QLatin1Char('&'));
403 for (auto queryElement : listQuery) {
404 if (!queryElement.isEmpty())
405 addOption(option: QCoapOption(QCoapOption::UriQuery, queryElement.toString()));
406 }
407
408 d->targetUri = uri;
409 return true;
410}
411
412/*!
413 \internal
414 Returns the token of the request.
415*/
416QCoapToken QCoapInternalRequest::token() const
417{
418 return message()->token();
419}
420
421/*!
422 \internal
423 Used to mark the transmission as "in progress", when starting or retrying
424 to transmit a message. This method manages the retransmission counter,
425 the transmission timeout and the exchange timeout.
426*/
427void QCoapInternalRequest::restartTransmission()
428{
429 Q_D(QCoapInternalRequest);
430
431 if (!d->transmissionInProgress) {
432 d->transmissionInProgress = true;
433 d->maxTransmitWaitTimer->start();
434 } else {
435 d->retransmissionCounter++;
436 d->timeout *= 2;
437 }
438
439 if (d->timeout > 0)
440 d->timeoutTimer->start(msec: static_cast<int>(d->timeout));
441}
442
443/*!
444 \internal
445
446 Starts the timer for keeping the multicast request \e alive.
447*/
448void QCoapInternalRequest::startMulticastTransmission()
449{
450 Q_ASSERT(isMulticast());
451
452 Q_D(QCoapInternalRequest);
453 d->multicastExpireTimer->start();
454}
455
456/*!
457 \internal
458 Marks the transmission as not running, after a successful reception or an
459 error. It resets the retransmission count if needed and stops all timeout timers.
460*/
461void QCoapInternalRequest::stopTransmission()
462{
463 Q_D(QCoapInternalRequest);
464 if (isMulticast()) {
465 d->multicastExpireTimer->stop();
466 } else {
467 d->transmissionInProgress = false;
468 d->retransmissionCounter = 0;
469 d->maxTransmitWaitTimer->stop();
470 d->timeoutTimer->stop();
471 }
472}
473
474/*!
475 \internal
476 Returns the target uri.
477
478 \sa setTargetUri()
479*/
480QUrl QCoapInternalRequest::targetUri() const
481{
482 Q_D(const QCoapInternalRequest);
483 return d->targetUri;
484}
485
486/*!
487 \internal
488 Returns the connection used to send this request.
489
490 \sa setConnection()
491*/
492QCoapConnection *QCoapInternalRequest::connection() const
493{
494 Q_D(const QCoapInternalRequest);
495 return d->connection;
496}
497
498/*!
499 \internal
500 Returns the method of the request.
501
502 \sa setMethod()
503*/
504QtCoap::Method QCoapInternalRequest::method() const
505{
506 Q_D(const QCoapInternalRequest);
507 return d->method;
508}
509
510/*!
511 \internal
512 Returns true if the request is an Observe request.
513
514*/
515bool QCoapInternalRequest::isObserve() const
516{
517 Q_D(const QCoapInternalRequest);
518 return d->message.hasOption(name: QCoapOption::Observe);
519}
520
521/*!
522 \internal
523 Returns true if the observe request needs to be cancelled.
524
525 \sa setCancelObserve()
526*/
527bool QCoapInternalRequest::isObserveCancelled() const
528{
529 Q_D(const QCoapInternalRequest);
530 return d->observeCancelled;
531}
532
533/*!
534 \internal
535
536 Returns \c true if the request is multicast, returns \c false otherwise.
537*/
538bool QCoapInternalRequest::isMulticast() const
539{
540 const QHostAddress hostAddress(targetUri().host());
541 return hostAddress.isMulticast();
542}
543
544/*!
545 \internal
546 Returns the value of the retransmission counter.
547*/
548uint QCoapInternalRequest::retransmissionCounter() const
549{
550 Q_D(const QCoapInternalRequest);
551 return d->retransmissionCounter;
552}
553
554/*!
555 \internal
556 Sets the method of the request to the given \a method.
557
558 \sa method()
559*/
560void QCoapInternalRequest::setMethod(QtCoap::Method method)
561{
562 Q_D(QCoapInternalRequest);
563 d->method = method;
564}
565
566/*!
567 \internal
568 Sets the connection to use to send this request to the given \a connection.
569
570 \sa connection()
571*/
572void QCoapInternalRequest::setConnection(QCoapConnection *connection)
573{
574 Q_D(QCoapInternalRequest);
575 d->connection = connection;
576}
577
578/*!
579 \internal
580 Marks the observe request as cancelled.
581
582 \sa isObserveCancelled()
583*/
584void QCoapInternalRequest::setObserveCancelled()
585{
586 Q_D(QCoapInternalRequest);
587 d->observeCancelled = true;
588}
589
590/*!
591 \internal
592 Sets the target uri to the given \a targetUri.
593
594 \sa targetUri()
595*/
596void QCoapInternalRequest::setTargetUri(QUrl targetUri)
597{
598 Q_D(QCoapInternalRequest);
599 d->targetUri = targetUri;
600}
601
602/*!
603 \internal
604 Sets the timeout to the given \a timeout value in milliseconds. Timeout is
605 used for reliable transmission of Confirmable messages.
606
607 When such request times out, its timeout value will double.
608*/
609void QCoapInternalRequest::setTimeout(uint timeout)
610{
611 Q_D(QCoapInternalRequest);
612 d->timeout = timeout;
613}
614
615/*!
616 \internal
617 Sets the maximum transmission span for the request. If the request is
618 not finished at the end of the transmission span, the request will timeout.
619*/
620void QCoapInternalRequest::setMaxTransmissionWait(uint duration)
621{
622 Q_D(QCoapInternalRequest);
623 d->maxTransmitWaitTimer->setInterval(static_cast<int>(duration));
624}
625
626/*!
627 \internal
628
629 Sets the timeout interval in milliseconds for keeping the multicast request
630 \e alive.
631
632 In the unicast case, receiving a response means that the request is finished.
633 In the multicast case it is not known how many responses will be received, so
634 the response, along with its token, will be kept for
635 NON_LIFETIME + MAX_LATENCY + MAX_SERVER_RESPONSE_DELAY time, as suggested
636 in \l {RFC 7390 - Section 2.5}.
637*/
638void QCoapInternalRequest::setMulticastTimeout(uint responseDelay)
639{
640 Q_D(QCoapInternalRequest);
641 d->multicastExpireTimer->setInterval(static_cast<int>(responseDelay));
642}
643
644/*!
645 \internal
646 Decode the \a uri provided and returns a QCoapOption.
647*/
648QCoapOption QCoapInternalRequest::uriHostOption(const QUrl &uri) const
649{
650 QHostAddress address(uri.host());
651
652 // No need for Uri-Host option with an IPv4 or IPv6 address
653 if (!address.isNull())
654 return QCoapOption();
655
656 return QCoapOption(QCoapOption::UriHost, uri.host());
657}
658
659QT_END_NAMESPACE
660

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