| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 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/QTest> |
| 30 | #include <QtTest/QSignalSpy> |
| 31 | #include <QtTest/QTestEventLoop> |
| 32 | |
| 33 | #include <QtCore/QCoreApplication> |
| 34 | #include <QtCore/QTimer> |
| 35 | #include <QtCore/QSocketNotifier> |
| 36 | #include <QtNetwork/QTcpServer> |
| 37 | #include <QtNetwork/QTcpSocket> |
| 38 | #include <QtNetwork/QUdpSocket> |
| 39 | #ifndef Q_OS_WINRT |
| 40 | #include <private/qnativesocketengine_p.h> |
| 41 | #else |
| 42 | #include <private/qnativesocketengine_winrt_p.h> |
| 43 | #endif |
| 44 | #define NATIVESOCKETENGINE QNativeSocketEngine |
| 45 | #ifdef Q_OS_UNIX |
| 46 | #include <private/qnet_unix_p.h> |
| 47 | #include <sys/select.h> |
| 48 | #endif |
| 49 | #include <limits> |
| 50 | |
| 51 | #if defined (Q_CC_MSVC) && defined(max) |
| 52 | # undef max |
| 53 | # undef min |
| 54 | #endif // Q_CC_MSVC |
| 55 | |
| 56 | |
| 57 | class tst_QSocketNotifier : public QObject |
| 58 | { |
| 59 | Q_OBJECT |
| 60 | private slots: |
| 61 | void unexpectedDisconnection(); |
| 62 | void mixingWithTimers(); |
| 63 | #ifdef Q_OS_UNIX |
| 64 | void posixSockets(); |
| 65 | #endif |
| 66 | void asyncMultipleDatagram(); |
| 67 | void activationReason_data(); |
| 68 | void activationReason(); |
| 69 | void legacyConnect(); |
| 70 | |
| 71 | protected slots: |
| 72 | void async_readDatagramSlot(); |
| 73 | void async_writeDatagramSlot(); |
| 74 | |
| 75 | private: |
| 76 | QUdpSocket *m_asyncSender; |
| 77 | QUdpSocket *m_asyncReceiver; |
| 78 | }; |
| 79 | |
| 80 | static QHostAddress makeNonAny(const QHostAddress &address, |
| 81 | QHostAddress::SpecialAddress preferForAny = QHostAddress::LocalHost) |
| 82 | { |
| 83 | if (address == QHostAddress::Any) |
| 84 | return preferForAny; |
| 85 | if (address == QHostAddress::AnyIPv4) |
| 86 | return QHostAddress::LocalHost; |
| 87 | if (address == QHostAddress::AnyIPv6) |
| 88 | return QHostAddress::LocalHostIPv6; |
| 89 | return address; |
| 90 | } |
| 91 | |
| 92 | class UnexpectedDisconnectTester : public QObject |
| 93 | { |
| 94 | Q_OBJECT |
| 95 | public: |
| 96 | NATIVESOCKETENGINE *readEnd1, *readEnd2; |
| 97 | int sequence; |
| 98 | |
| 99 | UnexpectedDisconnectTester(NATIVESOCKETENGINE *s1, NATIVESOCKETENGINE *s2) |
| 100 | : readEnd1(s1), readEnd2(s2), sequence(0) |
| 101 | { |
| 102 | QSocketNotifier *notifier1 = |
| 103 | new QSocketNotifier(readEnd1->socketDescriptor(), QSocketNotifier::Read, this); |
| 104 | connect(asender: notifier1, SIGNAL(activated(QSocketDescriptor)), SLOT(handleActivated())); |
| 105 | QSocketNotifier *notifier2 = |
| 106 | new QSocketNotifier(readEnd2->socketDescriptor(), QSocketNotifier::Read, this); |
| 107 | connect(asender: notifier2, SIGNAL(activated(QSocketDescriptor)), SLOT(handleActivated())); |
| 108 | } |
| 109 | |
| 110 | public slots: |
| 111 | void handleActivated() |
| 112 | { |
| 113 | char data1[1], data2[1]; |
| 114 | ++sequence; |
| 115 | if (sequence == 1) { |
| 116 | // read from both ends |
| 117 | (void) readEnd1->read(data: data1, maxlen: sizeof(data1)); |
| 118 | (void) readEnd2->read(data: data2, maxlen: sizeof(data2)); |
| 119 | emit finished(); |
| 120 | } else if (sequence == 2) { |
| 121 | // we should never get here |
| 122 | QCOMPARE(readEnd2->read(data2, sizeof(data2)), qint64(-2)); |
| 123 | QVERIFY(readEnd2->isValid()); |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | signals: |
| 128 | void finished(); |
| 129 | }; |
| 130 | |
| 131 | void tst_QSocketNotifier::unexpectedDisconnection() |
| 132 | { |
| 133 | #ifdef Q_OS_WINRT |
| 134 | // WinRT does not allow a connection to the localhost |
| 135 | QSKIP("Local connection not allowed" , SkipAll); |
| 136 | #else |
| 137 | /* |
| 138 | Given two sockets and two QSocketNotifiers registered on each |
| 139 | their socket. If both sockets receive data, and the first slot |
| 140 | invoked by one of the socket notifiers empties both sockets, the |
| 141 | other notifier will also emit activated(). This results in |
| 142 | unexpected disconnection in QAbstractSocket. |
| 143 | |
| 144 | The use case is that somebody calls one of the |
| 145 | waitFor... functions in a QSocketNotifier activated slot, and |
| 146 | the waitFor... functions do local selects that can empty both |
| 147 | stdin and stderr while waiting for fex bytes to be written. |
| 148 | */ |
| 149 | |
| 150 | QTcpServer server; |
| 151 | QVERIFY(server.listen(QHostAddress::LocalHost, 0)); |
| 152 | |
| 153 | NATIVESOCKETENGINE readEnd1; |
| 154 | readEnd1.initialize(type: QAbstractSocket::TcpSocket); |
| 155 | readEnd1.connectToHost(address: server.serverAddress(), port: server.serverPort()); |
| 156 | QVERIFY(readEnd1.waitForWrite()); |
| 157 | QCOMPARE(readEnd1.state(), QAbstractSocket::ConnectedState); |
| 158 | QVERIFY(server.waitForNewConnection()); |
| 159 | QTcpSocket *writeEnd1 = server.nextPendingConnection(); |
| 160 | QVERIFY(writeEnd1 != 0); |
| 161 | |
| 162 | NATIVESOCKETENGINE readEnd2; |
| 163 | readEnd2.initialize(type: QAbstractSocket::TcpSocket); |
| 164 | readEnd2.connectToHost(address: server.serverAddress(), port: server.serverPort()); |
| 165 | QVERIFY(readEnd2.waitForWrite()); |
| 166 | QCOMPARE(readEnd2.state(), QAbstractSocket::ConnectedState); |
| 167 | QVERIFY(server.waitForNewConnection()); |
| 168 | QTcpSocket *writeEnd2 = server.nextPendingConnection(); |
| 169 | QVERIFY(writeEnd2 != 0); |
| 170 | |
| 171 | writeEnd1->write(data: "1" , len: 1); |
| 172 | writeEnd2->write(data: "2" , len: 1); |
| 173 | |
| 174 | writeEnd1->waitForBytesWritten(); |
| 175 | writeEnd2->waitForBytesWritten(); |
| 176 | |
| 177 | writeEnd1->flush(); |
| 178 | writeEnd2->flush(); |
| 179 | |
| 180 | UnexpectedDisconnectTester tester(&readEnd1, &readEnd2); |
| 181 | |
| 182 | QTimer timer; |
| 183 | timer.setSingleShot(true); |
| 184 | timer.start(msec: 30000); |
| 185 | do { |
| 186 | // we have to wait until sequence value changes |
| 187 | // as any event can make us jump out processing |
| 188 | QCoreApplication::processEvents(flags: QEventLoop::WaitForMoreEvents); |
| 189 | QVERIFY(timer.isActive()); //escape if test would hang |
| 190 | } while(tester.sequence <= 0); |
| 191 | |
| 192 | QCOMPARE(readEnd1.state(), QAbstractSocket::ConnectedState); |
| 193 | QCOMPARE(readEnd2.state(), QAbstractSocket::ConnectedState); |
| 194 | |
| 195 | QCOMPARE(tester.sequence, 2); |
| 196 | |
| 197 | readEnd1.close(); |
| 198 | readEnd2.close(); |
| 199 | writeEnd1->close(); |
| 200 | writeEnd2->close(); |
| 201 | server.close(); |
| 202 | #endif // !Q_OS_WINRT |
| 203 | } |
| 204 | |
| 205 | class MixingWithTimersHelper : public QObject |
| 206 | { |
| 207 | Q_OBJECT |
| 208 | |
| 209 | public: |
| 210 | MixingWithTimersHelper(QTimer *timer, QTcpServer *server); |
| 211 | |
| 212 | bool timerActivated; |
| 213 | bool socketActivated; |
| 214 | |
| 215 | private slots: |
| 216 | void timerFired(); |
| 217 | void socketFired(); |
| 218 | }; |
| 219 | |
| 220 | MixingWithTimersHelper::MixingWithTimersHelper(QTimer *timer, QTcpServer *server) |
| 221 | { |
| 222 | timerActivated = false; |
| 223 | socketActivated = false; |
| 224 | |
| 225 | connect(asender: timer, SIGNAL(timeout()), SLOT(timerFired())); |
| 226 | connect(asender: server, SIGNAL(newConnection()), SLOT(socketFired())); |
| 227 | } |
| 228 | |
| 229 | void MixingWithTimersHelper::timerFired() |
| 230 | { |
| 231 | timerActivated = true; |
| 232 | } |
| 233 | |
| 234 | void MixingWithTimersHelper::socketFired() |
| 235 | { |
| 236 | socketActivated = true; |
| 237 | } |
| 238 | |
| 239 | void tst_QSocketNotifier::mixingWithTimers() |
| 240 | { |
| 241 | #ifdef Q_OS_WINRT |
| 242 | QSKIP("WinRT does not allow connection to localhost" , SkipAll); |
| 243 | #else |
| 244 | QTimer timer; |
| 245 | timer.setInterval(0); |
| 246 | timer.start(); |
| 247 | |
| 248 | QTcpServer server; |
| 249 | QVERIFY(server.listen(QHostAddress::LocalHost, 0)); |
| 250 | |
| 251 | MixingWithTimersHelper helper(&timer, &server); |
| 252 | |
| 253 | QCoreApplication::processEvents(); |
| 254 | |
| 255 | QCOMPARE(helper.timerActivated, true); |
| 256 | QCOMPARE(helper.socketActivated, false); |
| 257 | |
| 258 | helper.timerActivated = false; |
| 259 | helper.socketActivated = false; |
| 260 | |
| 261 | QTcpSocket socket; |
| 262 | socket.connectToHost(address: server.serverAddress(), port: server.serverPort()); |
| 263 | |
| 264 | QCoreApplication::processEvents(); |
| 265 | |
| 266 | QCOMPARE(helper.timerActivated, true); |
| 267 | QTRY_COMPARE(helper.socketActivated, true); |
| 268 | #endif // !Q_OS_WINRT |
| 269 | } |
| 270 | |
| 271 | #ifdef Q_OS_UNIX |
| 272 | // test only for posix |
| 273 | void tst_QSocketNotifier::posixSockets() |
| 274 | { |
| 275 | QTcpServer server; |
| 276 | QVERIFY(server.listen(QHostAddress::LocalHost, 0)); |
| 277 | |
| 278 | int posixSocket = qt_safe_socket(AF_INET, SOCK_STREAM, protocol: 0); |
| 279 | sockaddr_in addr; |
| 280 | addr.sin_addr.s_addr = htonl(hostlong: 0x7f000001); |
| 281 | addr.sin_family = AF_INET; |
| 282 | addr.sin_port = htons(hostshort: server.serverPort()); |
| 283 | qt_safe_connect(sockfd: posixSocket, addr: (const struct sockaddr*)&addr, addrlen: sizeof(sockaddr_in)); |
| 284 | QVERIFY(server.waitForNewConnection(5000)); |
| 285 | QScopedPointer<QTcpSocket> passive(server.nextPendingConnection()); |
| 286 | |
| 287 | ::fcntl(fd: posixSocket, F_SETFL, ::fcntl(fd: posixSocket, F_GETFL) | O_NONBLOCK); |
| 288 | |
| 289 | { |
| 290 | QSocketNotifier rn(posixSocket, QSocketNotifier::Read); |
| 291 | connect(sender: &rn, SIGNAL(activated(QSocketDescriptor)), receiver: &QTestEventLoop::instance(), SLOT(exitLoop())); |
| 292 | QSignalSpy readSpy(&rn, &QSocketNotifier::activated); |
| 293 | QVERIFY(readSpy.isValid()); |
| 294 | // No write notifier, some systems trigger write notification on socket creation, but not all |
| 295 | QSocketNotifier en(posixSocket, QSocketNotifier::Exception); |
| 296 | connect(sender: &en, SIGNAL(activated(QSocketDescriptor)), receiver: &QTestEventLoop::instance(), SLOT(exitLoop())); |
| 297 | QSignalSpy errorSpy(&en, &QSocketNotifier::activated); |
| 298 | QVERIFY(errorSpy.isValid()); |
| 299 | |
| 300 | passive->write(data: "hello" ,len: 6); |
| 301 | passive->waitForBytesWritten(msecs: 5000); |
| 302 | |
| 303 | QTestEventLoop::instance().enterLoop(secs: 3); |
| 304 | QCOMPARE(readSpy.count(), 1); |
| 305 | QCOMPARE(errorSpy.count(), 0); |
| 306 | |
| 307 | char buffer[100]; |
| 308 | int r = qt_safe_read(fd: posixSocket, data: buffer, maxlen: 100); |
| 309 | QCOMPARE(r, 6); |
| 310 | QCOMPARE(buffer, "hello" ); |
| 311 | |
| 312 | QSocketNotifier wn(posixSocket, QSocketNotifier::Write); |
| 313 | connect(sender: &wn, SIGNAL(activated(QSocketDescriptor)), receiver: &QTestEventLoop::instance(), SLOT(exitLoop())); |
| 314 | QSignalSpy writeSpy(&wn, &QSocketNotifier::activated); |
| 315 | QVERIFY(writeSpy.isValid()); |
| 316 | qt_safe_write(fd: posixSocket, data: "goodbye" , len: 8); |
| 317 | |
| 318 | QTestEventLoop::instance().enterLoop(secs: 3); |
| 319 | QCOMPARE(readSpy.count(), 1); |
| 320 | QCOMPARE(writeSpy.count(), 1); |
| 321 | QCOMPARE(errorSpy.count(), 0); |
| 322 | |
| 323 | // Write notifier may have fired before the read notifier inside |
| 324 | // QTcpSocket, give QTcpSocket a chance to see the incoming data |
| 325 | passive->waitForReadyRead(msecs: 100); |
| 326 | QCOMPARE(passive->readAll(), QByteArray("goodbye" ,8)); |
| 327 | } |
| 328 | qt_safe_close(fd: posixSocket); |
| 329 | } |
| 330 | #endif |
| 331 | |
| 332 | void tst_QSocketNotifier::async_readDatagramSlot() |
| 333 | { |
| 334 | char buf[1]; |
| 335 | QVERIFY(m_asyncReceiver->hasPendingDatagrams()); |
| 336 | do { |
| 337 | QCOMPARE(m_asyncReceiver->pendingDatagramSize(), qint64(1)); |
| 338 | QCOMPARE(m_asyncReceiver->readDatagram(buf, sizeof(buf)), qint64(1)); |
| 339 | if (buf[0] == '1') { |
| 340 | // wait for the second datagram message. |
| 341 | QTest::qSleep(ms: 100); |
| 342 | } |
| 343 | } while (m_asyncReceiver->hasPendingDatagrams()); |
| 344 | |
| 345 | if (buf[0] == '3') |
| 346 | QTestEventLoop::instance().exitLoop(); |
| 347 | } |
| 348 | |
| 349 | void tst_QSocketNotifier::async_writeDatagramSlot() |
| 350 | { |
| 351 | m_asyncSender->writeDatagram(datagram: "3" , host: makeNonAny(address: m_asyncReceiver->localAddress()), |
| 352 | port: m_asyncReceiver->localPort()); |
| 353 | } |
| 354 | |
| 355 | void tst_QSocketNotifier::asyncMultipleDatagram() |
| 356 | { |
| 357 | #ifdef Q_OS_WINRT |
| 358 | QSKIP("WinRT does not allow connection to localhost" , SkipAll); |
| 359 | #else |
| 360 | m_asyncSender = new QUdpSocket; |
| 361 | m_asyncReceiver = new QUdpSocket; |
| 362 | |
| 363 | QVERIFY(m_asyncReceiver->bind(QHostAddress(QHostAddress::AnyIPv4), 0)); |
| 364 | quint16 port = m_asyncReceiver->localPort(); |
| 365 | QVERIFY(port != 0); |
| 366 | |
| 367 | QSignalSpy spy(m_asyncReceiver, &QIODevice::readyRead); |
| 368 | connect(sender: m_asyncReceiver, signal: &QIODevice::readyRead, receiver: this, |
| 369 | slot: &tst_QSocketNotifier::async_readDatagramSlot); |
| 370 | |
| 371 | // activate socket notifiers |
| 372 | QTestEventLoop::instance().enterLoopMSecs(ms: 100); |
| 373 | |
| 374 | m_asyncSender->writeDatagram(datagram: "1" , host: makeNonAny(address: m_asyncReceiver->localAddress()), port); |
| 375 | m_asyncSender->writeDatagram(datagram: "2" , host: makeNonAny(address: m_asyncReceiver->localAddress()), port); |
| 376 | // wait a little to ensure that the datagrams we've just sent |
| 377 | // will be delivered on receiver side. |
| 378 | QTest::qSleep(ms: 100); |
| 379 | QVERIFY(m_asyncReceiver->hasPendingDatagrams()); |
| 380 | |
| 381 | QTimer::singleShot(interval: 500, receiver: this, slot: &tst_QSocketNotifier::async_writeDatagramSlot); |
| 382 | |
| 383 | QTestEventLoop::instance().enterLoop(secs: 1); |
| 384 | QVERIFY(!QTestEventLoop::instance().timeout()); |
| 385 | QCOMPARE(spy.count(), 2); |
| 386 | |
| 387 | delete m_asyncSender; |
| 388 | delete m_asyncReceiver; |
| 389 | #endif // !Q_OS_WINRT |
| 390 | } |
| 391 | |
| 392 | void tst_QSocketNotifier::activationReason_data() |
| 393 | { |
| 394 | QTest::addColumn<QSocketNotifier::Type>(name: "type" ); |
| 395 | QTest::addRow(format: "read" ) << QSocketNotifier::Read; |
| 396 | QTest::addRow(format: "write" ) << QSocketNotifier::Write; |
| 397 | QTest::addRow(format: "exception" ) << QSocketNotifier::Exception; |
| 398 | } |
| 399 | void tst_QSocketNotifier::activationReason() |
| 400 | { |
| 401 | QSocketDescriptor fd = 15; |
| 402 | |
| 403 | QFETCH(QSocketNotifier::Type, type); |
| 404 | |
| 405 | QSocketNotifier notifier(fd, type); |
| 406 | auto activation = new QEvent(QEvent::SockAct); |
| 407 | QCoreApplication::postEvent(receiver: ¬ifier, event: activation); |
| 408 | |
| 409 | QSocketNotifier::Type notifierType; |
| 410 | connect(sender: ¬ifier, signal: &QSocketNotifier::activated, context: this, |
| 411 | slot: [¬ifierType, fd](QSocketDescriptor sockfd, QSocketNotifier::Type sntype) { |
| 412 | if (sockfd == fd) |
| 413 | notifierType = sntype; |
| 414 | else |
| 415 | qWarning() << "Got an unexpected socket file descriptor:" << qintptr(sockfd); |
| 416 | }); |
| 417 | |
| 418 | QCoreApplication::processEvents(); |
| 419 | QCOMPARE(notifierType, type); |
| 420 | } |
| 421 | |
| 422 | // This test ensures that we can connect QSocketNotifier::activated to a slot taking an integer |
| 423 | // or qintptr. |
| 424 | void tst_QSocketNotifier::legacyConnect() |
| 425 | { |
| 426 | qintptr fd = 15; |
| 427 | QSocketNotifier notifier(fd, QSocketNotifier::Read); |
| 428 | auto activation = new QEvent(QEvent::SockAct); |
| 429 | QCoreApplication::postEvent(receiver: ¬ifier, event: activation); |
| 430 | |
| 431 | bool receivedQIntPtr = false; |
| 432 | connect(sender: ¬ifier, signal: &QSocketNotifier::activated, context: this, slot: [&receivedQIntPtr, fd](qintptr q){ |
| 433 | if (q == fd) |
| 434 | receivedQIntPtr = true; |
| 435 | }); |
| 436 | bool receivedInt = false; |
| 437 | connect(sender: ¬ifier, signal: &QSocketNotifier::activated, context: this, slot: [&receivedInt, fd](int q){ |
| 438 | if (q == fd) |
| 439 | receivedInt = true; |
| 440 | }); |
| 441 | |
| 442 | QCoreApplication::processEvents(); |
| 443 | QVERIFY(receivedQIntPtr); |
| 444 | QVERIFY(receivedInt); |
| 445 | } |
| 446 | |
| 447 | |
| 448 | QTEST_MAIN(tst_QSocketNotifier) |
| 449 | #include <tst_qsocketnotifier.moc> |
| 450 | |