| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2018 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the test suite of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | #include <QtTest/QtTest> |
| 30 | |
| 31 | #include <QtNetwork/qhostaddress.h> |
| 32 | #include <QtNetwork/qsslsocket.h> |
| 33 | #include <QtNetwork/qudpsocket.h> |
| 34 | #include <QtNetwork/qdtls.h> |
| 35 | |
| 36 | #include <QtCore/qcryptographichash.h> |
| 37 | #include <QtCore/qsharedpointer.h> |
| 38 | #include <QtCore/qbytearray.h> |
| 39 | #include <QtCore/qstring.h> |
| 40 | #include <QtCore/qobject.h> |
| 41 | #include <QtCore/qtimer.h> |
| 42 | #include <QtCore/qdebug.h> |
| 43 | |
| 44 | #include <utility> |
| 45 | #include <vector> |
| 46 | |
| 47 | QT_BEGIN_NAMESPACE |
| 48 | |
| 49 | #define STOP_ON_FAILURE \ |
| 50 | if (QTest::currentTestFailed()) \ |
| 51 | return; |
| 52 | |
| 53 | class tst_QDtlsCookie : public QObject |
| 54 | { |
| 55 | Q_OBJECT |
| 56 | |
| 57 | public slots: |
| 58 | void initTestCase(); |
| 59 | void init(); |
| 60 | |
| 61 | private slots: |
| 62 | // Tests: |
| 63 | void construction(); |
| 64 | void validateParameters_data(); |
| 65 | void validateParameters(); |
| 66 | void verifyClient(); |
| 67 | void cookieGeneratorParameters(); |
| 68 | void verifyMultipleClients(); |
| 69 | |
| 70 | protected slots: |
| 71 | // Aux. functions: |
| 72 | void stopLoopOnMessage(); |
| 73 | void serverReadyRead(); |
| 74 | void clientReadyRead(); |
| 75 | void handleClientTimeout(); |
| 76 | void makeNoise(); |
| 77 | void spawnClients(); |
| 78 | |
| 79 | private: |
| 80 | void sendClientHello(QUdpSocket *socket, QDtls *handshake, |
| 81 | const QByteArray &serverMessage = {}); |
| 82 | void receiveMessage(QUdpSocket *socket, QByteArray *message, |
| 83 | QHostAddress *address = nullptr, |
| 84 | quint16 *port = nullptr); |
| 85 | |
| 86 | static QHostAddress toNonAny(const QHostAddress &addr); |
| 87 | |
| 88 | enum AddressType |
| 89 | { |
| 90 | ValidAddress, |
| 91 | NullAddress, |
| 92 | BroadcastAddress, |
| 93 | MulticastAddress |
| 94 | }; |
| 95 | |
| 96 | QUdpSocket serverSocket; |
| 97 | QHostAddress serverAddress; |
| 98 | quint16 serverPort = 0; |
| 99 | |
| 100 | QTestEventLoop testLoop; |
| 101 | int handshakeTimeoutMS = 500; |
| 102 | |
| 103 | QDtlsClientVerifier listener; |
| 104 | using HandshakePtr = QSharedPointer<QDtls>; |
| 105 | HandshakePtr dtls; |
| 106 | |
| 107 | const QCryptographicHash::Algorithm defaultHash = |
| 108 | #ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 |
| 109 | QCryptographicHash::Sha1; |
| 110 | #else |
| 111 | QCryptographicHash::Sha256; |
| 112 | #endif |
| 113 | |
| 114 | using CookieParams = QDtlsClientVerifier::GeneratorParameters; |
| 115 | |
| 116 | QUdpSocket noiseMaker; |
| 117 | QHostAddress spammerAddress; |
| 118 | QTimer noiseTimer; |
| 119 | quint16 spammerPort = 0; |
| 120 | const int noiseTimeoutMS = 5; |
| 121 | |
| 122 | using SocketPtr = QSharedPointer<QUdpSocket>; |
| 123 | using ValidClient = QPair<SocketPtr, HandshakePtr>; |
| 124 | unsigned clientsToWait = 0; |
| 125 | unsigned clientsToAdd = 0; |
| 126 | std::vector<ValidClient> dtlsClients; |
| 127 | QTimer spawnTimer; |
| 128 | }; |
| 129 | |
| 130 | QHostAddress tst_QDtlsCookie::toNonAny(const QHostAddress &addr) |
| 131 | { |
| 132 | if (addr == QHostAddress::Any || addr == QHostAddress::AnyIPv4) |
| 133 | return QHostAddress::LocalHost; |
| 134 | if (addr == QHostAddress::AnyIPv6) |
| 135 | return QHostAddress::LocalHostIPv6; |
| 136 | return addr; |
| 137 | } |
| 138 | |
| 139 | void tst_QDtlsCookie::initTestCase() |
| 140 | { |
| 141 | QVERIFY(noiseMaker.bind()); |
| 142 | spammerAddress = toNonAny(addr: noiseMaker.localAddress()); |
| 143 | spammerPort = noiseMaker.localPort(); |
| 144 | } |
| 145 | |
| 146 | void tst_QDtlsCookie::init() |
| 147 | { |
| 148 | if (serverSocket.state() != QAbstractSocket::UnconnectedState) { |
| 149 | serverSocket.close(); |
| 150 | // Disconnect stopLoopOnMessage or serverReadyRead slots: |
| 151 | serverSocket.disconnect(); |
| 152 | } |
| 153 | |
| 154 | QCOMPARE(serverSocket.state(), QAbstractSocket::UnconnectedState); |
| 155 | QVERIFY(serverSocket.bind()); |
| 156 | |
| 157 | serverAddress = toNonAny(addr: serverSocket.localAddress()); |
| 158 | serverPort = serverSocket.localPort(); |
| 159 | |
| 160 | dtls.reset(t: new QDtls(QSslSocket::SslClientMode)); |
| 161 | dtls->setPeer(address: serverAddress, port: serverPort); |
| 162 | } |
| 163 | |
| 164 | void tst_QDtlsCookie::construction() |
| 165 | { |
| 166 | QDtlsClientVerifier verifier; |
| 167 | |
| 168 | QCOMPARE(verifier.dtlsError(), QDtlsError::NoError); |
| 169 | QCOMPARE(verifier.dtlsErrorString(), QString()); |
| 170 | QCOMPARE(verifier.verifiedHello(), QByteArray()); |
| 171 | |
| 172 | const auto params = verifier.cookieGeneratorParameters(); |
| 173 | QCOMPARE(params.hash, defaultHash); |
| 174 | QVERIFY(params.secret.size() > 0); |
| 175 | } |
| 176 | |
| 177 | void tst_QDtlsCookie::validateParameters_data() |
| 178 | { |
| 179 | QTest::addColumn<bool>(name: "invalidSocket" ); |
| 180 | QTest::addColumn<bool>(name: "emptyDatagram" ); |
| 181 | QTest::addColumn<int>(name: "addressType" ); |
| 182 | |
| 183 | QTest::addRow(format: "socket" ) << true << false << int(ValidAddress); |
| 184 | QTest::addRow(format: "dgram" ) << false << true << int(ValidAddress); |
| 185 | QTest::addRow(format: "addr(invalid)" ) << false << false << int(NullAddress); |
| 186 | QTest::addRow(format: "addr(broadcast)" ) << false << false << int(BroadcastAddress); |
| 187 | QTest::addRow(format: "addr(multicast)" ) << false << false << int(MulticastAddress); |
| 188 | |
| 189 | QTest::addRow(format: "socket-dgram" ) << true << true << int(ValidAddress); |
| 190 | QTest::addRow(format: "socket-dgram-addr(invalid)" ) << true << true << int(NullAddress); |
| 191 | QTest::addRow(format: "socket-dgram-addr(broadcast)" ) << true << true << int(BroadcastAddress); |
| 192 | QTest::addRow(format: "socket-dgram-addr(multicast)" ) << true << true << int(MulticastAddress); |
| 193 | |
| 194 | QTest::addRow(format: "dgram-addr(invalid)" ) << false << true << int(NullAddress); |
| 195 | QTest::addRow(format: "dgram-addr(broadcast)" ) << false << true << int(BroadcastAddress); |
| 196 | QTest::addRow(format: "dgram-addr(multicast)" ) << false << true << int(MulticastAddress); |
| 197 | |
| 198 | QTest::addRow(format: "socket-addr(invalid)" ) << true << false << int(NullAddress); |
| 199 | QTest::addRow(format: "socket-addr(broadcast)" ) << true << false << int(BroadcastAddress); |
| 200 | QTest::addRow(format: "socket-addr(multicast)" ) << true << false << int(MulticastAddress); |
| 201 | } |
| 202 | |
| 203 | void tst_QDtlsCookie::validateParameters() |
| 204 | { |
| 205 | connect(sender: &serverSocket, signal: &QUdpSocket::readyRead, receiver: this, |
| 206 | slot: &tst_QDtlsCookie::stopLoopOnMessage); |
| 207 | |
| 208 | QFETCH(const bool, invalidSocket); |
| 209 | QFETCH(const bool, emptyDatagram); |
| 210 | QFETCH(const int, addressType); |
| 211 | |
| 212 | QUdpSocket clientSocket; |
| 213 | QByteArray hello; |
| 214 | QHostAddress clientAddress; |
| 215 | quint16 clientPort = 0; |
| 216 | |
| 217 | sendClientHello(socket: &clientSocket, handshake: dtls.data()); |
| 218 | STOP_ON_FAILURE |
| 219 | receiveMessage(socket: &serverSocket, message: &hello, address: &clientAddress, port: &clientPort); |
| 220 | STOP_ON_FAILURE |
| 221 | |
| 222 | switch (addressType) { |
| 223 | case MulticastAddress: |
| 224 | clientAddress.setAddress(QStringLiteral("224.0.0.0" )); |
| 225 | break; |
| 226 | case BroadcastAddress: |
| 227 | clientAddress = QHostAddress::Broadcast; |
| 228 | break; |
| 229 | case NullAddress: |
| 230 | clientAddress = {}; |
| 231 | break; |
| 232 | } |
| 233 | |
| 234 | if (emptyDatagram) |
| 235 | hello.clear(); |
| 236 | |
| 237 | QUdpSocket *socket = invalidSocket ? nullptr : &serverSocket; |
| 238 | QCOMPARE(listener.verifyClient(socket, hello, clientAddress, clientPort), false); |
| 239 | QCOMPARE(listener.verifiedHello(), QByteArray()); |
| 240 | QCOMPARE(listener.dtlsError(), QDtlsError::InvalidInputParameters); |
| 241 | } |
| 242 | |
| 243 | void tst_QDtlsCookie::verifyClient() |
| 244 | { |
| 245 | connect(sender: &serverSocket, signal: &QUdpSocket::readyRead, receiver: this, |
| 246 | slot: &tst_QDtlsCookie::stopLoopOnMessage); |
| 247 | |
| 248 | QUdpSocket clientSocket; |
| 249 | connect(sender: &clientSocket, signal: &QUdpSocket::readyRead, receiver: this, |
| 250 | slot: &tst_QDtlsCookie::stopLoopOnMessage); |
| 251 | |
| 252 | // Client: send an initial ClientHello message without any cookie: |
| 253 | sendClientHello(socket: &clientSocket, handshake: dtls.data()); |
| 254 | STOP_ON_FAILURE |
| 255 | // Server: read the first ClientHello message: |
| 256 | QByteArray dgram; |
| 257 | QHostAddress clientAddress; |
| 258 | quint16 clientPort = 0; |
| 259 | receiveMessage(socket: &serverSocket, message: &dgram, address: &clientAddress, port: &clientPort); |
| 260 | STOP_ON_FAILURE |
| 261 | // Server: reply with a verify hello request (the client is not verified yet): |
| 262 | QCOMPARE(listener.verifyClient(&serverSocket, dgram, clientAddress, clientPort), false); |
| 263 | QCOMPARE(listener.verifiedHello(), QByteArray()); |
| 264 | QCOMPARE(listener.dtlsError(), QDtlsError::NoError); |
| 265 | // Client: read hello verify request: |
| 266 | receiveMessage(socket: &clientSocket, message: &dgram); |
| 267 | STOP_ON_FAILURE |
| 268 | // Client: send a new hello message, this time with a cookie attached: |
| 269 | sendClientHello(socket: &clientSocket, handshake: dtls.data(), serverMessage: dgram); |
| 270 | STOP_ON_FAILURE |
| 271 | // Server: read a client-verified message: |
| 272 | receiveMessage(socket: &serverSocket, message: &dgram, address: &clientAddress, port: &clientPort); |
| 273 | STOP_ON_FAILURE |
| 274 | // Client's readyRead is not interesting anymore: |
| 275 | clientSocket.close(); |
| 276 | |
| 277 | // Verify with the address and port we extracted, do it twice (DTLS "listen" |
| 278 | // must be stateless and work as many times as needed): |
| 279 | for (int i = 0; i < 2; ++i) { |
| 280 | QCOMPARE(listener.verifyClient(&serverSocket, dgram, clientAddress, clientPort), true); |
| 281 | QCOMPARE(listener.verifiedHello(), dgram); |
| 282 | QCOMPARE(listener.dtlsError(), QDtlsError::NoError); |
| 283 | } |
| 284 | |
| 285 | // Test that another freshly created (stateless) verifier can verify: |
| 286 | QDtlsClientVerifier anotherListener; |
| 287 | QCOMPARE(anotherListener.verifyClient(&serverSocket, dgram, clientAddress, |
| 288 | clientPort), true); |
| 289 | QCOMPARE(anotherListener.verifiedHello(), dgram); |
| 290 | QCOMPARE(anotherListener.dtlsError(), QDtlsError::NoError); |
| 291 | |
| 292 | // Now, let's test if a DTLS server is able to create a new TLS session |
| 293 | // re-using the client's 'Hello' with a cookie inside: |
| 294 | QDtls session(QSslSocket::SslServerMode); |
| 295 | auto dtlsConf = QSslConfiguration::defaultDtlsConfiguration(); |
| 296 | dtlsConf.setDtlsCookieVerificationEnabled(true); |
| 297 | session.setDtlsConfiguration(dtlsConf); |
| 298 | session.setPeer(address: clientAddress, port: clientPort); |
| 299 | // Borrow a secret and hash algorithm: |
| 300 | session.setCookieGeneratorParameters(listener.cookieGeneratorParameters()); |
| 301 | // Trigger TLS state machine change to think it accepted a cookie and started |
| 302 | // a handshake: |
| 303 | QVERIFY(session.doHandshake(&serverSocket, dgram)); |
| 304 | |
| 305 | // Now let's use a wrong port: |
| 306 | QCOMPARE(listener.verifyClient(&serverSocket, dgram, clientAddress, serverPort), false); |
| 307 | // Invalid cookie, no verified hello message: |
| 308 | QCOMPARE(listener.verifiedHello(), QByteArray()); |
| 309 | // But it's UDP so we ignore this "fishy datagram", no error expected: |
| 310 | QCOMPARE(listener.dtlsError(), QDtlsError::NoError); |
| 311 | } |
| 312 | |
| 313 | void tst_QDtlsCookie::cookieGeneratorParameters() |
| 314 | { |
| 315 | CookieParams params;// By defualt, 'secret' is empty. |
| 316 | QCOMPARE(listener.setCookieGeneratorParameters(params), false); |
| 317 | QCOMPARE(listener.dtlsError(), QDtlsError::InvalidInputParameters); |
| 318 | params.secret = "abcdefghijklmnopqrstuvwxyz" ; |
| 319 | QCOMPARE(listener.setCookieGeneratorParameters(params), true); |
| 320 | QCOMPARE(listener.dtlsError(), QDtlsError::NoError); |
| 321 | } |
| 322 | |
| 323 | void tst_QDtlsCookie::verifyMultipleClients() |
| 324 | { |
| 325 | // 'verifyClient' above was quite simple - it's like working with blocking |
| 326 | // sockets, step by step - we write, then make sure we read a datagram back |
| 327 | // etc. This test is more asynchronous - we are running an event loop and don't |
| 328 | // stop on the first datagram received, instead, we spawn many clients |
| 329 | // with which to exchange handshake messages and verify requests, while at |
| 330 | // the same time dealing with a 'noise maker' - a fake DTLS client, who keeps |
| 331 | // spamming our server with non-DTLS datagrams and initial ClientHello |
| 332 | // messages, but never replies to client verify requests. |
| 333 | connect(sender: &serverSocket, signal: &QUdpSocket::readyRead, receiver: this, slot: &tst_QDtlsCookie::serverReadyRead); |
| 334 | |
| 335 | noiseTimer.setInterval(noiseTimeoutMS); |
| 336 | connect(sender: &noiseTimer, signal: &QTimer::timeout, receiver: this, slot: &tst_QDtlsCookie::makeNoise); |
| 337 | |
| 338 | spawnTimer.setInterval(noiseTimeoutMS * 10); |
| 339 | connect(sender: &spawnTimer, signal: &QTimer::timeout, receiver: this, slot: &tst_QDtlsCookie::spawnClients); |
| 340 | |
| 341 | noiseTimer.start(); |
| 342 | spawnTimer.start(); |
| 343 | |
| 344 | clientsToAdd = clientsToWait = 100; |
| 345 | |
| 346 | testLoop.enterLoopMSecs(ms: handshakeTimeoutMS * clientsToWait); |
| 347 | QVERIFY(!testLoop.timeout()); |
| 348 | QVERIFY(clientsToWait == 0); |
| 349 | } |
| 350 | |
| 351 | void tst_QDtlsCookie::sendClientHello(QUdpSocket *socket, QDtls *dtls, |
| 352 | const QByteArray &serverMessage) |
| 353 | { |
| 354 | Q_ASSERT(socket && dtls); |
| 355 | dtls->doHandshake(socket, dgram: serverMessage); |
| 356 | // We don't really care about QDtls in this auto-test, but must be |
| 357 | // sure that we, indeed, sent our hello and if not - stop early without |
| 358 | // running event loop: |
| 359 | QCOMPARE(dtls->dtlsError(), QDtlsError::NoError); |
| 360 | // We never complete a handshake, so it must be 'HandshakeInProgress': |
| 361 | QCOMPARE(dtls->handshakeState(), QDtls::HandshakeInProgress); |
| 362 | } |
| 363 | |
| 364 | void tst_QDtlsCookie::receiveMessage(QUdpSocket *socket, QByteArray *message, |
| 365 | QHostAddress *address, quint16 *port) |
| 366 | { |
| 367 | Q_ASSERT(socket && message); |
| 368 | |
| 369 | if (socket->pendingDatagramSize() <= 0) |
| 370 | testLoop.enterLoopMSecs(ms: handshakeTimeoutMS); |
| 371 | |
| 372 | QVERIFY(!testLoop.timeout()); |
| 373 | QVERIFY(socket->pendingDatagramSize()); |
| 374 | |
| 375 | message->resize(size: socket->pendingDatagramSize()); |
| 376 | const qint64 read = socket->readDatagram(data: message->data(), maxlen: message->size(), |
| 377 | host: address, port); |
| 378 | QVERIFY(read > 0); |
| 379 | |
| 380 | message->resize(size: read); |
| 381 | if (address) |
| 382 | QVERIFY(!address->isNull()); |
| 383 | } |
| 384 | |
| 385 | void tst_QDtlsCookie::stopLoopOnMessage() |
| 386 | { |
| 387 | testLoop.exitLoop(); |
| 388 | } |
| 389 | |
| 390 | void tst_QDtlsCookie::serverReadyRead() |
| 391 | { |
| 392 | Q_ASSERT(clientsToWait); |
| 393 | |
| 394 | if (serverSocket.pendingDatagramSize() <= 0) |
| 395 | return; |
| 396 | |
| 397 | QByteArray hello; |
| 398 | QHostAddress clientAddress; |
| 399 | quint16 clientPort = 0; |
| 400 | |
| 401 | receiveMessage(socket: &serverSocket, message: &hello, address: &clientAddress, port: &clientPort); |
| 402 | if (QTest::currentTestFailed()) |
| 403 | return testLoop.exitLoop(); |
| 404 | |
| 405 | const bool ok = listener.verifyClient(socket: &serverSocket, dgram: hello, address: clientAddress, port: clientPort); |
| 406 | if (listener.dtlsError() != QDtlsError::NoError) { |
| 407 | // exit early, let the test fail. |
| 408 | return testLoop.exitLoop(); |
| 409 | } |
| 410 | |
| 411 | if (!ok) // not verified yet. |
| 412 | return; |
| 413 | |
| 414 | if (clientAddress == spammerAddress && clientPort == spammerPort) // should never happen |
| 415 | return testLoop.exitLoop(); |
| 416 | |
| 417 | --clientsToWait; |
| 418 | if (!clientsToWait) // done, success. |
| 419 | testLoop.exitLoop(); |
| 420 | } |
| 421 | |
| 422 | void tst_QDtlsCookie::clientReadyRead() |
| 423 | { |
| 424 | QUdpSocket *clientSocket = qobject_cast<QUdpSocket *>(object: sender()); |
| 425 | Q_ASSERT(clientSocket); |
| 426 | |
| 427 | if (clientSocket->pendingDatagramSize() <= 0) |
| 428 | return; |
| 429 | |
| 430 | QDtls *handshake = nullptr; |
| 431 | for (ValidClient &client : dtlsClients) { |
| 432 | if (client.first.data() == clientSocket) { |
| 433 | handshake = client.second.data(); |
| 434 | break; |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | Q_ASSERT(handshake); |
| 439 | |
| 440 | QByteArray response; |
| 441 | receiveMessage(socket: clientSocket, message: &response); |
| 442 | if (QTest::currentTestFailed() || !handshake->doHandshake(socket: clientSocket, dgram: response)) |
| 443 | testLoop.exitLoop(); |
| 444 | } |
| 445 | |
| 446 | void tst_QDtlsCookie::makeNoise() |
| 447 | { |
| 448 | noiseMaker.writeDatagram(datagram: {"Hello, my little DTLS server, take this useless dgram!" }, |
| 449 | host: serverAddress, port: serverPort); |
| 450 | QDtls fakeHandshake(QSslSocket::SslClientMode); |
| 451 | fakeHandshake.setPeer(address: serverAddress, port: serverPort); |
| 452 | fakeHandshake.doHandshake(socket: &noiseMaker, dgram: {}); |
| 453 | } |
| 454 | |
| 455 | void tst_QDtlsCookie::spawnClients() |
| 456 | { |
| 457 | for (int i = 0; i < 10 && clientsToAdd; ++i, --clientsToAdd) { |
| 458 | ValidClient newClient; |
| 459 | newClient.first.reset(t: new QUdpSocket); |
| 460 | connect(sender: newClient.first.data(), signal: &QUdpSocket::readyRead, |
| 461 | receiver: this, slot: &tst_QDtlsCookie::clientReadyRead); |
| 462 | newClient.second.reset(t: new QDtls(QSslSocket::SslClientMode)); |
| 463 | newClient.second->setPeer(address: serverAddress, port: serverPort); |
| 464 | connect(sender: newClient.second.data(), signal: &QDtls::handshakeTimeout, |
| 465 | receiver: this, slot: &tst_QDtlsCookie::handleClientTimeout); |
| 466 | newClient.second->doHandshake(socket: newClient.first.data(), dgram: {}); |
| 467 | dtlsClients.push_back(x: std::move(newClient)); |
| 468 | } |
| 469 | } |
| 470 | |
| 471 | void tst_QDtlsCookie::handleClientTimeout() |
| 472 | { |
| 473 | QDtls *handshake = qobject_cast<QDtls *>(object: sender()); |
| 474 | Q_ASSERT(handshake); |
| 475 | |
| 476 | QUdpSocket *clientSocket = nullptr; |
| 477 | for (ValidClient &client : dtlsClients) { |
| 478 | if (client.second.data() == handshake) { |
| 479 | clientSocket = client.first.data(); |
| 480 | break; |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | Q_ASSERT(clientSocket); |
| 485 | handshake->handleTimeout(socket: clientSocket); |
| 486 | } |
| 487 | |
| 488 | QT_END_NAMESPACE |
| 489 | |
| 490 | QTEST_MAIN(tst_QDtlsCookie) |
| 491 | |
| 492 | #include "tst_qdtlscookie.moc" |
| 493 | |