diff options
Diffstat (limited to 'tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp')
-rw-r--r-- | tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp | 1356 |
1 files changed, 1356 insertions, 0 deletions
diff --git a/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp new file mode 100644 index 0000000000..83d30cc40a --- /dev/null +++ b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp @@ -0,0 +1,1356 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <qcoreapplication.h> +#include <qfileinfo.h> +#include <qdatastream.h> +#include <qudpsocket.h> +#include <qhostaddress.h> +#include <qhostinfo.h> +#include <qmap.h> +#include <QNetworkProxy> +#include <QNetworkInterface> + +#include <qstringlist.h> +#include "../../../network-settings.h" + +#ifndef QT_NO_BEARERMANAGEMENT +#include <QtNetwork/qnetworkconfigmanager.h> +#include <QtNetwork/qnetworkconfiguration.h> +#include <QtNetwork/qnetworksession.h> +#endif + +Q_DECLARE_METATYPE(QHostAddress) +Q_DECLARE_METATYPE(QNetworkInterface) +Q_DECLARE_METATYPE(QSharedPointer<QNetworkSession>) +//TESTED_CLASS= +//TESTED_FILES= + +QT_FORWARD_DECLARE_CLASS(QUdpSocket) + +class tst_QUdpSocket : public QObject +{ + Q_OBJECT + +public: + tst_QUdpSocket(); + virtual ~tst_QUdpSocket(); + + +public slots: + void initTestCase_data(); + void init(); + void cleanup(); +private slots: + void constructing(); + void unconnectedServerAndClientTest(); + void broadcasting(); + void loop_data(); + void loop(); + void ipv6Loop_data(); + void ipv6Loop(); + void dualStack(); + void readLine(); + void pendingDatagramSize(); + void writeDatagram(); + void performance(); + void bindMode(); + void writeDatagramToNonExistingPeer_data(); + void writeDatagramToNonExistingPeer(); + void writeToNonExistingPeer_data(); + void writeToNonExistingPeer(); + void outOfProcessConnectedClientServerTest(); + void outOfProcessUnconnectedClientServerTest(); + void zeroLengthDatagram(); + void multicastTtlOption_data(); + void multicastTtlOption(); + void multicastLoopbackOption_data(); + void multicastLoopbackOption(); + void multicastJoinBeforeBind_data(); + void multicastJoinBeforeBind(); + void multicastLeaveAfterClose_data(); + void multicastLeaveAfterClose(); + void setMulticastInterface_data(); + void setMulticastInterface(); + void multicast_data(); + void multicast(); + void echo_data(); + void echo(); + +protected slots: + void empty_readyReadSlot(); + void empty_connectedSlot(); + +private: +#ifndef QT_NO_BEARERMANAGEMENT + QNetworkConfigurationManager *netConfMan; + QNetworkConfiguration networkConfiguration; + QSharedPointer<QNetworkSession> networkSession; +#endif +}; + +tst_QUdpSocket::tst_QUdpSocket() +{ + Q_SET_DEFAULT_IAP +} + +tst_QUdpSocket::~tst_QUdpSocket() +{ +} + +void tst_QUdpSocket::initTestCase_data() +{ + QTest::addColumn<bool>("setProxy"); + QTest::addColumn<int>("proxyType"); + + QTest::newRow("WithoutProxy") << false << 0; + QTest::newRow("WithSocks5Proxy") << true << int(QNetworkProxy::Socks5Proxy); + +#ifndef QT_NO_BEARERMANAGEMENT + netConfMan = new QNetworkConfigurationManager(this); + networkConfiguration = netConfMan->defaultConfiguration(); + networkSession = QSharedPointer<QNetworkSession>(new QNetworkSession(networkConfiguration)); + if (!networkSession->isOpen()) { + networkSession->open(); + QVERIFY(networkSession->waitForOpened(30000)); + } +#endif +} + +void tst_QUdpSocket::init() +{ + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) { + QFETCH_GLOBAL(int, proxyType); + if (proxyType == QNetworkProxy::Socks5Proxy) { + QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::serverName(), 1080)); + } + } +} + +void tst_QUdpSocket::cleanup() +{ + QNetworkProxy::setApplicationProxy(QNetworkProxy::DefaultProxy); +} + + +//---------------------------------------------------------------------------------- + +void tst_QUdpSocket::constructing() +{ + QUdpSocket socket; +#ifdef FORCE_SESSION + socket.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + + QVERIFY(socket.isSequential()); + QVERIFY(!socket.isOpen()); + QVERIFY(socket.socketType() == QUdpSocket::UdpSocket); + QCOMPARE((int) socket.bytesAvailable(), 0); + QCOMPARE(socket.canReadLine(), false); + QCOMPARE(socket.readLine(), QByteArray()); + QCOMPARE(socket.socketDescriptor(), -1); + QCOMPARE(socket.error(), QUdpSocket::UnknownSocketError); + QCOMPARE(socket.errorString(), QString("Unknown error")); + + // Check the state of the socket api +} + +void tst_QUdpSocket::unconnectedServerAndClientTest() +{ + QUdpSocket serverSocket; +#ifdef FORCE_SESSION + serverSocket.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + + qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState"); + + QSignalSpy stateChangedSpy(&serverSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState))); + QVERIFY2(serverSocket.bind(), serverSocket.errorString().toLatin1().constData()); + QCOMPARE(stateChangedSpy.count(), 1); + + const char *message[] = {"Yo mista", "Yo", "Wassap"}; + + QHostAddress serverAddress = QHostAddress::LocalHost; + if (!(serverSocket.localAddress() == QHostAddress::AnyIPv4 || serverSocket.localAddress() == QHostAddress::AnyIPv6)) + serverAddress = serverSocket.localAddress(); + + for (int i = 0; i < 3; ++i) { + QUdpSocket clientSocket; +#ifdef FORCE_SESSION + clientSocket.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QCOMPARE(int(clientSocket.writeDatagram(message[i], strlen(message[i]), + serverAddress, serverSocket.localPort())), + int(strlen(message[i]))); + char buf[1024]; + QHostAddress host; + quint16 port; + QVERIFY(serverSocket.waitForReadyRead(5000)); + QCOMPARE(int(serverSocket.readDatagram(buf, sizeof(buf), &host, &port)), + int(strlen(message[i]))); + buf[strlen(message[i])] = '\0'; + QCOMPARE(QByteArray(buf), QByteArray(message[i])); + } +} + +//---------------------------------------------------------------------------------- + +void tst_QUdpSocket::broadcasting() +{ + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) { + QFETCH_GLOBAL(int, proxyType); + if (proxyType == QNetworkProxy::Socks5Proxy) { + QSKIP("With socks5 Broadcast is not supported.", SkipSingle); + } + } +#ifdef Q_OS_AIX + QSKIP("Broadcast does not work on darko", SkipAll); +#endif + const char *message[] = {"Yo mista", "", "Yo", "Wassap"}; + + QList<QHostAddress> broadcastAddresses; + foreach (QNetworkInterface iface, QNetworkInterface::allInterfaces()) { + if ((iface.flags() & QNetworkInterface::CanBroadcast) + && iface.flags() & QNetworkInterface::IsUp) { + for (int i=0;i<iface.addressEntries().count();i++) + broadcastAddresses.append(iface.addressEntries().at(i).broadcast()); + } + } + if (broadcastAddresses.isEmpty()) + QSKIP("No interface can broadcast", SkipAll); + for (int i = 0; i < 4; ++i) { + QUdpSocket serverSocket; +#ifdef FORCE_SESSION + serverSocket.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QVERIFY2(serverSocket.bind(QHostAddress::Any, 5000), serverSocket.errorString().toLatin1().constData()); + + QCOMPARE(serverSocket.state(), QUdpSocket::BoundState); + + connect(&serverSocket, SIGNAL(readyRead()), SLOT(empty_readyReadSlot())); + + QUdpSocket broadcastSocket; +#ifdef FORCE_SESSION + broadcastSocket.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + broadcastSocket.bind(); + + for (int j = 0; j < 100; ++j) { + for (int k = 0; k < 4; k++) { + broadcastSocket.writeDatagram(message[i], strlen(message[i]), + QHostAddress::Broadcast, 5000); + foreach (QHostAddress addr, broadcastAddresses) + broadcastSocket.writeDatagram(message[i], strlen(message[i]), addr, 5000); + } + QTestEventLoop::instance().enterLoop(15); + if (QTestEventLoop::instance().timeout()) { +#if defined(Q_OS_FREEBSD) + QEXPECT_FAIL("", + "Broadcasting to 255.255.255.255 does not work on FreeBSD", + Abort); + QVERIFY(false); // seems that QFAIL() doesn't respect the QEXPECT_FAIL() :/ +#endif + QFAIL("Network operation timed out"); + } + QVERIFY(serverSocket.hasPendingDatagrams()); + + do { + QByteArray arr; arr.resize(serverSocket.pendingDatagramSize() + 1); + QHostAddress host; + quint16 port; + QCOMPARE((int) serverSocket.readDatagram(arr.data(), arr.size() - 1, &host, &port), + (int) strlen(message[i])); + arr.resize(strlen(message[i])); + QCOMPARE(arr, QByteArray(message[i])); + } while (serverSocket.hasPendingDatagrams()); + } + } +} + +//---------------------------------------------------------------------------------- + +void tst_QUdpSocket::loop_data() +{ + QTest::addColumn<QByteArray>("peterMessage"); + QTest::addColumn<QByteArray>("paulMessage"); + QTest::addColumn<bool>("success"); + + QTest::newRow("\"Almond!\" | \"Joy!\"") << QByteArray("Almond!") << QByteArray("Joy!") << true; + QTest::newRow("\"A\" | \"B\"") << QByteArray("A") << QByteArray("B") << true; + QTest::newRow("\"AB\" | \"B\"") << QByteArray("AB") << QByteArray("B") << true; + QTest::newRow("\"AB\" | \"BB\"") << QByteArray("AB") << QByteArray("BB") << true; + QTest::newRow("\"A\\0B\" | \"B\\0B\"") << QByteArray::fromRawData("A\0B", 3) << QByteArray::fromRawData("B\0B", 3) << true; + QTest::newRow("\"(nil)\" | \"(nil)\"") << QByteArray() << QByteArray() << true; + QTest::newRow("Bigmessage") << QByteArray(600, '@') << QByteArray(600, '@') << true; +} + +void tst_QUdpSocket::loop() +{ + QFETCH(QByteArray, peterMessage); + QFETCH(QByteArray, paulMessage); + QFETCH(bool, success); + + QUdpSocket peter; + QUdpSocket paul; +#ifdef FORCE_SESSION + peter.setProperty("_q_networksession", QVariant::fromValue(networkSession)); + paul.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + + QVERIFY2(peter.bind(), peter.errorString().toLatin1().constData()); + QVERIFY2(paul.bind(), paul.errorString().toLatin1().constData()); + + QHostAddress peterAddress = QHostAddress::LocalHost; + if (!(peter.localAddress() == QHostAddress::AnyIPv4 || peter.localAddress() == QHostAddress::AnyIPv6)) + peterAddress = peter.localAddress(); + QHostAddress pualAddress = QHostAddress::LocalHost; + if (!(paul.localAddress() == QHostAddress::AnyIPv4 || paul.localAddress() == QHostAddress::AnyIPv6)) + pualAddress = paul.localAddress(); + + QCOMPARE(peter.writeDatagram(peterMessage.data(), peterMessage.length(), + pualAddress, paul.localPort()), qint64(peterMessage.length())); + QCOMPARE(paul.writeDatagram(paulMessage.data(), paulMessage.length(), + peterAddress, peter.localPort()), qint64(paulMessage.length())); + + QVERIFY(peter.waitForReadyRead(9000)); + QVERIFY(paul.waitForReadyRead(9000)); + char peterBuffer[16*1024]; + char paulBuffer[16*1024]; + if (success) { + QCOMPARE(peter.readDatagram(peterBuffer, sizeof(peterBuffer)), qint64(paulMessage.length())); + QCOMPARE(paul.readDatagram(paulBuffer, sizeof(peterBuffer)), qint64(peterMessage.length())); + } else { + QVERIFY(peter.readDatagram(peterBuffer, sizeof(peterBuffer)) != paulMessage.length()); + QVERIFY(paul.readDatagram(paulBuffer, sizeof(peterBuffer)) != peterMessage.length()); + } + + QCOMPARE(QByteArray(peterBuffer, paulMessage.length()), paulMessage); + QCOMPARE(QByteArray(paulBuffer, peterMessage.length()), peterMessage); +} + +//---------------------------------------------------------------------------------- + +void tst_QUdpSocket::ipv6Loop_data() +{ + loop_data(); +} + +void tst_QUdpSocket::ipv6Loop() +{ + QFETCH(QByteArray, peterMessage); + QFETCH(QByteArray, paulMessage); + QFETCH(bool, success); + + QUdpSocket peter; + QUdpSocket paul; +#ifdef FORCE_SESSION + peter.setProperty("_q_networksession", QVariant::fromValue(networkSession)); + paul.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + + quint16 peterPort = 28124; + quint16 paulPort = 28123; + + if (!peter.bind(QHostAddress::LocalHostIPv6, peterPort)) { + QCOMPARE(peter.error(), QUdpSocket::UnsupportedSocketOperationError); + } else { + QVERIFY(paul.bind(QHostAddress::LocalHostIPv6, paulPort)); + + QCOMPARE(peter.writeDatagram(peterMessage.data(), peterMessage.length(), QHostAddress("::1"), + paulPort), qint64(peterMessage.length())); + QCOMPARE(paul.writeDatagram(paulMessage.data(), paulMessage.length(), + QHostAddress("::1"), peterPort), qint64(paulMessage.length())); + + char peterBuffer[16*1024]; + char paulBuffer[16*1024]; +#if !defined(Q_OS_WINCE) + QVERIFY(peter.waitForReadyRead(5000)); + QVERIFY(paul.waitForReadyRead(5000)); +#else + QVERIFY(peter.waitForReadyRead(15000)); + QVERIFY(paul.waitForReadyRead(15000)); +#endif + if (success) { + QCOMPARE(peter.readDatagram(peterBuffer, sizeof(peterBuffer)), qint64(paulMessage.length())); + QCOMPARE(paul.readDatagram(paulBuffer, sizeof(peterBuffer)), qint64(peterMessage.length())); + } else { + QVERIFY(peter.readDatagram(peterBuffer, sizeof(peterBuffer)) != paulMessage.length()); + QVERIFY(paul.readDatagram(paulBuffer, sizeof(peterBuffer)) != peterMessage.length()); + } + + QCOMPARE(QByteArray(peterBuffer, paulMessage.length()), paulMessage); + QCOMPARE(QByteArray(paulBuffer, peterMessage.length()), peterMessage); + } +} + +void tst_QUdpSocket::dualStack() +{ + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) + QSKIP("test server SOCKS proxy doesn't support IPv6", SkipSingle); + QUdpSocket dualSock; + QByteArray dualData("dual"); + QVERIFY(dualSock.bind(QHostAddress(QHostAddress::Any), 0)); + + QUdpSocket v4Sock; + QByteArray v4Data("v4"); + QVERIFY(v4Sock.bind(QHostAddress(QHostAddress::AnyIPv4), 0)); + + QUdpSocket v6Sock; + QByteArray v6Data("v6"); + QVERIFY(v6Sock.bind(QHostAddress(QHostAddress::AnyIPv6), 0)); + + QHostAddress from; + quint16 port; + QByteArray buffer; + //test v4 -> dual + QCOMPARE((int)v4Sock.writeDatagram(v4Data.constData(), v4Data.length(), QHostAddress(QHostAddress::LocalHost), dualSock.localPort()), v4Data.length()); + QVERIFY(dualSock.waitForReadyRead(5000)); + buffer.reserve(100); + qint64 size = dualSock.readDatagram(buffer.data(), 100, &from, &port); + QCOMPARE((int)size, v4Data.length()); + buffer.resize(size); + QCOMPARE(buffer, v4Data); + + //test v6 -> dual + QCOMPARE((int)v6Sock.writeDatagram(v6Data.constData(), v6Data.length(), QHostAddress(QHostAddress::LocalHostIPv6), dualSock.localPort()), v6Data.length()); + QVERIFY(dualSock.waitForReadyRead(5000)); + buffer.reserve(100); + size = dualSock.readDatagram(buffer.data(), 100, &from, &port); + QCOMPARE((int)size, v6Data.length()); + buffer.resize(size); + QCOMPARE(buffer, v6Data); + + //test dual -> v4 + QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.length(), QHostAddress(QHostAddress::LocalHost), v4Sock.localPort()), dualData.length()); + QVERIFY(v4Sock.waitForReadyRead(5000)); + buffer.reserve(100); + size = v4Sock.readDatagram(buffer.data(), 100, &from, &port); + QCOMPARE((int)size, dualData.length()); + buffer.resize(size); + QCOMPARE(buffer, dualData); + + //test dual -> v6 + QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.length(), QHostAddress(QHostAddress::LocalHostIPv6), v6Sock.localPort()), dualData.length()); + QVERIFY(v6Sock.waitForReadyRead(5000)); + buffer.reserve(100); + size = v6Sock.readDatagram(buffer.data(), 100, &from, &port); + QCOMPARE((int)size, dualData.length()); + buffer.resize(size); + QCOMPARE(buffer, dualData); + +} + +void tst_QUdpSocket::empty_readyReadSlot() +{ + QTestEventLoop::instance().exitLoop(); +} + +void tst_QUdpSocket::empty_connectedSlot() +{ + QTestEventLoop::instance().exitLoop(); +} + +//---------------------------------------------------------------------------------- + +void tst_QUdpSocket::readLine() +{ + QUdpSocket socket1; + QUdpSocket socket2; +#ifdef FORCE_SESSION + socket1.setProperty("_q_networksession", QVariant::fromValue(networkSession)); + socket2.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QVERIFY2(socket1.bind(), socket1.errorString().toLatin1().constData()); + + socket2.connectToHost("127.0.0.1", socket1.localPort()); + QVERIFY(socket2.waitForConnected(5000)); +} + +//---------------------------------------------------------------------------------- + +void tst_QUdpSocket::pendingDatagramSize() +{ + QUdpSocket server; +#ifdef FORCE_SESSION + server.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QVERIFY2(server.bind(), server.errorString().toLatin1().constData()); + + QHostAddress serverAddress = QHostAddress::LocalHost; + if (!(server.localAddress() == QHostAddress::AnyIPv4 || server.localAddress() == QHostAddress::AnyIPv6)) + serverAddress = server.localAddress(); + + QUdpSocket client; +#ifdef FORCE_SESSION + client.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QVERIFY(client.writeDatagram("this is", 7, serverAddress, server.localPort()) == 7); + QVERIFY(client.writeDatagram(0, 0, serverAddress, server.localPort()) == 0); + QVERIFY(client.writeDatagram("3 messages", 10, serverAddress, server.localPort()) == 10); + + char c = 0; + QVERIFY(server.waitForReadyRead()); + if (server.hasPendingDatagrams()) { +#if defined Q_OS_HPUX && defined __ia64 + QEXPECT_FAIL("", "HP-UX 11i v2 can't determine the datagram size correctly.", Abort); +#endif + QCOMPARE(server.pendingDatagramSize(), qint64(7)); + c = '\0'; + QCOMPARE(server.readDatagram(&c, 1), qint64(1)); + QCOMPARE(c, 't'); + c = '\0'; + } else { + QSKIP("does not have the 1st datagram", SkipSingle); + } + + if (server.hasPendingDatagrams()) { + QCOMPARE(server.pendingDatagramSize(), qint64(0)); + QCOMPARE(server.readDatagram(&c, 1), qint64(0)); + QCOMPARE(c, '\0'); // untouched + c = '\0'; + } else { + QSKIP("does not have the 2nd datagram", SkipSingle); + } + + if (server.hasPendingDatagrams()) { + QCOMPARE(server.pendingDatagramSize(), qint64(10)); + QCOMPARE(server.readDatagram(&c, 1), qint64(1)); + QCOMPARE(c, '3'); + } else { + QSKIP("does not have the 3rd datagram", SkipSingle); + } +} + + +void tst_QUdpSocket::writeDatagram() +{ + QUdpSocket server; +#ifdef FORCE_SESSION + server.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QVERIFY2(server.bind(), server.errorString().toLatin1().constData()); + + QHostAddress serverAddress = QHostAddress::LocalHost; + if (!(server.localAddress() == QHostAddress::AnyIPv4 || server.localAddress() == QHostAddress::AnyIPv6)) + serverAddress = server.localAddress(); + + QUdpSocket client; +#ifdef FORCE_SESSION + client.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + + qRegisterMetaType<qint64>("qint64"); + qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError"); + + for(int i=0;;i++) { + QSignalSpy errorspy(&client, SIGNAL(error(QAbstractSocket::SocketError))); + QSignalSpy bytesspy(&client, SIGNAL(bytesWritten(qint64))); + + qint64 written = client.writeDatagram(QByteArray(i * 1024, 'w'), serverAddress, + server.localPort()); + + if (written != i * 1024) { +#if defined (Q_OS_HPUX) + QSKIP("HP-UX 11.11 on hai (PA-RISC 64) truncates too long datagrams.", SkipSingle); +#endif + QCOMPARE(bytesspy.count(), 0); + QCOMPARE(errorspy.count(), 1); + QCOMPARE(*static_cast<const int *>(errorspy.at(0).at(0).constData()), + int(QUdpSocket::DatagramTooLargeError)); + QCOMPARE(client.error(), QUdpSocket::DatagramTooLargeError); + break; + } + QVERIFY(bytesspy.count() == 1); + QCOMPARE(*static_cast<const qint64 *>(bytesspy.at(0).at(0).constData()), + qint64(i * 1024)); + QCOMPARE(errorspy.count(), 0); + if (!server.waitForReadyRead(5000)) { +#ifdef Q_OS_SYMBIAN + //symbian receive buffer for datagrams is ~30k, but it can send datagrams up to the maximum 64k... + if (i > 28) { + i = 64; + continue; + } +#endif + QSKIP(QString("UDP packet lost at size %1, unable to complete the test.").arg(i * 1024).toLatin1().data(), SkipSingle); + } + QCOMPARE(server.pendingDatagramSize(), qint64(i * 1024)); + QCOMPARE(server.readDatagram(0, 0), qint64(0)); + } +} + +void tst_QUdpSocket::performance() +{ +#if defined(Q_OS_SYMBIAN) + // Large packets seems not to go through on Symbian + // Reason might be also fragmentation due to VPN connection etc + + QFETCH_GLOBAL(bool, setProxy); + QFETCH_GLOBAL(int, proxyType); + + int arrSize = 8192; + if (setProxy && proxyType == QNetworkProxy::Socks5Proxy) + arrSize = 1024; + + QByteArray arr(arrSize, '@'); +#else + QByteArray arr(8192, '@'); +#endif // Q_OS_SYMBIAN + + QUdpSocket server; +#ifdef FORCE_SESSION + server.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QVERIFY2(server.bind(), server.errorString().toLatin1().constData()); + + QHostAddress serverAddress = QHostAddress::LocalHost; + if (!(server.localAddress() == QHostAddress::AnyIPv4 || server.localAddress() == QHostAddress::AnyIPv6)) + serverAddress = server.localAddress(); + + QUdpSocket client; +#ifdef FORCE_SESSION + client.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + client.connectToHost(serverAddress, server.localPort()); + QVERIFY(client.waitForConnected(10000)); + + QTime stopWatch; + stopWatch.start(); + + qint64 nbytes = 0; + while (stopWatch.elapsed() < 5000) { + for (int i = 0; i < 100; ++i) { + if (client.write(arr.data(), arr.size()) > 0) { + do { + nbytes += server.readDatagram(arr.data(), arr.size()); + } while (server.hasPendingDatagrams()); + } + } + } + + float secs = stopWatch.elapsed() / 1000.0; + qDebug("\t%.2fMB/%.2fs: %.2fMB/s", float(nbytes / (1024.0*1024.0)), + secs, float(nbytes / (1024.0*1024.0)) / secs); + +#if defined(Q_OS_SYMBIAN) + if(nbytes == 0) { + qDebug("No bytes passed through local UDP socket, since UDP socket write returns EWOULDBLOCK"); + qDebug("Should try with blocking sockets, but it is not currently possible due to Open C defect"); + } +#endif + +} + +void tst_QUdpSocket::bindMode() +{ + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) { + QFETCH_GLOBAL(int, proxyType); + if (proxyType == QNetworkProxy::Socks5Proxy) { + QSKIP("With socks5 explicit port binding is not supported.", SkipAll); + } + } + + QUdpSocket socket; +#ifdef FORCE_SESSION + socket.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QVERIFY2(socket.bind(), socket.errorString().toLatin1().constData()); + QUdpSocket socket2; +#ifdef FORCE_SESSION + socket2.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QVERIFY(!socket2.bind(socket.localPort())); +#if defined(Q_OS_SYMBIAN) + if(RProcess().HasCapability(ECapabilityNetworkControl)) { + qDebug("Test executed *with* NetworkControl capability"); + // In Symbian OS ReuseAddressHint together with NetworkControl capability + // gives application *always* right to bind to port. I.e. it does not matter + // if first socket was bound with any bind flag. Since autotests in Symbian + // are currently executed with ALL -TCB rights, this path is the one executed. + QVERIFY(socket2.bind(socket.localPort(), QUdpSocket::ReuseAddressHint)); + socket.close(); + socket2.close(); + + QVERIFY2(socket.bind(0, QUdpSocket::ShareAddress), socket.errorString().toLatin1().constData()); + QVERIFY(!socket2.bind(socket.localPort())); + QVERIFY2(socket2.bind(socket.localPort(), QUdpSocket::ReuseAddressHint), socket2.errorString().toLatin1().constData()); + socket.close(); + socket2.close(); + + QVERIFY2(socket.bind(0, QUdpSocket::DontShareAddress), socket.errorString().toLatin1().constData()); + QVERIFY(!socket2.bind(socket.localPort())); + QVERIFY(socket2.bind(socket.localPort(), QUdpSocket::ReuseAddressHint)); + socket.close(); + socket2.close(); + } else { + qDebug("Test executed *without* NetworkControl capability"); + // If we don't have NetworkControl capability, attempt to bind already bound + // address will *always* fail. I.e. it does not matter if first socket was + // bound with any bind flag. + QVERIFY(!socket2.bind(socket.localPort(), QUdpSocket::ReuseAddressHint)); + socket.close(); + + QVERIFY2(socket.bind(0, QUdpSocket::ShareAddress), socket.errorString().toLatin1().constData()); + QVERIFY(!socket2.bind(socket.localPort())); + QVERIFY2(!socket2.bind(socket.localPort(), QUdpSocket::ReuseAddressHint), socket2.errorString().toLatin1().constData()); + socket.close(); + + QVERIFY2(socket.bind(0, QUdpSocket::DontShareAddress), socket.errorString().toLatin1().constData()); + QVERIFY(!socket2.bind(socket.localPort())); + QVERIFY(!socket2.bind(socket.localPort(), QUdpSocket::ReuseAddressHint)); + socket.close(); + } +#elif defined(Q_OS_UNIX) + QVERIFY(!socket2.bind(socket.localPort(), QUdpSocket::ReuseAddressHint)); + socket.close(); + QVERIFY2(socket.bind(0, QUdpSocket::ShareAddress), socket.errorString().toLatin1().constData()); + QVERIFY2(socket2.bind(socket.localPort()), socket2.errorString().toLatin1().constData()); + socket2.close(); + QVERIFY2(socket2.bind(socket.localPort(), QUdpSocket::ReuseAddressHint), socket2.errorString().toLatin1().constData()); +#else + + // Depending on the user's privileges, this or will succeed or + // fail. Admins are allowed to reuse the address, but nobody else. + if (!socket2.bind(socket.localPort(), QUdpSocket::ReuseAddressHint), socket2.errorString().toLatin1().constData()) + qWarning("Failed to bind with QUdpSocket::ReuseAddressHint, user isn't an administrator?"); + socket.close(); + QVERIFY2(socket.bind(0, QUdpSocket::ShareAddress), socket.errorString().toLatin1().constData()); + QVERIFY(!socket2.bind(socket.localPort())); + socket.close(); + QVERIFY2(socket.bind(0, QUdpSocket::DontShareAddress), socket.errorString().toLatin1().constData()); + QVERIFY(!socket2.bind(socket.localPort())); + QVERIFY(!socket2.bind(socket.localPort(), QUdpSocket::ReuseAddressHint)); +#endif +} + +void tst_QUdpSocket::writeDatagramToNonExistingPeer_data() +{ + QTest::addColumn<bool>("bind"); + QTest::addColumn<QHostAddress>("peerAddress"); + QHostAddress localhost(QHostAddress::LocalHost); + QHostAddress remote = QHostInfo::fromName(QtNetworkSettings::serverName()).addresses().first(); + + QTest::newRow("localhost-unbound") << false << localhost; + QTest::newRow("localhost-bound") << true << localhost; + QTest::newRow("remote-unbound") << false << remote; + QTest::newRow("remote-bound") << true << remote; +} + +void tst_QUdpSocket::writeDatagramToNonExistingPeer() +{ + QFETCH(bool, bind); + QFETCH(QHostAddress, peerAddress); + + quint16 peerPort = 33533 + int(bind); + + QUdpSocket sUdp; +#ifdef FORCE_SESSION + sUdp.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QSignalSpy sReadyReadSpy(&sUdp, SIGNAL(readyRead())); + if (bind) + QVERIFY(sUdp.bind()); + QCOMPARE(sUdp.writeDatagram("", 1, peerAddress, peerPort), qint64(1)); + QTestEventLoop::instance().enterLoop(1); + QCOMPARE(sReadyReadSpy.count(), 0); +} + +void tst_QUdpSocket::writeToNonExistingPeer_data() +{ + QTest::addColumn<QHostAddress>("peerAddress"); + QHostAddress localhost(QHostAddress::LocalHost); + QHostAddress remote = QHostInfo::fromName(QtNetworkSettings::serverName()).addresses().first(); + // write (required to be connected) + QTest::newRow("localhost") << localhost; + QTest::newRow("remote") << remote; +} + +void tst_QUdpSocket::writeToNonExistingPeer() +{ + QSKIP("Connected-mode UDP sockets and their behaviour are erratic", SkipAll); + QFETCH(QHostAddress, peerAddress); + quint16 peerPort = 34534; + qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError"); + + QUdpSocket sConnected; +#ifdef FORCE_SESSION + sConnected.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QSignalSpy sConnectedReadyReadSpy(&sConnected, SIGNAL(readyRead())); + QSignalSpy sConnectedErrorSpy(&sConnected, SIGNAL(error(QAbstractSocket::SocketError))); + sConnected.connectToHost(peerAddress, peerPort, QIODevice::ReadWrite); + QVERIFY(sConnected.waitForConnected(10000)); + + // the first write succeeds... + QCOMPARE(sConnected.write("", 1), qint64(1)); + + // the second one should fail! + QTest::qSleep(1000); // do not process events + QCOMPARE(sConnected.write("", 1), qint64(-1)); + QCOMPARE(int(sConnected.error()), int(QUdpSocket::ConnectionRefusedError)); + + // the third one will succeed... + QCOMPARE(sConnected.write("", 1), qint64(1)); + QTestEventLoop::instance().enterLoop(1); + QCOMPARE(sConnectedReadyReadSpy.count(), 0); + QCOMPARE(sConnectedErrorSpy.count(), 1); + QCOMPARE(int(sConnected.error()), int(QUdpSocket::ConnectionRefusedError)); + + // we should now get a read error + QCOMPARE(sConnected.write("", 1), qint64(1)); + QTest::qSleep(1000); // do not process events + char buf[2]; + QVERIFY(!sConnected.hasPendingDatagrams()); + QCOMPARE(sConnected.bytesAvailable(), Q_INT64_C(0)); + QCOMPARE(sConnected.pendingDatagramSize(), Q_INT64_C(-1)); + QCOMPARE(sConnected.readDatagram(buf, 2), Q_INT64_C(-1)); + QCOMPARE(int(sConnected.error()), int(QUdpSocket::ConnectionRefusedError)); + + QCOMPARE(sConnected.write("", 1), qint64(1)); + QTest::qSleep(1000); // do not process events + QCOMPARE(sConnected.read(buf, 2), Q_INT64_C(0)); + QCOMPARE(int(sConnected.error()), int(QUdpSocket::ConnectionRefusedError)); + + // we should still be connected + QCOMPARE(int(sConnected.state()), int(QUdpSocket::ConnectedState)); +} + +void tst_QUdpSocket::outOfProcessConnectedClientServerTest() +{ +#if defined(Q_OS_WINCE) || defined (Q_OS_SYMBIAN) + QSKIP("This test depends on reading data from QProcess (not supported on Qt/WinCE and Symbian).", SkipAll); +#endif +#if defined(QT_NO_PROCESS) + QSKIP("Qt was compiled with QT_NO_PROCESS", SkipAll); +#else + + QProcess serverProcess; + serverProcess.start(QLatin1String("clientserver/clientserver server 1 1"), + QIODevice::ReadWrite | QIODevice::Text); + QVERIFY2(serverProcess.waitForStarted(3000), + qPrintable("Failed to start subprocess: " + serverProcess.errorString())); + + // Wait until the server has started and reports success. + while (!serverProcess.canReadLine()) + QVERIFY(serverProcess.waitForReadyRead(3000)); + QByteArray serverGreeting = serverProcess.readLine(); + QVERIFY(serverGreeting != QByteArray("XXX\n")); + int serverPort = serverGreeting.trimmed().toInt(); + QVERIFY(serverPort > 0 && serverPort < 65536); + + QProcess clientProcess; + clientProcess.start(QString::fromLatin1("clientserver/clientserver connectedclient %1 %2") + .arg(QLatin1String("127.0.0.1")).arg(serverPort), + QIODevice::ReadWrite | QIODevice::Text); + QVERIFY2(clientProcess.waitForStarted(3000), + qPrintable("Failed to start subprocess: " + clientProcess.errorString())); + + // Wait until the server has started and reports success. + while (!clientProcess.canReadLine()) + QVERIFY(clientProcess.waitForReadyRead(3000)); + QByteArray clientGreeting = clientProcess.readLine(); + QCOMPARE(clientGreeting, QByteArray("ok\n")); + + // Let the client and server talk for 3 seconds + QTest::qWait(3000); + + QStringList serverData = QString::fromLocal8Bit(serverProcess.readAll()).split("\n"); + QStringList clientData = QString::fromLocal8Bit(clientProcess.readAll()).split("\n"); + QVERIFY(serverData.size() > 5); + QVERIFY(clientData.size() > 5); + + for (int i = 0; i < clientData.size() / 2; ++i) { + QCOMPARE(clientData.at(i * 2), QString("readData()")); + QCOMPARE(serverData.at(i * 3), QString("readData()")); + + QString cdata = clientData.at(i * 2 + 1); + QString sdata = serverData.at(i * 3 + 1); + QVERIFY(cdata.startsWith(QLatin1String("got "))); + + QCOMPARE(cdata.mid(4).trimmed().toInt(), sdata.mid(4).trimmed().toInt() * 2); + QVERIFY(serverData.at(i * 3 + 2).startsWith(QLatin1String("sending "))); + QCOMPARE(serverData.at(i * 3 + 2).trimmed().mid(8).toInt(), + sdata.mid(4).trimmed().toInt() * 2); + } + + clientProcess.kill(); + QVERIFY(clientProcess.waitForFinished()); + serverProcess.kill(); + QVERIFY(serverProcess.waitForFinished()); +#endif +} + +void tst_QUdpSocket::outOfProcessUnconnectedClientServerTest() +{ +#if defined(Q_OS_WINCE) || defined (Q_OS_SYMBIAN) + QSKIP("This test depends on reading data from QProcess (not supported on Qt/WinCE and Symbian).", SkipAll); +#endif +#if defined(QT_NO_PROCESS) + QSKIP("Qt was compiled with QT_NO_PROCESS", SkipAll); +#else + + QProcess serverProcess; + serverProcess.start(QLatin1String("clientserver/clientserver server 1 1"), + QIODevice::ReadWrite | QIODevice::Text); + QVERIFY2(serverProcess.waitForStarted(3000), + qPrintable("Failed to start subprocess: " + serverProcess.errorString())); + + // Wait until the server has started and reports success. + while (!serverProcess.canReadLine()) + QVERIFY(serverProcess.waitForReadyRead(3000)); + QByteArray serverGreeting = serverProcess.readLine(); + QVERIFY(serverGreeting != QByteArray("XXX\n")); + int serverPort = serverGreeting.trimmed().toInt(); + QVERIFY(serverPort > 0 && serverPort < 65536); + + QProcess clientProcess; + clientProcess.start(QString::fromLatin1("clientserver/clientserver unconnectedclient %1 %2") + .arg(QLatin1String("127.0.0.1")).arg(serverPort), + QIODevice::ReadWrite | QIODevice::Text); + QVERIFY2(clientProcess.waitForStarted(3000), + qPrintable("Failed to start subprocess: " + clientProcess.errorString())); + + // Wait until the server has started and reports success. + while (!clientProcess.canReadLine()) + QVERIFY(clientProcess.waitForReadyRead(3000)); + QByteArray clientGreeting = clientProcess.readLine(); + QCOMPARE(clientGreeting, QByteArray("ok\n")); + + // Let the client and server talk for 3 seconds + QTest::qWait(3000); + + QStringList serverData = QString::fromLocal8Bit(serverProcess.readAll()).split("\n"); + QStringList clientData = QString::fromLocal8Bit(clientProcess.readAll()).split("\n"); + + QVERIFY(serverData.size() > 5); + QVERIFY(clientData.size() > 5); + + for (int i = 0; i < clientData.size() / 2; ++i) { + QCOMPARE(clientData.at(i * 2), QString("readData()")); + QCOMPARE(serverData.at(i * 3), QString("readData()")); + + QString cdata = clientData.at(i * 2 + 1); + QString sdata = serverData.at(i * 3 + 1); + QVERIFY(cdata.startsWith(QLatin1String("got "))); + + QCOMPARE(cdata.mid(4).trimmed().toInt(), sdata.mid(4).trimmed().toInt() * 2); + QVERIFY(serverData.at(i * 3 + 2).startsWith(QLatin1String("sending "))); + QCOMPARE(serverData.at(i * 3 + 2).trimmed().mid(8).toInt(), + sdata.mid(4).trimmed().toInt() * 2); + } + + clientProcess.kill(); + QVERIFY(clientProcess.waitForFinished()); + serverProcess.kill(); + QVERIFY(serverProcess.waitForFinished()); +#endif +} + +void tst_QUdpSocket::zeroLengthDatagram() +{ + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) + return; + + QUdpSocket receiver; +#ifdef FORCE_SESSION + receiver.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QVERIFY(receiver.bind()); + + QVERIFY(!receiver.waitForReadyRead(100)); + QVERIFY(!receiver.hasPendingDatagrams()); + + QUdpSocket sender; +#ifdef FORCE_SESSION + sender.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + QCOMPARE(sender.writeDatagram(QByteArray(), QHostAddress::LocalHost, receiver.localPort()), qint64(0)); + + QVERIFY(receiver.waitForReadyRead(1000)); + QVERIFY(receiver.hasPendingDatagrams()); + + char buf; + QCOMPARE(receiver.readDatagram(&buf, 1), qint64(0)); +} + +void tst_QUdpSocket::multicastTtlOption_data() +{ + QTest::addColumn<QHostAddress>("bindAddress"); + QTest::addColumn<int>("ttl"); + QTest::addColumn<int>("expected"); + + QList<QHostAddress> addresses; + addresses += QHostAddress(QHostAddress::Any); + addresses += QHostAddress(QHostAddress::AnyIPv6); + + foreach (const QHostAddress &address, addresses) { + QTest::newRow(QString("%1 0").arg(address.toString()).toAscii()) << address << 0 << 0; + QTest::newRow(QString("%1 1").arg(address.toString()).toAscii()) << address << 1 << 1; + QTest::newRow(QString("%1 2").arg(address.toString()).toAscii()) << address << 2 << 2; + QTest::newRow(QString("%1 128").arg(address.toString()).toAscii()) << address << 128 << 128; + QTest::newRow(QString("%1 255").arg(address.toString()).toAscii()) << address << 255 << 255; + QTest::newRow(QString("%1 1024").arg(address.toString()).toAscii()) << address << 1024 << 1; + } +} + +void tst_QUdpSocket::multicastTtlOption() +{ + QFETCH_GLOBAL(bool, setProxy); + QFETCH(QHostAddress, bindAddress); + QFETCH(int, ttl); + QFETCH(int, expected); + if (setProxy) { + // UDP multicast does not work with proxies + expected = 0; + } + + QUdpSocket udpSocket; +#ifdef FORCE_SESSION + udpSocket.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + // bind, but ignore the result, we are only interested in initializing the socket + (void) udpSocket.bind(bindAddress, 0); + udpSocket.setSocketOption(QUdpSocket::MulticastTtlOption, ttl); + QCOMPARE(udpSocket.socketOption(QUdpSocket::MulticastTtlOption).toInt(), expected); +} + +void tst_QUdpSocket::multicastLoopbackOption_data() +{ + QTest::addColumn<QHostAddress>("bindAddress"); + QTest::addColumn<int>("loopback"); + QTest::addColumn<int>("expected"); + + QList<QHostAddress> addresses; + addresses += QHostAddress(QHostAddress::Any); + addresses += QHostAddress(QHostAddress::AnyIPv6); + + foreach (const QHostAddress &address, addresses) { + QTest::newRow(QString("%1 0").arg(address.toString()).toAscii()) << address << 0 << 0; + QTest::newRow(QString("%1 1").arg(address.toString()).toAscii()) << address << 1 << 1; + QTest::newRow(QString("%1 2").arg(address.toString()).toAscii()) << address << 2 << 1; + QTest::newRow(QString("%1 0 again").arg(address.toString()).toAscii()) << address << 0 << 0; + QTest::newRow(QString("%1 2 again").arg(address.toString()).toAscii()) << address << 2 << 1; + QTest::newRow(QString("%1 0 last time").arg(address.toString()).toAscii()) << address << 0 << 0; + QTest::newRow(QString("%1 1 again").arg(address.toString()).toAscii()) << address << 1 << 1; + } +} + +void tst_QUdpSocket::multicastLoopbackOption() +{ + QFETCH_GLOBAL(bool, setProxy); + QFETCH(QHostAddress, bindAddress); + QFETCH(int, loopback); + QFETCH(int, expected); + if (setProxy) { + // UDP multicast does not work with proxies + expected = 0; + } + + QUdpSocket udpSocket; +#ifdef FORCE_SESSION + udpSocket.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + // bind, but ignore the result, we are only interested in initializing the socket + (void) udpSocket.bind(bindAddress, 0); + udpSocket.setSocketOption(QUdpSocket::MulticastLoopbackOption, loopback); + QCOMPARE(udpSocket.socketOption(QUdpSocket::MulticastLoopbackOption).toInt(), expected); +} + +void tst_QUdpSocket::multicastJoinBeforeBind_data() +{ + QTest::addColumn<QHostAddress>("groupAddress"); + QTest::newRow("valid ipv4 group address") << QHostAddress("239.255.118.62"); + QTest::newRow("invalid ipv4 group address") << QHostAddress(QHostAddress::Broadcast); + QTest::newRow("valid ipv6 group address") << QHostAddress("FF01::114"); + QTest::newRow("invalid ipv6 group address") << QHostAddress(QHostAddress::AnyIPv6); +} + +void tst_QUdpSocket::multicastJoinBeforeBind() +{ + QFETCH(QHostAddress, groupAddress); + + QUdpSocket udpSocket; +#ifdef FORCE_SESSION + udpSocket.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + // cannot join group before binding + QTest::ignoreMessage(QtWarningMsg, "QUdpSocket::joinMulticastGroup() called on a QUdpSocket when not in QUdpSocket::BoundState"); + QVERIFY(!udpSocket.joinMulticastGroup(groupAddress)); +} + +void tst_QUdpSocket::multicastLeaveAfterClose_data() +{ + QTest::addColumn<QHostAddress>("groupAddress"); + QTest::newRow("valid ipv4 group address") << QHostAddress("239.255.118.62"); + QTest::newRow("valid ipv6 group address") << QHostAddress("FF01::114"); +} + +void tst_QUdpSocket::multicastLeaveAfterClose() +{ + QFETCH_GLOBAL(bool, setProxy); + QFETCH(QHostAddress, groupAddress); + if (setProxy) { + QSKIP("UDP Multicast does not work with proxies", SkipAll); + } + + QUdpSocket udpSocket; +#ifdef FORCE_SESSION + udpSocket.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif +#ifdef Q_OS_SYMBIAN + QVERIFY2(udpSocket.bind(), + qPrintable(udpSocket.errorString())); +#else + QVERIFY2(udpSocket.bind(groupAddress, 0), + qPrintable(udpSocket.errorString())); +#endif + QVERIFY2(udpSocket.joinMulticastGroup(groupAddress), + qPrintable(udpSocket.errorString())); + udpSocket.close(); + QTest::ignoreMessage(QtWarningMsg, "QUdpSocket::leaveMulticastGroup() called on a QUdpSocket when not in QUdpSocket::BoundState"); + QVERIFY(!udpSocket.leaveMulticastGroup(groupAddress)); +} + +void tst_QUdpSocket::setMulticastInterface_data() +{ + QTest::addColumn<QNetworkInterface>("iface"); + QTest::addColumn<QHostAddress>("address"); + QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); + foreach (const QNetworkInterface &iface, interfaces) { + foreach (const QNetworkAddressEntry &entry, iface.addressEntries()) { + QTest::newRow(QString("%1:%2").arg(iface.name()).arg(entry.ip().toString()).toAscii()) + << iface + << entry.ip(); + } + } +} + +void tst_QUdpSocket::setMulticastInterface() +{ +#ifdef Q_OS_SYMBIAN + QSKIP("Symbian has no IPV6_MULTICAST_IF equivalent", SkipAll); +#else + QFETCH_GLOBAL(bool, setProxy); + QFETCH(QNetworkInterface, iface); + QFETCH(QHostAddress, address); + + QUdpSocket udpSocket; + // bind initializes the socket + bool bound = udpSocket.bind((address.protocol() == QAbstractSocket::IPv6Protocol + ? QHostAddress(QHostAddress::AnyIPv6) + : QHostAddress(QHostAddress::Any)), + 0); + if (!bound) + QTest::ignoreMessage(QtWarningMsg, "QUdpSocket::setMulticastInterface() called on a QUdpSocket when not in QUdpSocket::BoundState"); + udpSocket.setMulticastInterface(iface); + if (!bound) + QTest::ignoreMessage(QtWarningMsg, "QUdpSocket::multicastInterface() called on a QUdpSocket when not in QUdpSocket::BoundState"); + QNetworkInterface iface2 = udpSocket.multicastInterface(); + if (!setProxy) { + QVERIFY(iface2.isValid()); + QCOMPARE(iface.name(), iface2.name()); + } else { + QVERIFY(!iface2.isValid()); + } +#endif +} + +void tst_QUdpSocket::multicast_data() +{ + QHostAddress anyAddress = QHostAddress(QHostAddress::AnyIPv4); + QHostAddress groupAddress = QHostAddress("239.255.118.62"); + QHostAddress any6Address = QHostAddress(QHostAddress::AnyIPv6); + QHostAddress group6Address = QHostAddress("FF01::114"); + + QTest::addColumn<QHostAddress>("bindAddress"); + QTest::addColumn<bool>("bindResult"); + QTest::addColumn<QHostAddress>("groupAddress"); + QTest::addColumn<bool>("joinResult"); + QTest::newRow("valid bind, group ipv4 address") << anyAddress << true << groupAddress << true; + QTest::newRow("valid bind, invalid group ipv4 address") << anyAddress << true << anyAddress << false; + QTest::newRow("same bind, group ipv4 address") << groupAddress << true << groupAddress << true; + QTest::newRow("valid bind, group ipv6 address") << any6Address << true << group6Address << true; + QTest::newRow("valid bind, invalid group ipv6 address") << any6Address << true << any6Address << false; + QTest::newRow("same bind, group ipv6 address") << group6Address << true << group6Address << true; +} + +void tst_QUdpSocket::multicast() +{ + QFETCH_GLOBAL(bool, setProxy); + QFETCH(QHostAddress, bindAddress); + QFETCH(bool, bindResult); + QFETCH(QHostAddress, groupAddress); + QFETCH(bool, joinResult); + if (setProxy) { + // UDP multicast does not work with proxies + if ( +#ifndef Q_OS_WIN + //windows native socket engine binds 0.0.0.0 instead of the requested multicast address + (bindAddress.protocol() == QAbstractSocket::IPv4Protocol && (bindAddress.toIPv4Address() & 0xffff0000) == 0xefff0000) || +#endif + bindAddress.protocol() == QAbstractSocket::IPv6Protocol) { + // proxy cannot bind to IPv6 or multicast addresses + bindResult = false; + } + joinResult = false; + } + + QUdpSocket receiver; +#ifdef FORCE_SESSION + receiver.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + + // bind first, then verify that we can join the multicast group +#ifdef Q_OS_SYMBIAN + if (!setProxy) { + QEXPECT_FAIL("same bind, group ipv4 address", "bind to group address not supported on symbian", Abort); + QEXPECT_FAIL("same bind, group ipv6 address", "bind to group address not supported on symbian", Abort); + } +#endif + QVERIFY2(receiver.bind(bindAddress, 0) == bindResult, + qPrintable(receiver.errorString())); + if (!bindResult) + return; + + QVERIFY2(receiver.joinMulticastGroup(groupAddress) == joinResult, + qPrintable(receiver.errorString())); + if (!joinResult) + return; + + QList<QByteArray> datagrams = QList<QByteArray>() + << QByteArray("0123") + << QByteArray("4567") + << QByteArray("89ab") + << QByteArray("cdef"); + + QUdpSocket sender; +#ifdef FORCE_SESSION + sender.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + sender.bind(); + foreach (const QByteArray &datagram, datagrams) { + QCOMPARE(int(sender.writeDatagram(datagram, groupAddress, receiver.localPort())), + int(datagram.size())); + } + + QVERIFY2(receiver.waitForReadyRead(), + qPrintable(receiver.errorString())); + QVERIFY(receiver.hasPendingDatagrams()); + QList<QByteArray> receivedDatagrams; + while (receiver.hasPendingDatagrams()) { + QByteArray datagram; + datagram.resize(receiver.pendingDatagramSize()); + receiver.readDatagram(datagram.data(), datagram.size(), 0, 0); + receivedDatagrams << datagram; + } +#ifdef Q_OS_SYMBIAN + QEXPECT_FAIL("valid bind, group ipv4 address", "IPv4 multicast not supported on symbian", Abort); +#endif + QCOMPARE(receivedDatagrams, datagrams); + + QVERIFY2(receiver.leaveMulticastGroup(groupAddress), qPrintable(receiver.errorString())); +} + +void tst_QUdpSocket::echo_data() +{ + QTest::addColumn<bool>("connect"); + QTest::newRow("writeDatagram") << false; + QTest::newRow("write") << true; +} + +void tst_QUdpSocket::echo() +{ + QFETCH(bool, connect); + QHostInfo info = QHostInfo::fromName(QtNetworkSettings::serverName()); + QVERIFY(info.addresses().count()); + QHostAddress remote = info.addresses().first(); + + QUdpSocket sock; +#ifdef FORCE_SESSION + sock.setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + if (connect) { + sock.connectToHost(remote, 7); + QVERIFY(sock.waitForConnected(10000)); + } else { + sock.bind(); + } + QByteArray out(30, 'x'); + QByteArray in; + int successes = 0; + for (int i=0;i<10;i++) { + if (connect) { + sock.write(out); + } else { + sock.writeDatagram(out, remote, 7); + } + if (sock.waitForReadyRead(1000)) { + while (sock.hasPendingDatagrams()) { + QHostAddress from; + quint16 port; + if (connect) { + in = sock.read(sock.pendingDatagramSize()); + } else { + in.resize(sock.pendingDatagramSize()); + sock.readDatagram(in.data(), in.length(), &from, &port); + } + if (in==out) + successes++; + } + } + if (!sock.isValid()) + QFAIL(sock.errorString().toLatin1().constData()); + qDebug() << "packets in" << successes << "out" << i; + QTest::qWait(50); //choke to avoid triggering flood/DDoS protections on echo service + } + QVERIFY(successes >= 9); +} + +QTEST_MAIN(tst_QUdpSocket) +#include "tst_qudpsocket.moc" |