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