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 "qcoapqudpconnection_p.h"
6
7#include <QtCore/qloggingcategory.h>
8#include <QtNetwork/qnetworkdatagram.h>
9
10#if QT_CONFIG(dtls)
11#include <QtNetwork/QDtls>
12#include <QtNetwork/QSslPreSharedKeyAuthenticator>
13#include <QtNetwork/QSslConfiguration>
14#include <QtNetwork/QSslKey>
15#endif
16
17QT_BEGIN_NAMESPACE
18
19Q_DECLARE_LOGGING_CATEGORY(lcCoapConnection)
20
21/*!
22 \internal
23
24 \class QCoapQUdpConnection
25 \inmodule QtCoap
26
27 \brief The QCoapQUdpConnection class handles the transfer of frames to
28 and from a server.
29
30 \reentrant
31
32 The QCoapQUdpConnection class is used by the QCoapClient class to send
33 requests to a server. It has a socket listening for UDP messages,
34 that is used to send the CoAP frames.
35
36 When a reply is available, the QCoapQUdpConnection object emits a readyRead()
37 signal.
38
39 \sa QCoapClient
40*/
41
42/*!
43 Constructs a new QCoapQUdpConnection for the given \a securityMode and
44 sets \a parent as the parent object.
45
46 \note Since QtCoap::RawPublicKey is not supported yet, the connection
47 will fall back to the QtCoap::NoSecurity in the QtCoap::RawPublicKey mode.
48 That is, the connection won't be secure in this mode.
49*/
50QCoapQUdpConnection::QCoapQUdpConnection(QtCoap::SecurityMode securityMode, QObject *parent) :
51 QCoapQUdpConnection(*new QCoapQUdpConnectionPrivate(securityMode), parent)
52{
53}
54
55/*!
56 \internal
57
58 Constructs a new QCoapQUdpConnection as a child of \a parent, with \a dd as
59 its \c d_ptr. This constructor must be used when internally subclassing
60 the QCoapQUdpConnection class.
61*/
62QCoapQUdpConnection::QCoapQUdpConnection(QCoapQUdpConnectionPrivate &dd, QObject *parent) :
63 QCoapConnection(dd, parent)
64{
65 Q_D(QCoapQUdpConnection);
66
67 createSocket();
68
69 if (isSecure()) {
70#if QT_CONFIG(dtls)
71 connect(sender: this, signal: &QCoapConnection::securityConfigurationChanged,
72 slot: [this]() {
73 Q_D(QCoapQUdpConnection);
74 d->setSecurityConfiguration(securityConfiguration());
75 });
76
77 auto configuration = QSslConfiguration::defaultDtlsConfiguration();
78
79 switch (d->securityMode) {
80 case QtCoap::SecurityMode::RawPublicKey:
81 qCWarning(lcCoapConnection, "RawPublicKey security is not supported yet,"
82 "disabling security");
83 d->securityMode = QtCoap::SecurityMode::NoSecurity;
84 break;
85 case QtCoap::SecurityMode::PreSharedKey:
86 d->dtls = new QDtls(QSslSocket::SslClientMode, this);
87 configuration.setPeerVerifyMode(QSslSocket::VerifyNone);
88 d->dtls->setDtlsConfiguration(configuration);
89
90 connect(sender: d->dtls.data(), signal: &QDtls::pskRequired, context: this, slot: &QCoapQUdpConnection::pskRequired);
91 connect(sender: d->dtls.data(), signal: &QDtls::handshakeTimeout,
92 context: this, slot: &QCoapQUdpConnection::handshakeTimeout);
93 break;
94 case QtCoap::SecurityMode::Certificate:
95 d->dtls = new QDtls(QSslSocket::SslClientMode, this);
96 configuration.setPeerVerifyMode(QSslSocket::VerifyPeer);
97 d->dtls->setDtlsConfiguration(configuration);
98
99 connect(sender: d->dtls.data(), signal: &QDtls::handshakeTimeout,
100 context: this, slot: &QCoapQUdpConnection::handshakeTimeout);
101 break;
102 default:
103 break;
104 }
105#else
106 qCWarning(lcCoapConnection, "DTLS is disabled, falling back to QtCoap::NoSecurity mode.");
107 d->securityMode = QtCoap::SecurityMode::NoSecurity;
108#endif
109 }
110}
111
112/*!
113 \internal
114
115 Creates the socket used by the connection and sets it in connection's private
116 class.
117*/
118void QCoapQUdpConnection::createSocket()
119{
120 Q_D(QCoapQUdpConnection);
121
122 d->udpSocket = new QUdpSocket(this);
123
124 connect(sender: d->udpSocket.data(), signal: &QUdpSocket::readyRead, slot: [this]() {
125 Q_D(QCoapQUdpConnection);
126 d->socketReadyRead();
127 });
128 connect(sender: d->udpSocket.data(), signal: &QUdpSocket::errorOccurred,
129 slot: [this](QAbstractSocket::SocketError socketError) {
130 qCWarning(lcCoapConnection) << "CoAP UDP socket error" << socketError
131 << socket()->errorString();
132 emit error(error: socketError);
133 });
134}
135
136QCoapQUdpConnectionPrivate::QCoapQUdpConnectionPrivate(QtCoap::SecurityMode security)
137 : QCoapConnectionPrivate(security)
138#if QT_CONFIG(dtls)
139 , dtls(nullptr)
140#endif
141 , udpSocket(nullptr)
142{
143}
144
145QCoapQUdpConnectionPrivate::~QCoapQUdpConnectionPrivate()
146{
147#if QT_CONFIG(dtls)
148 if (dtls && dtls->isConnectionEncrypted()) {
149 Q_ASSERT(udpSocket);
150 dtls->shutdown(socket: udpSocket);
151 }
152#endif
153}
154
155/*!
156 \internal
157
158 Binds the socket to a random port and returns \c true if it succeeds.
159*/
160bool QCoapQUdpConnectionPrivate::bind()
161{
162 return socket()->bind(addr: QHostAddress::Any, port: 0, mode: QAbstractSocket::ShareAddress);
163}
164
165/*!
166 \internal
167
168 Binds the socket if it is not already done and emits the bound() signal when
169 the socket is ready.
170*/
171void QCoapQUdpConnectionPrivate::bindSocket()
172{
173 Q_Q(QCoapQUdpConnection);
174
175 if (state != QCoapQUdpConnection::ConnectionState::Bound && bind())
176 emit q->bound();
177}
178
179/*!
180 \internal
181
182 Prepares the socket for data transmission to the given \a host and
183 \a port by binding the socket. In case of a secure connection also
184 starts the handshake with the server.
185 Emits the bound() signal when the transport is ready.
186*/
187void QCoapQUdpConnection::bind(const QString &host, quint16 port)
188{
189 Q_D(QCoapQUdpConnection);
190
191 if (!isSecure()) {
192 d->bindSocket();
193#if QT_CONFIG(dtls)
194 } else {
195 Q_ASSERT(d->dtls);
196 if (d->dtls->isConnectionEncrypted()) {
197 emit bound();
198 } else if (socket()->state() == QAbstractSocket::UnconnectedState) {
199 socket()->bind();
200 d->dtls->setPeer(address: QHostAddress(host), port);
201 if (!d->dtls->doHandshake(socket: d->socket()))
202 qCWarning(lcCoapConnection) << "Handshake error: " << d->dtls->dtlsErrorString();
203 }
204#else
205 Q_UNUSED(host);
206 Q_UNUSED(port);
207#endif
208 }
209}
210
211/*!
212 \internal
213
214 Sends the given \a data frame to the \a host at the \a port.
215*/
216void QCoapQUdpConnection::writeData(const QByteArray &data, const QString &host, quint16 port)
217{
218 Q_D(QCoapQUdpConnection);
219 d->writeToSocket(data, host, port);
220}
221
222/*!
223 \internal
224
225 \brief Close the UDP socket
226
227 In the case of a secure connection, this also interrupts any ongoing
228 hand-shake and shuts down the DTLS connection.
229*/
230void QCoapQUdpConnection::close()
231{
232 Q_D(QCoapQUdpConnection);
233
234#if QT_CONFIG(dtls)
235 if (isSecure()) {
236 if (d->dtls->handshakeState() == QDtls::HandshakeInProgress)
237 d->dtls->abortHandshake(socket: d->socket());
238
239 if (d->dtls->isConnectionEncrypted())
240 d->dtls->shutdown(socket: d->socket());
241 }
242#endif
243 d->socket()->close();
244}
245
246/*!
247 \internal
248
249 Sets the QUdpSocket socket \a option to \a value.
250*/
251void QCoapQUdpConnection::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value)
252{
253 Q_D(QCoapQUdpConnection);
254 d->socket()->setSocketOption(option, value);
255}
256
257/*!
258 \internal
259
260 Sends the given \a data frame to the \a host at the \a port.
261*/
262void QCoapQUdpConnectionPrivate::writeToSocket(const QByteArray &data, const QString &host, quint16 port)
263{
264#if QT_CONFIG(dtls)
265 Q_Q(QCoapQUdpConnection);
266#endif
267 if (!socket()->isWritable()) {
268 bool opened = socket()->open(mode: socket()->openMode() | QIODevice::WriteOnly);
269 if (!opened) {
270 qCWarning(lcCoapConnection, "Failed to open the UDP socket with write permission");
271 return;
272 }
273 }
274
275 QHostAddress hostAddress(host);
276 if (hostAddress.isNull()) {
277 qCWarning(lcCoapConnection) << "Invalid host IP address" << host
278 << "- only IPv4/IPv6 destination addresses are supported.";
279 return;
280 }
281
282 const qint64 bytesWritten =
283#if QT_CONFIG(dtls)
284 q->isSecure() ? (Q_ASSERT(dtls), dtls->writeDatagramEncrypted(socket: socket(), dgram: data)) :
285#endif
286 socket()->writeDatagram(datagram: data, host: hostAddress, port);
287
288 if (bytesWritten < 0)
289 qCWarning(lcCoapConnection) << "Failed to write datagram:" << socket()->errorString();
290}
291
292/*!
293 \internal
294
295 This slot reads all data stored in the socket and emits a readyRead()
296 signal for each received datagram.
297*/
298void QCoapQUdpConnectionPrivate::socketReadyRead()
299{
300 Q_Q(QCoapQUdpConnection);
301
302 if (!socket()->isReadable()) {
303 bool opened = socket()->open(mode: socket()->openMode() | QIODevice::ReadOnly);
304 if (!opened) {
305 qCWarning(lcCoapConnection, "Failed to open the UDP socket with read permission");
306 return;
307 }
308 }
309
310 while (socket()->hasPendingDatagrams()) {
311 if (!q->isSecure()) {
312 const auto &datagram = socket()->receiveDatagram();
313 emit q->readyRead(data: datagram.data(), sender: datagram.senderAddress());
314#if QT_CONFIG(dtls)
315 } else {
316 handleEncryptedDatagram();
317#endif
318 }
319 }
320}
321
322/*!
323 \internal
324
325 Returns the socket.
326*/
327QUdpSocket *QCoapQUdpConnection::socket() const
328{
329 Q_D(const QCoapQUdpConnection);
330 return d->udpSocket;
331}
332
333/*!
334 \internal
335
336 Sets the DTLS configuration.
337*/
338void QCoapQUdpConnectionPrivate::setSecurityConfiguration(
339 const QCoapSecurityConfiguration &configuration)
340{
341#if QT_CONFIG(dtls)
342 Q_ASSERT(dtls);
343 auto dtlsConfig = dtls->dtlsConfiguration();
344
345 if (!configuration.defaultCipherString().isEmpty()) {
346 dtlsConfig.setBackendConfigurationOption(name: "CipherString",
347 value: configuration.defaultCipherString());
348 }
349
350 if (!configuration.caCertificates().isEmpty())
351 dtlsConfig.setCaCertificates(configuration.caCertificates().toList());
352
353 if (!configuration.localCertificateChain().isEmpty())
354 dtlsConfig.setLocalCertificateChain(configuration.localCertificateChain().toList());
355
356 if (!configuration.privateKey().isNull()) {
357 if (configuration.privateKey().algorithm() != QSsl::Opaque) {
358 QSslKey privateKey(configuration.privateKey().key(),
359 configuration.privateKey().algorithm(),
360 configuration.privateKey().encodingFormat(),
361 QSsl::PrivateKey,
362 configuration.privateKey().passPhrase());
363 dtlsConfig.setPrivateKey(privateKey);
364 } else if (configuration.privateKey().handle()) {
365 QSslKey opaqueKey(configuration.privateKey().handle());
366 dtlsConfig.setPrivateKey(opaqueKey);
367 } else {
368 qCWarning(lcCoapConnection, "Failed to set private key, the provided key is invalid");
369 }
370 }
371
372 dtls->setDtlsConfiguration(dtlsConfig);
373#else
374 Q_UNUSED(configuration);
375#endif
376}
377
378#if QT_CONFIG(dtls)
379/*!
380 \internal
381
382 This slot is invoked when PSK authentication is required. It is used
383 for setting the identity and pre shared key in order for the TLS handshake
384 to complete.
385*/
386void QCoapQUdpConnection::pskRequired(QSslPreSharedKeyAuthenticator *authenticator)
387{
388 Q_ASSERT(authenticator);
389 authenticator->setIdentity(securityConfiguration().preSharedKeyIdentity());
390 authenticator->setPreSharedKey(securityConfiguration().preSharedKey());
391}
392
393/*!
394 \internal
395
396 This slot handles handshake timeouts.
397*/
398void QCoapQUdpConnection::handshakeTimeout()
399{
400 Q_D(QCoapQUdpConnection);
401
402 qCWarning(lcCoapConnection, "Handshake timeout, trying to re-transmit");
403 if (d->dtls->handshakeState() == QDtls::HandshakeInProgress &&
404 !d->dtls->handleTimeout(socket: d->udpSocket)) {
405 qCWarning(lcCoapConnection) << "Failed to re-transmit" << d->dtls->dtlsErrorString();
406 }
407}
408
409/*!
410 \internal
411
412 Returns a decrypted datagram received from a UDP socket.
413*/
414QNetworkDatagram QCoapQUdpConnectionPrivate::receiveDatagramDecrypted() const
415{
416 auto datagram = socket()->receiveDatagram();
417 const QByteArray plainText = dtls->decryptDatagram(socket: socket(), dgram: datagram.data());
418 datagram.setData(plainText);
419 return datagram;
420}
421
422/*!
423 \internal
424
425 If the connection is encrypted, emits the readyRead() signal for
426 the pending datagram. Otherwise starts the DTLS handshake and
427 if it is completed, starts to send the pending request.
428*/
429void QCoapQUdpConnectionPrivate::handleEncryptedDatagram()
430{
431 Q_Q(QCoapQUdpConnection);
432
433 Q_ASSERT(dtls);
434
435 if (dtls->isConnectionEncrypted()) {
436 const auto &datagram = receiveDatagramDecrypted();
437 emit q->readyRead(data: datagram.data(), sender: datagram.senderAddress());
438 } else {
439 if (!dtls->doHandshake(socket: socket(), dgram: socket()->receiveDatagram().data())) {
440 qCWarning(lcCoapConnection) << "Handshake error: " << dtls->dtlsErrorString();
441 return;
442 }
443
444 // The handshake is successfully done, emit the bound() signal to inform
445 // listeners that the socket is ready to start transferring data.
446 if (dtls->isConnectionEncrypted())
447 emit q->bound();
448 }
449}
450
451#endif // dtls
452
453QT_END_NAMESPACE
454

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