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

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