// Copyright (C) 2021 The Qt Company Ltd.
// Copyright (C) 2017 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <QTest>
#include <QSignalSpy>
#include <QtEndian>
#if QT_CONFIG(process)
#include <QProcess>
#endif
#include <QScopeGuard>
#include <QVersionNumber>
#include <private/qlatch_p.h>

#include <qcoreapplication.h>
#include <qfileinfo.h>
#include <qdatastream.h>
#include <qdebug.h>
#include <qrandom.h>
#include <qudpsocket.h>
#include <qhostaddress.h>
#include <qhostinfo.h>
#include <qtcpsocket.h>
#include <qmap.h>
#include <qelapsedtimer.h>
#include <qnetworkdatagram.h>
#include <QNetworkProxy>
#include <QNetworkInterface>

#include <qstringlist.h>
#include <QSet>
#include "../../../network-settings.h"
#include <QtTest/private/qemulationdetector_p.h>

#if defined(Q_OS_LINUX)
#define SHOULD_CHECK_SYSCALL_SUPPORT
#endif
#ifdef Q_OS_UNIX
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>
#else
#include <winsock2.h>
#include <ws2tcpip.h>
#endif

#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(SO_NREAD)
#  define RELIABLE_BYTES_AVAILABLE
#endif

#ifndef INVALID_SOCKET
#  define INVALID_SOCKET -1
#endif

using namespace Qt::StringLiterals;

Q_DECLARE_METATYPE(QHostAddress)

QT_FORWARD_DECLARE_CLASS(QUdpSocket)

class tst_QUdpSocket : public QObject
{
    Q_OBJECT

private slots:
    void initTestCase_data();
    void initTestCase();
    void init();
    void cleanup();
    void constructing();
    void setSocketDescriptor_data();
    void setSocketDescriptor();
    void unconnectedServerAndClientTest();
    void broadcasting();
    void broadcastingDualSocket_data();
    void broadcastingDualSocket();
    void loop_data();
    void loop();
    void ipv6Loop_data();
    void ipv6Loop();
    void dualStack();
    void dualStackAutoBinding();
    void dualStackNoIPv4onV6only();
    void connectToHost();
    void bindAndConnectToHost();
    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();
    void linkLocalIPv6();
    void linkLocalIPv4();
    void readyRead();
    void readyReadForEmptyDatagram();
    void asyncReadDatagram();
    void writeInHostLookupState();

    void readyReadConnectionThrottling();

protected slots:
    void empty_readyReadSlot();
    void empty_connectedSlot();
    void async_readDatagramSlot();

private:
    bool shouldSkipIpv6TestsForBrokenSetsockopt();
    bool shouldWorkaroundLinuxKernelBug();
#ifdef SHOULD_CHECK_SYSCALL_SUPPORT
    bool ipv6SetsockoptionMissing(int level, int optname);
#endif
    QNetworkInterface interfaceForGroup(const QHostAddress &multicastGroup);

    bool m_skipUnsupportedIPv6Tests;
    bool m_workaroundLinuxKernelBug;
    int loopbackInterface = 0;      // unknown by default
    QList<QHostAddress> allAddresses;
    QHostAddress multicastGroup4, multicastGroup6;
    QList<QHostAddress> linklocalMulticastGroups;
    QNetworkInterface ifaceWithIPv6;
    QUdpSocket *m_asyncSender;
    QUdpSocket *m_asyncReceiver;
};

// Unlike for IPv6 with IPV6_PKTINFO, IPv4 has no standardized way of obtaining
// the packet's destination addresses. This means the destinationAddress() be
// empty, so whitelist the OSes for which we know we have an implementation.
// That's currently all of them, which means there probably is code in this
// test that assumes this works without checking this variable.
//
// Note: this applies to single-stack operations; dual stack implementations
// appear to be buggy or not present at all in some OSes.
static constexpr bool HasWorkingIPv4DestinationAddress = true;

#ifdef SHOULD_CHECK_SYSCALL_SUPPORT
bool tst_QUdpSocket::ipv6SetsockoptionMissing(int level, int optname)
{
    int testSocket;

    testSocket = socket(PF_INET6, SOCK_DGRAM, 0);

    // If we can't test here, assume it's not missing
    if (testSocket == -1)
        return false;

    bool result = false;
    if (setsockopt(testSocket, level, optname, nullptr, 0) == -1)
        if (errno == ENOPROTOOPT)
            result = true;

    close(testSocket);
    return result;
}
#endif //SHOULD_CHECK_SYSCALL_SUPPORT

bool tst_QUdpSocket::shouldSkipIpv6TestsForBrokenSetsockopt()
{
#ifdef SHOULD_CHECK_SYSCALL_SUPPORT
    // Following parameters for setsockopt are not supported by all QEMU versions:
    if (ipv6SetsockoptionMissing(SOL_IPV6, IPV6_JOIN_GROUP)
        || ipv6SetsockoptionMissing(SOL_IPV6, IPV6_MULTICAST_HOPS)
        || ipv6SetsockoptionMissing(SOL_IPV6, IPV6_MULTICAST_IF)
        || ipv6SetsockoptionMissing(SOL_IPV6, IPV6_MULTICAST_LOOP)
        || ipv6SetsockoptionMissing(SOL_IPV6, IPV6_RECVHOPLIMIT)) {
        return true;
    }
#endif //SHOULD_CHECK_SYSCALL_SUPPORT

    return false;
}

QNetworkInterface tst_QUdpSocket::interfaceForGroup(const QHostAddress &multicastGroup)
{
    if (multicastGroup.protocol() == QAbstractSocket::IPv4Protocol)
        return QNetworkInterface();

    QString scope = multicastGroup.scopeId();
    if (!scope.isEmpty())
        return QNetworkInterface::interfaceFromName(scope);

    return ifaceWithIPv6;
}

bool tst_QUdpSocket::shouldWorkaroundLinuxKernelBug()
{
#ifdef Q_OS_LINUX
    const QVersionNumber version = QVersionNumber::fromString(QSysInfo::kernelVersion());
    return version.majorVersion() == 4 && version.minorVersion() >= 6 && version.minorVersion() < 13;
#else
    return false;
#endif
}

static QHostAddress makeNonAny(const QHostAddress &address, QHostAddress::SpecialAddress preferForAny = QHostAddress::LocalHost)
{
    if (address == QHostAddress::Any)
        return preferForAny;
    if (address == QHostAddress::AnyIPv4)
        return QHostAddress::LocalHost;
    if (address == QHostAddress::AnyIPv6)
        return QHostAddress::LocalHostIPv6;
    return address;
}

void tst_QUdpSocket::initTestCase_data()
{
    // hack: we only enable the Socks5 over UDP tests on the old
    // test server, because they fail on the new one. See QTBUG-35490
    bool newTestServer = true;
#ifndef QT_TEST_SERVER
    QTcpSocket socket;
    socket.connectToHost(QtNetworkSettings::serverName(), 22);
    if (socket.waitForConnected(10000)) {
        socket.waitForReadyRead(5000);
        QByteArray ba = socket.readAll();
        if (ba.startsWith("SSH-2.0-OpenSSH_5.8p1"))
            newTestServer = false;
        socket.disconnectFromHost();
    }
#endif

    QTest::addColumn<bool>("setProxy");
    QTest::addColumn<int>("proxyType");

    QTest::newRow("WithoutProxy") << false << 0;
#if QT_CONFIG(socks5)
    if (!newTestServer)
        QTest::newRow("WithSocks5Proxy") << true << int(QNetworkProxy::Socks5Proxy);
#endif
}

void tst_QUdpSocket::initTestCase()
{
#ifdef QT_TEST_SERVER
     QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::socksProxyServerName(), 1080));
     QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::echoServerName(), 7));
#else
    if (!QtNetworkSettings::verifyTestNetworkSettings())
        QSKIP("No network test server available");
#endif

    allAddresses.clear();
    for (const QNetworkInterface &iface : QNetworkInterface::allInterfaces()) {
        if (!iface.flags().testAnyFlags(QNetworkInterface::IsUp))
            continue;
        if (iface.flags().testAnyFlags(QNetworkInterface::IsLoopBack))
            loopbackInterface = iface.index();

        // add this interface's addresses
        const QList<QNetworkAddressEntry> addresses = iface.addressEntries();
        for (const QNetworkAddressEntry &entry : addresses) {
            allAddresses += entry.ip();
            if (!loopbackInterface && entry.ip().isLoopback())
                loopbackInterface = iface.index();
        }
    }

    m_skipUnsupportedIPv6Tests = shouldSkipIpv6TestsForBrokenSetsockopt();

    // Create a pair of random multicast groups so we avoid clashing with any
    // other tst_qudpsocket running on the same network at the same time.
    quint64 r[2] = {
        // ff14:: is temporary, not prefix-based, admin-local
        qToBigEndian(Q_UINT64_C(0xff14) << 48),
        QRandomGenerator64::global()->generate64()
    };
    multicastGroup6.setAddress(*reinterpret_cast<Q_IPV6ADDR *>(&r));

    // 239.0.0.0/8 is "Organization-Local Scope"
    multicastGroup4.setAddress((239U << 24) | (r[1] & 0xffffff));

    // figure out some link-local IPv6 multicast groups
    // ff12:: is temporary, not prefix-based, link-local
    r[0] = qToBigEndian(Q_UINT64_C(0xff12) << 48);
    QHostAddress llbase(*reinterpret_cast<Q_IPV6ADDR *>(&r));
    for (const QHostAddress &a : std::as_const(allAddresses)) {
        QString scope = a.scopeId();
        if (scope.isEmpty())
            continue;
        llbase.setScopeId(scope);
        linklocalMulticastGroups << llbase;
        if (!ifaceWithIPv6.isValid()) {
            // Remember the first interface we've found that has IPv6 so we can
            // bind non-link-local sockets to it (the first is least likely to
            // be some weird virtual interface).
            ifaceWithIPv6 = QNetworkInterface::interfaceFromName(scope);
        }
    }

    qDebug() << "Will use multicast groups" << multicastGroup4 << multicastGroup6 << linklocalMulticastGroups;
    qDebug() << "Will bind IPv6 sockets to" << ifaceWithIPv6;

    m_workaroundLinuxKernelBug = shouldWorkaroundLinuxKernelBug();
    if (QTestPrivate::isRunningArmOnX86())
        QSKIP("This test is unreliable due to QEMU emulation shortcomings.");
}

void tst_QUdpSocket::init()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy) {
#if QT_CONFIG(socks5)
        QFETCH_GLOBAL(int, proxyType);
        if (proxyType == QNetworkProxy::Socks5Proxy) {
            QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::socksProxyServerName(), 1080));
        }
#else
        QSKIP("No proxy support");
#endif // QT_CONFIG(socks5)
    }
}

void tst_QUdpSocket::cleanup()
{
#ifndef QT_NO_NETWORKPROXY
        QNetworkProxy::setApplicationProxy(QNetworkProxy::DefaultProxy);
#endif // !QT_NO_NETWORKPROXY
}


//----------------------------------------------------------------------------------

void tst_QUdpSocket::constructing()
{
    QUdpSocket socket;

    QVERIFY(socket.isSequential());
    QVERIFY(!socket.isOpen());
    QCOMPARE(socket.socketType(), QUdpSocket::UdpSocket);
    QCOMPARE((int) socket.bytesAvailable(), 0);
    QCOMPARE(socket.canReadLine(), false);
    QCOMPARE(socket.readLine(), QByteArray());
    QCOMPARE(socket.socketDescriptor(), (qintptr)-1);
    QCOMPARE(socket.error(), QUdpSocket::UnknownSocketError);
    QCOMPARE(socket.errorString(), QString("Unknown error"));

    // Check the state of the socket api
}

//----------------------------------------------------------------------------------

void tst_QUdpSocket::setSocketDescriptor_data()
{
    QTest::addColumn<QAbstractSocket::NetworkLayerProtocol>("protocol");
    QTest::newRow("ipv4") << QAbstractSocket::IPv4Protocol;

    bool hasIPv6 = true;
#if defined(PF_INET6) && defined(Q_OS_UNIX)
    if (int fd = socket(PF_INET6, SOCK_DGRAM, 0); fd >= 0)
        close(fd);
    else
        hasIPv6 = false;
#endif

    if (hasIPv6) {
        QTest::newRow("ipv6") << QAbstractSocket::IPv6Protocol;
#  if defined(IPV6_V6ONLY) && !defined(Q_OS_VXWORKS)
        QTest::newRow("dualstack") << QAbstractSocket::AnyIPProtocol;
#  endif
    }
}

void tst_QUdpSocket::setSocketDescriptor()
{
    QFETCH(QAbstractSocket::NetworkLayerProtocol, protocol);
    qintptr descriptor = -1;
    int port = -1;

    int domain = (protocol == QAbstractSocket::IPv4Protocol) ? AF_INET : AF_INET6;
    descriptor = socket(domain, SOCK_DGRAM, 0);

    if (descriptor == INVALID_SOCKET)
        QSKIP("Could not create native socket");

#ifdef Q_OS_WIN
    auto closeSocket = qScopeGuard([&descriptor] { closesocket(descriptor); });
    using socklen_t = int;
#else
    auto closeSocket = qScopeGuard([&descriptor] { close(descriptor); });
#endif

    if (domain == AF_INET) {
        sockaddr_in addr = {};
        addr.sin_family = AF_INET;
        if (bind(descriptor, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) < 0)
            QSKIP("Could not bind IPv4 socket");

        socklen_t len = sizeof(addr);
        if (getsockname(descriptor, reinterpret_cast<sockaddr *>(&addr), &len) < 0)
            QSKIP("Could not get local IPv4 port number");
        port = qFromBigEndian(addr.sin_port);
#ifdef PF_INET6
    } else {
#  ifdef IPV6_V6ONLY
        int v6only = protocol == QAbstractSocket::IPv6Protocol;
        if (setsockopt(descriptor, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&v6only, sizeof(v6only)) < 0)
            QSKIP("Could not set IPV6_V6ONLY");
#  endif

        sockaddr_in6 addr = {};
        addr.sin6_family = AF_INET6;
        if (bind(descriptor, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) < 0)
            QSKIP("Could not bind IPv6 socket");

        socklen_t len = sizeof(addr);
        if (getsockname(descriptor, reinterpret_cast<sockaddr *>(&addr), &len) < 0)
            QSKIP("Could not get local IPv6 port number");
        port = qFromBigEndian(addr.sin6_port);
#  endif // IPv6
    }

    QUdpSocket socket;
    QSignalSpy spy(&socket, &QUdpSocket::stateChanged);
    QVERIFY2(socket.setSocketDescriptor(descriptor, QAbstractSocket::BoundState),
             qPrintable(socket.errorString()));
    QCOMPARE(socket.socketDescriptor(), descriptor);
    closeSocket.dismiss();

    QVERIFY(socket.isValid());
    QCOMPARE(socket.socketType(), QAbstractSocket::UdpSocket);

    QCOMPARE(socket.state(), QAbstractSocket::BoundState);
    QCOMPARE(spy.size(), 1);
    QCOMPARE(spy.at(0), QVariantList{QVariant::fromValue(QAbstractSocket::BoundState)});

    QCOMPARE(socket.localPort(), port);
    if (protocol == QAbstractSocket::IPv4Protocol)
        QCOMPARE(socket.localAddress(), QHostAddress::AnyIPv4);
    else if (protocol == QAbstractSocket::IPv6Protocol)
        QCOMPARE(socket.localAddress(), QHostAddress::AnyIPv6);
    else
        QCOMPARE(socket.localAddress(), QHostAddress::Any);
    QCOMPARE(socket.bytesToWrite(), 0);
    QCOMPARE(socket.bytesAvailable(), 0);
}

//----------------------------------------------------------------------------------

void tst_QUdpSocket::unconnectedServerAndClientTest()
{
    QUdpSocket serverSocket;

    qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState");

    QSignalSpy stateChangedSpy(&serverSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)));
    QVERIFY2(serverSocket.bind(), serverSocket.errorString().toLatin1().constData());
    QCOMPARE(stateChangedSpy.size(), 1);

    const char *message[] = {"Yo mista", "Yo", "Wassap"};

    QHostAddress serverAddress = makeNonAny(serverSocket.localAddress());
    for (int i = 0; i < 3; ++i) {
        QUdpSocket clientSocket;
        QCOMPARE(int(clientSocket.writeDatagram(message[i], strlen(message[i]),
                                               serverAddress, serverSocket.localPort())),
                int(strlen(message[i])));
        char buf[1024];
        QHostAddress host;
        quint16 port;
        QVERIFY2(serverSocket.waitForReadyRead(5000), QtNetworkSettings::msgSocketError(serverSocket).constData());
        QCOMPARE(int(serverSocket.readDatagram(buf, sizeof(buf), &host, &port)),
                int(strlen(message[i])));
        buf[strlen(message[i])] = '\0';
        QCOMPARE(QByteArray(buf), QByteArray(message[i]));
        QCOMPARE(port, clientSocket.localPort());
        if (host.toIPv4Address()) // in case the sender is IPv4 mapped in IPv6
            QCOMPARE(host.toIPv4Address(), makeNonAny(clientSocket.localAddress()).toIPv4Address());
        else
            QCOMPARE(host, makeNonAny(clientSocket.localAddress()));
    }
}

//----------------------------------------------------------------------------------

static QList<QHostAddress> getBroadcastAddresses()
{
    QList<QHostAddress> broadcastAddresses;

#ifndef Q_OS_BSD
    broadcastAddresses << QHostAddress(0x7fffffff); // 127.255.255.255, on interface "lo"
#endif
    broadcastAddresses << QHostAddress::Broadcast;

    const auto ifaces = QNetworkInterface::allInterfaces();
    for (const QNetworkInterface &iface : ifaces) {
        if ((iface.flags() & QNetworkInterface::CanBroadcast)
            && iface.flags() & QNetworkInterface::IsUp) {
            for (int i=0;i<iface.addressEntries().size();i++) {
                QHostAddress broadcast = iface.addressEntries().at(i).broadcast();
                if (broadcast.protocol() == QAbstractSocket::IPv4Protocol)
                    broadcastAddresses.append(broadcast);
            }
        }
    }
    return broadcastAddresses;
}

void tst_QUdpSocket::broadcasting()
{
    if (m_workaroundLinuxKernelBug)
        QSKIP("This test can fail due to linux kernel bug");

    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy) {
#ifndef QT_NO_NETWORKPROXY
        QFETCH_GLOBAL(int, proxyType);
        if (proxyType == QNetworkProxy::Socks5Proxy)
            QSKIP("With socks5 Broadcast is not supported.");
#else // !QT_NO_NETWORKPROXY
        QSKIP("No proxy support");
#endif // QT_NO_NETWORKPROXY
    }
#ifdef Q_OS_AIX
    QSKIP("Broadcast does not work on darko");
#endif
    const char *message[] = {"Yo mista", "", "Yo", "Wassap"};

    const QList<QHostAddress> broadcastAddresses = getBroadcastAddresses();
    if (broadcastAddresses.isEmpty())
        QSKIP("No interface can broadcast");
    bool destinationsPrinted = false;
    for (int i = 0; i < 4; ++i) {
        QUdpSocket serverSocket;
        QVERIFY2(serverSocket.bind(QHostAddress(QHostAddress::AnyIPv4), 0), serverSocket.errorString().toLatin1().constData());
        quint16 serverPort = serverSocket.localPort();

        QCOMPARE(serverSocket.state(), QUdpSocket::BoundState);

        connect(&serverSocket, SIGNAL(readyRead()), SLOT(empty_readyReadSlot()));

        QUdpSocket broadcastSocket;
        broadcastSocket.bind(QHostAddress(QHostAddress::AnyIPv4), 0);

        for (int j = 0; j < 10; ++j) {
            {
                // qDebug() << "Broadcasting to" << broadcastAddresses;
                QSet<QHostAddress> addresses;
                QString lastErrorString;
                for (int k = 0; k < 4; k++) {
                    for (const QHostAddress &addr : broadcastAddresses) {
                        qint64 written = broadcastSocket.writeDatagram(message[i], strlen(message[i]),
                                                                       addr, serverPort);
                        if (written < 0) {
                            lastErrorString = broadcastSocket.errorString();
                        } else {
                            QCOMPARE(written, strlen(message[i]));
                            addresses << addr;
                        }
                    }
                }
                QVERIFY2(!addresses.isEmpty(), qPrintable(lastErrorString));
                if (!destinationsPrinted)
                    qDebug() << "Successfully sent datagrams to" << addresses;
                destinationsPrinted = true;
            }

            QTestEventLoop::instance().enterLoop(15);
            if (QTestEventLoop::instance().timeout())
                QFAIL("Network operation timed out");
            QVERIFY(serverSocket.hasPendingDatagrams());

            do {
                const int messageLength = int(strlen(message[i]));
                QNetworkDatagram dgram = serverSocket.receiveDatagram();
                QVERIFY(dgram.isValid());
                QByteArray arr = dgram.data();

                QCOMPARE(arr.size(), messageLength);
                arr.resize(messageLength);
                QCOMPARE(arr, QByteArray(message[i]));

                if (dgram.senderAddress().toIPv4Address()) // in case it's a v6-mapped address
                    QVERIFY2(allAddresses.contains(QHostAddress(dgram.senderAddress().toIPv4Address())),
                             dgram.senderAddress().toString().toLatin1());
                else if (!dgram.senderAddress().isNull())
                    QVERIFY2(allAddresses.contains(dgram.senderAddress()),
                             dgram.senderAddress().toString().toLatin1());
                QCOMPARE(dgram.senderPort(), int(broadcastSocket.localPort()));
                if (HasWorkingIPv4DestinationAddress) {
                    QVERIFY2(dgram.destinationAddress() == QHostAddress::Broadcast
                            || broadcastAddresses.contains(dgram.destinationAddress()),
                             dgram.destinationAddress().toString().toLatin1());
                }
                QCOMPARE(dgram.destinationPort(), int(serverSocket.localPort()));

                int ttl = dgram.hopLimit();
                if (ttl != -1)
                    QVERIFY(ttl != 0);
            } while (serverSocket.hasPendingDatagrams());
        }
    }
}

//----------------------------------------------------------------------------------

void tst_QUdpSocket::broadcastingDualSocket_data()
{
    QTest::addColumn<bool>("explicitlyBind");
    QTest::newRow("without-binding") << false;
    QTest::newRow("explicitly-binding") << true;
}

void tst_QUdpSocket::broadcastingDualSocket()
{
#if defined(Q_OS_VXWORKS)
    QSKIP("Broadcasting on dual-stack sockets not supported on this platform");
#endif
    if (m_workaroundLinuxKernelBug)
        QSKIP("This test can fail due to linux kernel bug");

    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        QSKIP("No sense in testing broadcasts with proxies");


    QFETCH(bool, explicitlyBind);
    QByteArray msg = "This is a message sent over broadcast";

    QUdpSocket serverSocket;
    QVERIFY2(serverSocket.bind(QHostAddress(QHostAddress::Any), 0),
             serverSocket.errorString().toLatin1().constData());
    quint16 serverPort = serverSocket.localPort();

    QCOMPARE(serverSocket.state(), QUdpSocket::BoundState);

    connect(&serverSocket, SIGNAL(readyRead()), SLOT(empty_readyReadSlot()));

    QUdpSocket broadcastSocket;
    if (explicitlyBind) {
        QVERIFY2(broadcastSocket.bind(QHostAddress(QHostAddress::Any), 0),
                 broadcastSocket.errorString().toLatin1().constData());
        // qDebug() << "Bound server to" << serverSocket.localAddress() << serverPort
        //          << "and will send from" << broadcastSocket.localAddress() << broadcastSocket.localPort();
    } else {
        // qDebug() << "Bound server to" << serverSocket.localAddress() << serverPort;
    }

    const QList<QHostAddress> broadcastAddresses = getBroadcastAddresses();
    if (broadcastAddresses.isEmpty())
        QSKIP("No interface can broadcast");

    // send to all broadcast addresses and hope some messages arrive
    {
        // qDebug() << "Broadcasting to" << broadcastAddresses;
        QList<QHostAddress> addresses;
        QString lastErrorString;
        for (const QHostAddress &address : broadcastAddresses) {
            qint64 written = broadcastSocket.writeDatagram(msg, address, serverPort);
            if (written < 0) {
                lastErrorString = broadcastSocket.errorString();
            } else {
                QCOMPARE(written, msg.size());
                addresses << address;
            }
        }
        QVERIFY2(!addresses.isEmpty(), qPrintable(lastErrorString));
        qDebug() << "Successfully sent datagrams to" << addresses;
    }

    QTestEventLoop::instance().enterLoop(3);
    if (QTestEventLoop::instance().timeout())
        QFAIL("Network operation timed out");

    // Collect all datagrams
    QList<QNetworkDatagram> datagrams;
    QVERIFY(serverSocket.hasPendingDatagrams());
    do {
        datagrams << serverSocket.receiveDatagram();
    } while (serverSocket.hasPendingDatagrams());

    {
        // collect the list of addresses first to print in debugging
        QList<QHostAddress> addresses;
        for (const QNetworkDatagram &dgram : std::as_const(datagrams))
            addresses << dgram.destinationAddress();
        qDebug() << "Received datagrams sent to" << addresses;
    }

    for (const QNetworkDatagram &dgram : std::as_const(datagrams)) {
        QVERIFY(dgram.isValid());
        QCOMPARE(dgram.data(), QByteArrayView(msg));

        // verify sender
        QCOMPARE(dgram.senderPort(), broadcastSocket.localPort());
    }
}

//----------------------------------------------------------------------------------

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;

    // make sure we bind to IPv4
    QHostAddress localhost = QHostAddress::LocalHost;
    QVERIFY2(peter.bind(localhost), peter.errorString().toLatin1().constData());
    QVERIFY2(paul.bind(localhost), paul.errorString().toLatin1().constData());

    QHostAddress peterAddress = makeNonAny(peter.localAddress());
    QHostAddress paulAddress = makeNonAny(paul.localAddress());

    QCOMPARE(peter.writeDatagram(peterMessage.data(), peterMessage.size(),
                                paulAddress, paul.localPort()), qint64(peterMessage.size()));
    QCOMPARE(paul.writeDatagram(paulMessage.data(), paulMessage.size(),
                               peterAddress, peter.localPort()), qint64(paulMessage.size()));

    QVERIFY2(peter.waitForReadyRead(9000), QtNetworkSettings::msgSocketError(peter).constData());
    QVERIFY2(paul.waitForReadyRead(9000), QtNetworkSettings::msgSocketError(paul).constData());

    QNetworkDatagram peterDatagram = peter.receiveDatagram(paulMessage.size() * 2);
    QNetworkDatagram paulDatagram = paul.receiveDatagram(peterMessage.size() * 2);
    if (success) {
        QCOMPARE(peterDatagram.data().size(), qint64(paulMessage.size()));
        QCOMPARE(paulDatagram.data().size(), qint64(peterMessage.size()));
    } else {
        // this code path seems to never be executed
        QVERIFY(peterDatagram.data().size() != paulMessage.size());
        QVERIFY(paulDatagram.data().size() != peterMessage.size());
    }

    QCOMPARE(peterDatagram.data().left(paulMessage.size()), paulMessage);
    QCOMPARE(paulDatagram.data().left(peterMessage.size()), peterMessage);

    QCOMPARE(peterDatagram.senderAddress(), paulAddress);
    QCOMPARE(paulDatagram.senderAddress(), peterAddress);
    QCOMPARE(paulDatagram.senderPort(), int(peter.localPort()));
    QCOMPARE(peterDatagram.senderPort(), int(paul.localPort()));

    if (!HasWorkingIPv4DestinationAddress) {
        QCOMPARE(peterDatagram.destinationAddress().protocol(), QAbstractSocket::UnknownNetworkLayerProtocol);
        QCOMPARE(paulDatagram.destinationAddress().protocol(), QAbstractSocket::UnknownNetworkLayerProtocol);
    } else {
        QCOMPARE(peterDatagram.destinationAddress(), makeNonAny(peter.localAddress()));
        QCOMPARE(paulDatagram.destinationAddress(), makeNonAny(paul.localAddress()));
        QVERIFY(peterDatagram.destinationAddress().isEqual(makeNonAny(peter.localAddress())));
        QVERIFY(paulDatagram.destinationAddress().isEqual(makeNonAny(paul.localAddress())));

        if (loopbackInterface) {
            QCOMPARE(peterDatagram.interfaceIndex(), loopbackInterface);
            QCOMPARE(paulDatagram.interfaceIndex(), loopbackInterface);
        }
    }
}

//----------------------------------------------------------------------------------

void tst_QUdpSocket::ipv6Loop_data()
{
    loop_data();
}

void tst_QUdpSocket::ipv6Loop()
{
    QFETCH(QByteArray, peterMessage);
    QFETCH(QByteArray, paulMessage);
    QFETCH(bool, success);

    QUdpSocket peter;
    QUdpSocket paul;

    int peterPort;
    int paulPort;

    if (!peter.bind(QHostAddress(QHostAddress::LocalHostIPv6), 0)) {
        QCOMPARE(peter.error(), QUdpSocket::UnsupportedSocketOperationError);
        return;
    }

    QVERIFY(paul.bind(QHostAddress(QHostAddress::LocalHostIPv6), 0));

    QHostAddress peterAddress = makeNonAny(peter.localAddress());
    QHostAddress paulAddress = makeNonAny(paul.localAddress());
    peterPort = peter.localPort();
    paulPort = paul.localPort();

    QCOMPARE(peter.writeDatagram(peterMessage.data(), peterMessage.size(), QHostAddress("::1"),
                                    paulPort), qint64(peterMessage.size()));
    QCOMPARE(paul.writeDatagram(paulMessage.data(), paulMessage.size(),
                                   QHostAddress("::1"), peterPort), qint64(paulMessage.size()));

    QVERIFY(peter.waitForReadyRead(5000));
    QVERIFY(paul.waitForReadyRead(5000));
    QNetworkDatagram peterDatagram = peter.receiveDatagram(paulMessage.size() * 2);
    QNetworkDatagram paulDatagram = paul.receiveDatagram(peterMessage.size() * 2);

    if (success) {
        QCOMPARE(peterDatagram.data().size(), qint64(paulMessage.size()));
        QCOMPARE(paulDatagram.data().size(), qint64(peterMessage.size()));
    } else {
        // this code path seems to never be executed
        QVERIFY(peterDatagram.data().size() != paulMessage.size());
        QVERIFY(paulDatagram.data().size() != peterMessage.size());
    }

    QCOMPARE(peterDatagram.data().left(paulMessage.size()), paulMessage);
    QCOMPARE(paulDatagram.data().left(peterMessage.size()), peterMessage);

    QCOMPARE(peterDatagram.senderAddress(), paulAddress);
    QCOMPARE(paulDatagram.senderAddress(), peterAddress);
    QCOMPARE(paulDatagram.senderPort(), peterPort);
    QCOMPARE(peterDatagram.senderPort(), paulPort);

    // For IPv6, IPV6_PKTINFO is a mandatory feature (RFC 3542).
    QCOMPARE(peterDatagram.destinationAddress(), makeNonAny(peter.localAddress()));
    QCOMPARE(paulDatagram.destinationAddress(), makeNonAny(paul.localAddress()));
    QCOMPARE(peterDatagram.destinationPort(), peterPort);
    QCOMPARE(paulDatagram.destinationPort(), paulPort);

    if (loopbackInterface) {
        QCOMPARE(peterDatagram.interfaceIndex(), loopbackInterface);
        QCOMPARE(paulDatagram.interfaceIndex(), loopbackInterface);
    }
}

void tst_QUdpSocket::dualStack()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        QSKIP("test server SOCKS proxy doesn't support IPv6");
    QUdpSocket dualSock;
    QByteArray dualData("dual");
    QVERIFY(dualSock.bind(QHostAddress(QHostAddress::Any), 0));

    QUdpSocket v4Sock;
    QByteArray v4Data("v4");
    QVERIFY(v4Sock.bind(QHostAddress(QHostAddress::AnyIPv4), 0));

    //test v4 -> dual
    QCOMPARE((int)v4Sock.writeDatagram(v4Data.constData(), v4Data.size(), QHostAddress(QHostAddress::LocalHost), dualSock.localPort()), v4Data.size());
    QVERIFY2(dualSock.waitForReadyRead(5000), QtNetworkSettings::msgSocketError(dualSock).constData());
    QNetworkDatagram dgram = dualSock.receiveDatagram(100);
    QVERIFY(dgram.isValid());
    QCOMPARE(dgram.data(), v4Data);
    QCOMPARE(dgram.senderPort(), int(v4Sock.localPort()));
    // receiving v4 on dual stack will receive as IPv6, so use isEqual()
    QVERIFY(dgram.senderAddress().isEqual(makeNonAny(v4Sock.localAddress(), QHostAddress::Null)));
    if (dualSock.localAddress().protocol() == QAbstractSocket::IPv4Protocol)
        QCOMPARE(dgram.senderAddress(), makeNonAny(v4Sock.localAddress(), QHostAddress::Null));
    if (dgram.destinationPort() != -1) {
        QCOMPARE(dgram.destinationPort(), int(dualSock.localPort()));
        QVERIFY(dgram.destinationAddress().isEqual(makeNonAny(dualSock.localAddress(), QHostAddress::LocalHost)));
    } else {
        // Observed on QNX: the IPV6_PKTINFO ancillary data appears to be
        // missing if the sender is IPv4.
        qInfo("Getting IPv4 destination address failed.");
    }

    if (QtNetworkSettings::hasIPv6()) {
        QUdpSocket v6Sock;
        QByteArray v6Data("v6");
        QVERIFY(v6Sock.bind(QHostAddress(QHostAddress::AnyIPv6), 0));

        //test v6 -> dual
        QCOMPARE((int)v6Sock.writeDatagram(v6Data.constData(), v6Data.size(), QHostAddress(QHostAddress::LocalHostIPv6), dualSock.localPort()), v6Data.size());
        QVERIFY2(dualSock.waitForReadyRead(5000), QtNetworkSettings::msgSocketError(dualSock).constData());
        dgram = dualSock.receiveDatagram(100);
        QVERIFY(dgram.isValid());
        QCOMPARE(dgram.data(), v6Data);
        QCOMPARE(dgram.senderPort(), int(v6Sock.localPort()));
        QCOMPARE(dgram.senderAddress(), makeNonAny(v6Sock.localAddress(), QHostAddress::LocalHostIPv6));
        QCOMPARE(dgram.destinationPort(), int(dualSock.localPort()));
        QCOMPARE(dgram.destinationAddress(), makeNonAny(dualSock.localAddress(), QHostAddress::LocalHostIPv6));

        //test dual -> v6
        QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.size(), QHostAddress(QHostAddress::LocalHostIPv6), v6Sock.localPort()), dualData.size());
        QVERIFY2(v6Sock.waitForReadyRead(5000), QtNetworkSettings::msgSocketError(v6Sock).constData());
        dgram = v6Sock.receiveDatagram(100);
        QVERIFY(dgram.isValid());
        QCOMPARE(dgram.data(), dualData);
        QCOMPARE(dgram.senderPort(), int(dualSock.localPort()));
        QCOMPARE(dgram.senderAddress(), makeNonAny(dualSock.localAddress(), QHostAddress::LocalHostIPv6));
        QCOMPARE(dgram.destinationPort(), int(v6Sock.localPort()));
        QCOMPARE(dgram.destinationAddress(), makeNonAny(v6Sock.localAddress(), QHostAddress::LocalHostIPv6));
    }

    //test dual -> v4
    QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.size(), QHostAddress(QHostAddress::LocalHost), v4Sock.localPort()), dualData.size());
    QVERIFY2(v4Sock.waitForReadyRead(5000), QtNetworkSettings::msgSocketError(v4Sock).constData());
    dgram = v4Sock.receiveDatagram(100);
    QVERIFY(dgram.isValid());
    QCOMPARE(dgram.data(), dualData);
    QCOMPARE(dgram.senderPort(), int(dualSock.localPort()));
    QCOMPARE(dgram.senderAddress(), makeNonAny(dualSock.localAddress(), QHostAddress::LocalHost));
    QCOMPARE(dgram.destinationPort(), int(v4Sock.localPort()));
    if (HasWorkingIPv4DestinationAddress) {
        QCOMPARE(dgram.destinationAddress(), makeNonAny(v4Sock.localAddress(), QHostAddress::LocalHost));
        if (loopbackInterface)
            QCOMPARE(dgram.interfaceIndex(), loopbackInterface);
    }
}

void tst_QUdpSocket::dualStackAutoBinding()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        QSKIP("test server SOCKS proxy doesn't support IPv6");
    if (!QtNetworkSettings::hasIPv6())
        QSKIP("system doesn't support ipv6!");
    QUdpSocket v4Sock;
    QVERIFY(v4Sock.bind(QHostAddress(QHostAddress::AnyIPv4), 0));

    QUdpSocket v6Sock;
    QVERIFY(v6Sock.bind(QHostAddress(QHostAddress::AnyIPv6), 0));

    QByteArray dualData("dual");
    QHostAddress from;
    quint16 port;
    QByteArray buffer;
    int size;

    {
        //test an autobound socket can send to both v4 and v6 addresses (v4 first)
        QUdpSocket dualSock;

        QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.size(), QHostAddress(QHostAddress::LocalHost), v4Sock.localPort()), dualData.size());
        QVERIFY2(v4Sock.waitForReadyRead(5000), QtNetworkSettings::msgSocketError(v4Sock).constData());
        buffer.reserve(100);
        size = v4Sock.readDatagram(buffer.data(), 100, &from, &port);
        QCOMPARE((int)size, dualData.size());
        buffer.resize(size);
        QCOMPARE(buffer, dualData);

        QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.size(), QHostAddress(QHostAddress::LocalHostIPv6), v6Sock.localPort()), dualData.size());
        QVERIFY2(v6Sock.waitForReadyRead(5000), QtNetworkSettings::msgSocketError(v6Sock).constData());
        buffer.reserve(100);
        size = v6Sock.readDatagram(buffer.data(), 100, &from, &port);
        QCOMPARE((int)size, dualData.size());
        buffer.resize(size);
        QCOMPARE(buffer, dualData);
    }

    {
        //test an autobound socket can send to both v4 and v6 addresses (v6 first)
        QUdpSocket dualSock;

        QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.size(), QHostAddress(QHostAddress::LocalHostIPv6), v6Sock.localPort()), dualData.size());
        QVERIFY2(v6Sock.waitForReadyRead(5000), QtNetworkSettings::msgSocketError(v6Sock).constData());
        buffer.reserve(100);
        size = v6Sock.readDatagram(buffer.data(), 100, &from, &port);
        QCOMPARE((int)size, dualData.size());
        buffer.resize(size);
        QCOMPARE(buffer, dualData);

        QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.size(), QHostAddress(QHostAddress::LocalHost), v4Sock.localPort()), dualData.size());
        QVERIFY2(v4Sock.waitForReadyRead(5000), QtNetworkSettings::msgSocketError(v4Sock).constData());
        buffer.reserve(100);
        size = v4Sock.readDatagram(buffer.data(), 100, &from, &port);
        QCOMPARE((int)size, dualData.size());
        buffer.resize(size);
        QCOMPARE(buffer, dualData);
    }
}

void tst_QUdpSocket::dualStackNoIPv4onV6only()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        QSKIP("test server SOCKS proxy doesn't support IPv6");
    if (!QtNetworkSettings::hasIPv6())
        QSKIP("system doesn't support ipv6!");
    QUdpSocket v4Sock;
    QVERIFY(v4Sock.bind(QHostAddress(QHostAddress::AnyIPv4), 0));
    QByteArray v4Data("v4");

    QUdpSocket v6Sock;
    QVERIFY(v6Sock.bind(QHostAddress(QHostAddress::AnyIPv6), 0));

    //test v4 -> v6 (should not be received as this is a v6 only socket)
    QCOMPARE((int)v4Sock.writeDatagram(v4Data.constData(), v4Data.size(), QHostAddress(QHostAddress::LocalHost), v6Sock.localPort()), v4Data.size());
    QVERIFY(!v6Sock.waitForReadyRead(1000));
}

void tst_QUdpSocket::empty_readyReadSlot()
{
    QTestEventLoop::instance().exitLoop();
}

void tst_QUdpSocket::empty_connectedSlot()
{
    QTestEventLoop::instance().exitLoop();
}

//----------------------------------------------------------------------------------

void tst_QUdpSocket::connectToHost()
{
    QUdpSocket socket1;
    QUdpSocket socket2;

    QVERIFY2(socket1.bind(), socket1.errorString().toLatin1().constData());

    socket2.connectToHost(makeNonAny(socket1.localAddress()), socket1.localPort());
    QVERIFY(socket2.waitForConnected(5000));
}

//----------------------------------------------------------------------------------

void tst_QUdpSocket::bindAndConnectToHost()
{
    QUdpSocket socket1;
    QUdpSocket socket2;
    QUdpSocket dummysocket;

    // we use the dummy socket to use up a file descriptor
    dummysocket.bind();

    QVERIFY2(socket2.bind(), socket2.errorString().toLatin1());
    quint16 boundPort = socket2.localPort();
    qintptr fd = socket2.socketDescriptor();

    QVERIFY2(socket1.bind(), socket1.errorString().toLatin1().constData());

    dummysocket.close();
    socket2.connectToHost(makeNonAny(socket1.localAddress()), socket1.localPort());
    QVERIFY(socket2.waitForConnected(5000));

    QCOMPARE(socket2.localPort(), boundPort);
    QCOMPARE(socket2.socketDescriptor(), fd);
}

//----------------------------------------------------------------------------------

void tst_QUdpSocket::pendingDatagramSize()
{
    if (m_workaroundLinuxKernelBug)
        QSKIP("This test can fail due to linux kernel bug");

    QUdpSocket server;
    QVERIFY2(server.bind(), server.errorString().toLatin1().constData());

    QHostAddress serverAddress = makeNonAny(server.localAddress());
    QUdpSocket client;
    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;
    QVERIFY2(server.waitForReadyRead(), QtNetworkSettings::msgSocketError(server).constData());
    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");

    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");

    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");
}


void tst_QUdpSocket::writeDatagram()
{
    QUdpSocket server;
    QVERIFY2(server.bind(), server.errorString().toLatin1().constData());

    QHostAddress serverAddress = makeNonAny(server.localAddress());
    QUdpSocket client;

    qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");

    for(int i=0;;i++) {
        QSignalSpy errorspy(&client, SIGNAL(errorOccurred(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.");
#endif
            QCOMPARE(bytesspy.size(), 0);
            QCOMPARE(errorspy.size(), 1);
            QCOMPARE(*static_cast<const int *>(errorspy.at(0).at(0).constData()),
                    int(QUdpSocket::DatagramTooLargeError));
            QCOMPARE(client.error(), QUdpSocket::DatagramTooLargeError);
            break;
        }
        QCOMPARE(bytesspy.size(), 1);
        QCOMPARE(*static_cast<const qint64 *>(bytesspy.at(0).at(0).constData()),
                qint64(i * 1024));
        QCOMPARE(errorspy.size(), 0);
        if (!server.waitForReadyRead(5000))
            QSKIP(QString("UDP packet lost at size %1, unable to complete the test.").arg(i * 1024).toLatin1().data());
        QCOMPARE(server.pendingDatagramSize(), qint64(i * 1024));
        QCOMPARE(server.readDatagram(0, 0), qint64(0));
    }
}

void tst_QUdpSocket::performance()
{
    QByteArray arr(8192, '@');

    QUdpSocket server;
    QVERIFY2(server.bind(), server.errorString().toLatin1().constData());

    QHostAddress serverAddress = makeNonAny(server.localAddress());
    QUdpSocket client;
    client.connectToHost(serverAddress, server.localPort());
    QVERIFY(client.waitForConnected(10000));

    QElapsedTimer 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);
}

void tst_QUdpSocket::bindMode()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy) {
#ifndef QT_NO_NETWORKPROXY
        QFETCH_GLOBAL(int, proxyType);
        if (proxyType == QNetworkProxy::Socks5Proxy)
            QSKIP("With socks5 explicit port binding is not supported.");
#else // !QT_NO_NETWORKPROXY
        QSKIP("No proxy support");
#endif // QT_NO_NETWORKPROXY
    }

    QUdpSocket socket;
    QVERIFY2(socket.bind(), socket.errorString().toLatin1().constData());
    QUdpSocket socket2;
    QVERIFY(!socket2.bind(socket.localPort()));
#if 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)) {
        qWarning("Failed to bind with QUdpSocket::ReuseAddressHint(%s), user isn't an administrator?",
                 qPrintable(socket2.errorString()));
    }
    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);
    QList<QHostAddress> serverAddresses(QHostInfo::fromName(QtNetworkSettings::socksProxyServerName()).addresses());
    if (serverAddresses.isEmpty())
        return;

    QHostAddress remote = serverAddresses.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()
{
    if (QHostInfo::fromName(QtNetworkSettings::socksProxyServerName()).addresses().isEmpty())
        QFAIL("Could not find test server address");
    QFETCH(bool, bind);
    QFETCH(QHostAddress, peerAddress);

    quint16 peerPort = 33533 + int(bind);

    QUdpSocket sUdp;
    QSignalSpy sReadyReadSpy(&sUdp, SIGNAL(readyRead()));
    if (bind)
        QVERIFY(sUdp.bind());
    QCOMPARE(sUdp.writeDatagram("", 1, peerAddress, peerPort), qint64(1));
    QTestEventLoop::instance().enterLoop(1);
    QCOMPARE(sReadyReadSpy.size(), 0);
}

void tst_QUdpSocket::writeToNonExistingPeer_data()
{
    QTest::addColumn<QHostAddress>("peerAddress");
    QHostAddress localhost(QHostAddress::LocalHost);
    QList<QHostAddress> serverAddresses(QHostInfo::fromName(QtNetworkSettings::socksProxyServerName()).addresses());
    if (serverAddresses.isEmpty())
        return;

    QHostAddress remote = serverAddresses.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");
    if (QHostInfo::fromName(QtNetworkSettings::socksProxyServerName()).addresses().isEmpty())
        QFAIL("Could not find test server address");
    QFETCH(QHostAddress, peerAddress);
    quint16 peerPort = 34534;
    qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");

    QUdpSocket sConnected;
    QSignalSpy sConnectedReadyReadSpy(&sConnected, SIGNAL(readyRead()));
    QSignalSpy sConnectedErrorSpy(&sConnected, SIGNAL(errorOccurred(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.size(), 0);
    QCOMPARE(sConnectedErrorSpy.size(), 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 !QT_CONFIG(process)
    QSKIP("No qprocess support");
#else
    QProcess serverProcess;
    serverProcess.start(QLatin1String("clientserver/clientserver server 1 1"), {},
                        QIODevice::ReadWrite | QIODevice::Text);

    const auto serverProcessCleaner = qScopeGuard([&serverProcess] {
        serverProcess.kill();
        serverProcess.waitForFinished();
    });

    if (!serverProcess.waitForStarted(3000))
        QSKIP("Failed to start server as a subprocess");

    // Wait until the server has started and reports success.
    while (!serverProcess.canReadLine()) {
       if (!serverProcess.waitForReadyRead(3000))
           QSKIP("No output from the server process, bailing out");
    }

    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);

    const auto clientProcessCleaner = qScopeGuard([&clientProcess] {
        clientProcess.kill();
        clientProcess.waitForFinished();
    });

    if (!clientProcess.waitForStarted(3000))
        QSKIP("Client process did not start");

    // Wait until the client has started and reports success.
    while (!clientProcess.canReadLine()) {
        if (!clientProcess.waitForReadyRead(3000))
            QSKIP("No output from the client process, bailing out");
    }

    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);
    }
#endif
}

void tst_QUdpSocket::outOfProcessUnconnectedClientServerTest()
{
#if !QT_CONFIG(process)
    QSKIP("No qprocess support");
#else
    QProcess serverProcess;
    serverProcess.start(QLatin1String("clientserver/clientserver server 1 1"), {},
                        QIODevice::ReadWrite | QIODevice::Text);

    const auto serverProcessCleaner = qScopeGuard([&serverProcess] {
        serverProcess.kill();
        serverProcess.waitForFinished();
    });

    if (!serverProcess.waitForStarted(3000))
        QSKIP("Failed to start the server subprocess");

    // Wait until the server has started and reports success.
    while (!serverProcess.canReadLine()) {
        if (!serverProcess.waitForReadyRead(3000))
            QSKIP("No output from the server, probably, it is not running");
    }

    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);

    const auto clientProcessCleaner = qScopeGuard([&clientProcess] {
        clientProcess.kill();
        clientProcess.waitForFinished();
    });

    if (!clientProcess.waitForStarted(3000))
        QSKIP("Failed to start the client's subprocess");

    // Wait until the client has started and reports success.
    while (!clientProcess.canReadLine()) {
        if (!clientProcess.waitForReadyRead(3000))
            QSKIP("The client subprocess produced not output, exiting.");
    }

    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);
    }
#endif
}

void tst_QUdpSocket::zeroLengthDatagram()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        return;

    QUdpSocket receiver;
    QVERIFY(receiver.bind());

    QVERIFY(!receiver.waitForReadyRead(100));
    QVERIFY(!receiver.hasPendingDatagrams());

    QUdpSocket sender;
    QCOMPARE(sender.writeDatagram(QNetworkDatagram(QByteArray(), QHostAddress::LocalHost, receiver.localPort())), qint64(0));

    QVERIFY2(receiver.waitForReadyRead(1000), QtNetworkSettings::msgSocketError(receiver).constData());
    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");

    const QHostAddress addresses[] = {
        QHostAddress(QHostAddress::AnyIPv4),
        QHostAddress(QHostAddress::AnyIPv6),
    };

    for (const QHostAddress &address : addresses) {
        const QByteArray addressB = address.toString().toLatin1();
        QTest::newRow((addressB + " 0").constData()) << address << 0 << 0;
        QTest::newRow((addressB + " 1").constData()) << address << 1 << 1;
        QTest::newRow((addressB + " 2").constData()) << address << 2 << 2;
        QTest::newRow((addressB + " 128").constData()) << address << 128 << 128;
        QTest::newRow((addressB + " 255").constData()) << address << 255 << 255;
        QTest::newRow((addressB + " 1024").constData()) << 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;
    }

    // Some syscalls needed for ipv6 udp multicasting are not functional
    if (m_skipUnsupportedIPv6Tests) {
        if (bindAddress.protocol() == QAbstractSocket::IPv6Protocol) {
            QSKIP("Syscalls needed for ipv6 udp multicasting missing functionality");
        }
    }

    QUdpSocket udpSocket;
    // 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");

    const QHostAddress addresses[] = {
        QHostAddress(QHostAddress::AnyIPv4),
        QHostAddress(QHostAddress::AnyIPv6),
    };

    for (const QHostAddress &address : addresses) {
        const QByteArray addressB = address.toString().toLatin1();
        QTest::newRow((addressB + " 0").constData()) << address << 0 << 0;
        QTest::newRow((addressB + " 1").constData()) << address << 1 << 1;
        QTest::newRow((addressB + " 2").constData()) << address << 2 << 1;
        QTest::newRow((addressB + " 0 again").constData()) << address << 0 << 0;
        QTest::newRow((addressB + " 2 again").constData()) << address << 2 << 1;
        QTest::newRow((addressB + " 0 last time").constData()) << address << 0 << 0;
        QTest::newRow((addressB + " 1 again").constData()) << 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;
    }

    // Some syscalls needed for ipv6 udp multicasting are not functional
    if (m_skipUnsupportedIPv6Tests) {
        if (bindAddress.protocol() == QAbstractSocket::IPv6Protocol) {
            QSKIP("Syscalls needed for ipv6 udp multicasting missing functionality");
        }
    }

    QUdpSocket udpSocket;
    // 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") << multicastGroup4;
    QTest::newRow("invalid ipv4 group address") << QHostAddress(QHostAddress::Broadcast);
    QTest::newRow("valid ipv6 group address") << multicastGroup6;
    for (const QHostAddress &a : std::as_const(linklocalMulticastGroups))
        QTest::addRow("valid ipv6 %s-link group address", a.scopeId().toLatin1().constData()) << a;
    QTest::newRow("invalid ipv6 group address") << QHostAddress(QHostAddress::AnyIPv6);
}

void tst_QUdpSocket::multicastJoinBeforeBind()
{
    QFETCH(QHostAddress, groupAddress);

    QUdpSocket udpSocket;
    // 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("ipv4") << multicastGroup4;
    QTest::newRow("ipv6") << multicastGroup6;
    for (const QHostAddress &a : std::as_const(linklocalMulticastGroups))
        QTest::addRow("ipv6-link-%s", a.scopeId().toLatin1().constData()) << a;
}

void tst_QUdpSocket::multicastLeaveAfterClose()
{
    QFETCH_GLOBAL(bool, setProxy);
    QFETCH(QHostAddress, groupAddress);
    if (setProxy)
        QSKIP("UDP Multicast does not work with proxies");
    if (!QtNetworkSettings::hasIPv6() && groupAddress.protocol() == QAbstractSocket::IPv6Protocol)
        QSKIP("system doesn't support ipv6!");

    // Some syscalls needed for ipv6 udp multicasting are not functional
    if (m_skipUnsupportedIPv6Tests) {
        if (groupAddress.protocol() == QAbstractSocket::IPv6Protocol) {
            QSKIP("Syscalls needed for ipv6 udp multicasting missing functionality");
        }
    }

    QUdpSocket udpSocket;
    QHostAddress bindAddress = QHostAddress::AnyIPv4;
    if (groupAddress.protocol() == QAbstractSocket::IPv6Protocol)
        bindAddress = QHostAddress::AnyIPv6;
    QVERIFY2(udpSocket.bind(bindAddress, 0),
             qPrintable(udpSocket.errorString()));
    QVERIFY2(udpSocket.joinMulticastGroup(groupAddress, interfaceForGroup(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");
    const QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
    int added = 0;
    for (const QNetworkInterface &iface : interfaces) {
        if ((iface.flags() & QNetworkInterface::IsUp) == 0)
            continue;
        const auto entries = iface.addressEntries();
        for (const QNetworkAddressEntry &entry : entries) {
            const QByteArray testName = iface.name().toLatin1() + ':' + entry.ip().toString().toLatin1();
            QTest::newRow(testName.constData()) << iface << entry.ip();
            ++added;
        }
    }

    if (added == 0)
        QSKIP("QNetworkInterface returned no addresses on interfaces with IsUp, not even loopback. "
              "You may want to run tst_qnetworkinterface on your system.");
}

void tst_QUdpSocket::setMulticastInterface()
{
    QFETCH_GLOBAL(bool, setProxy);
    QFETCH(QNetworkInterface, iface);
    QFETCH(QHostAddress, address);

    // Some syscalls needed for udp multicasting are not functional
    if (m_skipUnsupportedIPv6Tests) {
        QSKIP("Syscalls needed for udp multicasting missing functionality");
    }

    QUdpSocket udpSocket;
    // bind initializes the socket
    bool bound = udpSocket.bind((address.protocol() == QAbstractSocket::IPv6Protocol
                                 ? QHostAddress(QHostAddress::AnyIPv6)
                                 : QHostAddress(QHostAddress::AnyIPv4)),
                                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());
    }
}

void tst_QUdpSocket::multicast_data()
{
    QHostAddress anyAddress = QHostAddress(QHostAddress::AnyIPv4);
    QHostAddress groupAddress = multicastGroup4;
    QHostAddress any6Address = QHostAddress(QHostAddress::AnyIPv6);
    QHostAddress group6Address = multicastGroup6;
    QHostAddress dualAddress = QHostAddress(QHostAddress::Any);

    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("valid bind, group ipv6 address") << any6Address << true << group6Address << true;
    for (const QHostAddress &a : std::as_const(linklocalMulticastGroups))
        QTest::addRow("valid bind, %s-link group ipv6 address", a.scopeId().toLatin1().constData())
                << any6Address << true << a << true;
    QTest::newRow("valid bind, invalid group ipv6 address") << any6Address << true << any6Address << false;
    QTest::newRow("dual bind, group ipv4 address") << dualAddress << true << groupAddress << false;
    QTest::newRow("dual bind, group ipv6 address") << dualAddress << true << group6Address << true;
    for (const QHostAddress &a : std::as_const(linklocalMulticastGroups))
        QTest::addRow("dual bind, %s-link group ipv6 address", a.scopeId().toLatin1().constData())
                << dualAddress << true << a << true;
}

void tst_QUdpSocket::multicast()
{
    QFETCH_GLOBAL(bool, setProxy);
    QFETCH(QHostAddress, bindAddress);
    QFETCH(bool, bindResult);
    QFETCH(QHostAddress, groupAddress);
    QFETCH(bool, joinResult);
    if (groupAddress.protocol() == QAbstractSocket::IPv6Protocol && !QtNetworkSettings::hasIPv6())
        QSKIP("system doesn't support ipv6!");
    if (setProxy) {
        // UDP multicast does not work with proxies
        return;
    }

    // Some syscalls needed for ipv6 udp multicasting are not functional
    if (m_skipUnsupportedIPv6Tests) {
        if (groupAddress.protocol() == QAbstractSocket::IPv6Protocol) {
            QSKIP("Syscalls needed for ipv6 udp multicasting missing functionality");
        }
    }

    QUdpSocket receiver;

    // bind first, then verify that we can join the multicast group
    QVERIFY2(receiver.bind(bindAddress, 0) == bindResult,
             qPrintable(receiver.errorString()));
    if (!bindResult)
        return;

    if (bindAddress == QHostAddress::Any && groupAddress.protocol() == QAbstractSocket::IPv4Protocol) {
        QCOMPARE(joinResult, false);
        QTest::ignoreMessage(QtWarningMsg,
                             "QAbstractSocket: cannot bind to QHostAddress::Any (or an IPv6 address) and join an IPv4 multicast group;"
                             " bind to QHostAddress::AnyIPv4 instead if you want to do this");
    }
    QVERIFY2(receiver.joinMulticastGroup(groupAddress, interfaceForGroup(groupAddress)) == joinResult,
             qPrintable(receiver.errorString()));
    if (!joinResult)
        return;

    const QByteArray datagrams[] = {
        "0123"_ba,
        "4567"_ba,
        "89ab"_ba,
        "cdef"_ba,
    };

    QUdpSocket sender;
    sender.bind();
    for (const QByteArray &datagram : datagrams) {
        QNetworkDatagram dgram(datagram, groupAddress, receiver.localPort());
        dgram.setInterfaceIndex(interfaceForGroup(groupAddress).index());
        QCOMPARE(int(sender.writeDatagram(dgram)),
                 int(datagram.size()));
    }

    QVERIFY2(receiver.waitForReadyRead(), QtNetworkSettings::msgSocketError(receiver).constData());
    QVERIFY(receiver.hasPendingDatagrams());
    QList<QByteArray> receivedDatagrams;
    while (receiver.hasPendingDatagrams()) {
        QNetworkDatagram dgram = receiver.receiveDatagram();
        receivedDatagrams << dgram.data();

        QVERIFY2(allAddresses.contains(dgram.senderAddress()),
                dgram.senderAddress().toString().toLatin1());
        QCOMPARE(dgram.senderPort(), int(sender.localPort()));
        if (HasWorkingIPv4DestinationAddress) {
            QCOMPARE(dgram.destinationAddress(), groupAddress);
        }
        QCOMPARE(dgram.destinationPort(), int(receiver.localPort()));

        int ttl = dgram.hopLimit();
        if (ttl != -1)
            QVERIFY(ttl != 0);
    }
    QCOMPARE(receivedDatagrams, datagrams);

    QVERIFY2(receiver.leaveMulticastGroup(groupAddress, interfaceForGroup(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::echoServerName());
    QVERIFY(info.addresses().size());
    QHostAddress remote = info.addresses().first();

    QUdpSocket sock;
    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.size(), &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
    }
    QVERIFY2(successes >= 9, QByteArray::number(successes).constData());
}

void tst_QUdpSocket::linkLocalIPv6()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        return;

    QList <QHostAddress> addresses;
    QSet <QString> scopes;
    QHostAddress localMask("fe80::");
    const auto ifaces = QNetworkInterface::allInterfaces();
    for (const QNetworkInterface &iface : ifaces) {
        //Windows preallocates link local addresses to interfaces that are down.
        //These may or may not work depending on network driver
        if (iface.flags() & QNetworkInterface::IsUp) {
#if defined(Q_OS_WIN)
            // Do not add the Teredo Tunneling Pseudo Interface on Windows.
            if (iface.humanReadableName().contains("Teredo"))
                continue;
#elif defined(Q_OS_DARWIN)
            // Do not add "utun" interfaces on macOS: nothing ever gets received
            // (we don't know why)
            if (iface.name().startsWith("utun"))
                continue;
#endif

            const auto entries = iface.addressEntries();
            for (const QNetworkAddressEntry &addressEntry : entries) {
                QHostAddress addr(addressEntry.ip());
                if (!addr.scopeId().isEmpty() && addr.isInSubnet(localMask, 64)) {
                    scopes << addr.scopeId();
                    addresses << addr;
                    qDebug() << addr;
                }
            }
        }
    }
    if (addresses.isEmpty())
        QSKIP("No IPv6 link local addresses");

    QList <QUdpSocket*> sockets;
    quint16 port = 0;
    for (const QHostAddress &addr : std::as_const(addresses)) {
        QUdpSocket *s = new QUdpSocket;
        QVERIFY2(s->bind(addr, port), addr.toString().toLatin1()
                 + '/' + QByteArray::number(port) + ": " + qPrintable(s->errorString()));
        port = s->localPort(); //bind same port, different networks
        sockets << s;
    }

    QByteArray testData("hello");
    for (QUdpSocket *s : std::as_const(sockets)) {
        QUdpSocket neutral;
        QVERIFY(neutral.bind(QHostAddress(QHostAddress::AnyIPv6)));
        QSignalSpy neutralReadSpy(&neutral, SIGNAL(readyRead()));

        QSignalSpy spy(s, SIGNAL(readyRead()));

        QVERIFY(s->writeDatagram(testData, s->localAddress(), neutral.localPort()));
        QTRY_VERIFY(neutralReadSpy.size() > 0); //note may need to accept a firewall prompt

        QNetworkDatagram dgram = neutral.receiveDatagram(testData.size() * 2);
        QVERIFY(dgram.isValid());
        QCOMPARE(dgram.senderAddress(), s->localAddress());
        QCOMPARE(dgram.senderPort(), int(s->localPort()));
        QCOMPARE(dgram.destinationAddress(), s->localAddress());
        QCOMPARE(dgram.destinationPort(), int(neutral.localPort()));
        QCOMPARE(dgram.data().size(), testData.size());
        QCOMPARE(dgram.data(), testData);

        QVERIFY(neutral.writeDatagram(dgram.makeReply(testData)));
        QTRY_VERIFY(spy.size() > 0); //note may need to accept a firewall prompt

        dgram = s->receiveDatagram(testData.size() * 2);
        QCOMPARE(dgram.data(), testData);

        //sockets bound to other interfaces shouldn't have received anything
        for (QUdpSocket *s2 : std::as_const(sockets))
            QCOMPARE((int)s2->bytesAvailable(), 0);

        //Sending to the same address with different scope should normally fail
        //However it will pass if there is a route between two interfaces,
        //e.g. connected to a home/office network via wired and wireless interfaces
        //which is a reasonably common case.
        //So this is not auto tested.
    }
    qDeleteAll(sockets);
}

void tst_QUdpSocket::linkLocalIPv4()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        return;

    QList <QHostAddress> addresses;
    QHostAddress localMask("169.254.0.0");
    const auto ifaces = QNetworkInterface::allInterfaces();
    for (const QNetworkInterface &iface : ifaces) {
        //Windows preallocates link local addresses to interfaces that are down.
        //These may or may not work depending on network driver (they do not work for the Bluetooth PAN driver)
        if (iface.flags() & QNetworkInterface::IsUp) {
#if defined(Q_OS_WIN)
            // Do not add the Teredo Tunneling Pseudo Interface on Windows.
            if (iface.humanReadableName().contains("Teredo"))
                continue;
#elif defined(Q_OS_DARWIN)
            // Do not add "utun" interfaces on macOS: nothing ever gets received
            // (we don't know why)
            if (iface.name().startsWith("utun"))
                continue;
#endif
            const auto entries = iface.addressEntries();
            for (const QNetworkAddressEntry &addr : entries) {
                if (addr.ip().isInSubnet(localMask, 16)) {
                    addresses << addr.ip();
                    qDebug() << "Found IPv4 link local address" << addr.ip();
                }
            }
        }
    }
    if (addresses.isEmpty())
        QSKIP("No IPv4 link local addresses");

    QList <QUdpSocket*> sockets;
    quint16 port = 0;
    for (const QHostAddress &addr : std::as_const(addresses)) {
        QUdpSocket *s = new QUdpSocket;
        QVERIFY2(s->bind(addr, port), qPrintable(s->errorString()));
        port = s->localPort(); //bind same port, different networks
        sockets << s;
    }

    QUdpSocket neutral;
    QVERIFY(neutral.bind(QHostAddress(QHostAddress::AnyIPv4)));

    QByteArray testData("hello");
    for (QUdpSocket *s : std::as_const(sockets)) {
        QVERIFY(s->writeDatagram(testData, s->localAddress(), neutral.localPort()));
        QVERIFY2(neutral.waitForReadyRead(10000), QtNetworkSettings::msgSocketError(neutral).constData());

        QNetworkDatagram dgram = neutral.receiveDatagram(testData.size() * 2);
        QVERIFY(dgram.isValid());
        QCOMPARE(dgram.senderAddress(), s->localAddress());
        QCOMPARE(dgram.senderPort(), int(s->localPort()));
        QCOMPARE(dgram.data().size(), testData.size());
        QCOMPARE(dgram.data(), testData);

        if (!HasWorkingIPv4DestinationAddress) {
            QCOMPARE(dgram.destinationAddress().protocol(), QAbstractSocket::UnknownNetworkLayerProtocol);
        } else {
            QCOMPARE(dgram.destinationAddress(), s->localAddress());
        }
        QCOMPARE(dgram.destinationPort(), int(neutral.localPort()));

        QVERIFY(neutral.writeDatagram(dgram.makeReply(testData)));
        QVERIFY2(s->waitForReadyRead(10000), QtNetworkSettings::msgSocketError(*s).constData());
        dgram = s->receiveDatagram(testData.size() * 2);
        QVERIFY(dgram.isValid());
        QCOMPARE(dgram.data(), testData);

        //sockets bound to other interfaces shouldn't have received anything
        for (QUdpSocket *s2 : std::as_const(sockets))
            QCOMPARE((int)s2->bytesAvailable(), 0);
    }
    qDeleteAll(sockets);
}

void tst_QUdpSocket::readyRead()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        return;

    char buf[1];
    QUdpSocket sender, receiver;

    QVERIFY(receiver.bind(QHostAddress(QHostAddress::AnyIPv4), 0));
    quint16 port = receiver.localPort();
    QVERIFY(port != 0);

    QSignalSpy spy(&receiver, SIGNAL(readyRead()));

    // send a datagram to that port
    sender.writeDatagram("aa", makeNonAny(receiver.localAddress()), port);

    // wait a little
    // if QTBUG-43857 is still going, we'll live-lock on socket notifications from receiver's socket
    QTest::qWait(100);

    // make sure only one signal was emitted
    QCOMPARE(spy.size(), 1);
    QVERIFY(receiver.hasPendingDatagrams());
#ifdef RELIABLE_BYTES_AVAILABLE
    QCOMPARE(receiver.bytesAvailable(), qint64(2));
#endif
    QCOMPARE(receiver.pendingDatagramSize(), qint64(2));

    // write another datagram
    sender.writeDatagram("ab", makeNonAny(receiver.localAddress()), port);

    // no new signal should be emitted because we haven't read the first datagram yet
    QTest::qWait(100);
    QCOMPARE(spy.size(), 1);
    QVERIFY(receiver.hasPendingDatagrams());
    QVERIFY(receiver.bytesAvailable() >= 1);    // most likely is 1, but it could be 1 + 2 in the future
    QCOMPARE(receiver.pendingDatagramSize(), qint64(2));

    // read all the datagrams (we could read one only, but we can't be sure the OS is queueing)
    while (receiver.hasPendingDatagrams())
        receiver.readDatagram(buf, sizeof buf);

    // write a new datagram and ensure the signal is emitted now
    sender.writeDatagram("abc", makeNonAny(receiver.localAddress()), port);
    QTest::qWait(100);
    QCOMPARE(spy.size(), 2);
    QVERIFY(receiver.hasPendingDatagrams());
#ifdef RELIABLE_BYTES_AVAILABLE
    QCOMPARE(receiver.bytesAvailable(), qint64(3));
#endif
    QCOMPARE(receiver.pendingDatagramSize(), qint64(3));
}

void tst_QUdpSocket::readyReadForEmptyDatagram()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        return;

    QUdpSocket sender, receiver;

    QVERIFY(receiver.bind(QHostAddress(QHostAddress::AnyIPv4), 0));
    quint16 port = receiver.localPort();
    QVERIFY(port != 0);

    connect(&receiver, SIGNAL(readyRead()), SLOT(empty_readyReadSlot()));

    // send an empty datagram to that port
    sender.writeDatagram("", makeNonAny(receiver.localAddress()), port);

    // ensure that we got a readyRead, despite bytesAvailable() == 0
    QTestEventLoop::instance().enterLoop(1);
    QVERIFY(!QTestEventLoop::instance().timeout());

    char buf[1];
    QVERIFY(receiver.hasPendingDatagrams());
    QCOMPARE(receiver.pendingDatagramSize(), qint64(0));
#ifdef RELIABLE_BYTES_AVAILABLE
    QCOMPARE(receiver.bytesAvailable(), qint64(0));
#endif
    QCOMPARE(receiver.readDatagram(buf, sizeof buf), qint64(0));
}

void tst_QUdpSocket::async_readDatagramSlot()
{
    char buf[1];
    QVERIFY(m_asyncReceiver->hasPendingDatagrams());
    QCOMPARE(m_asyncReceiver->pendingDatagramSize(), qint64(1));
#ifdef RELIABLE_BYTES_AVAILABLE
    QCOMPARE(m_asyncReceiver->bytesAvailable(), qint64(1));
#endif
    QCOMPARE(m_asyncReceiver->readDatagram(buf, sizeof(buf)), qint64(1));

    if (buf[0] == '2') {
        QTestEventLoop::instance().exitLoop();
        return;
    }

    m_asyncSender->writeDatagram("2", makeNonAny(m_asyncReceiver->localAddress()), m_asyncReceiver->localPort());
    // wait a little to ensure that the datagram we've just sent
    // will be delivered on receiver side.
    QTest::qSleep(100);
}

void tst_QUdpSocket::asyncReadDatagram()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        return;

    m_asyncSender = new QUdpSocket;
    m_asyncReceiver = new QUdpSocket;

    QVERIFY(m_asyncReceiver->bind(QHostAddress(QHostAddress::AnyIPv4), 0));
    quint16 port = m_asyncReceiver->localPort();
    QVERIFY(port != 0);

    QSignalSpy spy(m_asyncReceiver, SIGNAL(readyRead()));
    connect(m_asyncReceiver, SIGNAL(readyRead()), SLOT(async_readDatagramSlot()));

    m_asyncSender->writeDatagram("1", makeNonAny(m_asyncReceiver->localAddress()), port);

    QTestEventLoop::instance().enterLoop(1);

    QVERIFY(!QTestEventLoop::instance().timeout());
    QCOMPARE(spy.size(), 2);

    delete m_asyncSender;
    delete m_asyncReceiver;
}

void tst_QUdpSocket::writeInHostLookupState()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        return;

    QUdpSocket socket;
    socket.connectToHost("nosuchserver.qt-project.org", 80);
    QCOMPARE(socket.state(), QUdpSocket::HostLookupState);
    QVERIFY(!socket.putChar('0'));
}

void tst_QUdpSocket::readyReadConnectionThrottling()
{
    QFETCH_GLOBAL(bool, setProxy);
    if (setProxy)
        return;
    using namespace std::chrono_literals;

    // QTBUG-105871:
    // We have some signal/slot connection throttling in QAbstractSocket, but it
    // was caring about the bytes, not about the datagrams.
    // Test that we don't disable read notifications until we have at least one
    // datagram available. Otherwise our good users who use the datagram APIs
    // can get into scenarios where they no longer get the readyRead signal
    // unless they call a read function once in a while.

    QUdpSocket receiver;
    QVERIFY(receiver.bind(QHostAddress(QHostAddress::LocalHost), 0));

    QLatch latch(1);

    // Repro-ing deterministically eludes me, so we are bruteforcing it:
    // The thread acts as a remote sender, flooding the receiver with datagrams,
    // and at some point the receiver would get into the broken state mentioned
    // earlier.
    std::unique_ptr<QThread> thread(QThread::create([&latch, port = receiver.localPort()]() {
        QUdpSocket sender;
        sender.connectToHost(QHostAddress(QHostAddress::LocalHost), port);
        QCOMPARE(sender.state(), QUdpSocket::ConnectedState);

        constexpr qsizetype PayloadSize = 242;
        const QByteArray payload(PayloadSize, 'a');

        latch.wait(); // Wait for main thread to be ready
        while (true) {
            // We send 100 datagrams at a time, then sleep.
            // This is mostly to let the main thread catch up between bursts so
            // it doesn't get stuck in the loop.
            for (int i = 0; i < 100; ++i) {
                [[maybe_unused]]
                qsizetype sent = sender.write(payload);
                Q_ASSERT(sent > 0);
            }
            if (QThread::currentThread()->isInterruptionRequested())
                break;
            QThread::sleep(20ms);
        }
    }));
    thread->start();
    auto threadStopAndWaitGuard = qScopeGuard([&thread] {
        thread->requestInterruption();
        thread->quit();
        thread->wait();
    });

    qsizetype count = 0;
    QObject::connect(&receiver, &QUdpSocket::readyRead, &receiver,
            [&] {
                while (receiver.hasPendingDatagrams()) {
                    receiver.readDatagram(nullptr, 0);
                    ++count;
                }
                // If this prints `false, xxxx` we were pretty much guaranteed
                // that we would not get called again:
                // qDebug() << receiver.hasPendingDatagrams() << receiver.bytesAvailable();
            },
            Qt::QueuedConnection);

    latch.countDown();
    constexpr qsizetype MaxCount = 500;
    QVERIFY2(QTest::qWaitFor([&] { return count >= MaxCount; }, 10s),
             QByteArray::number(count).constData());
}

QTEST_MAIN(tst_QUdpSocket)
#include "tst_qudpsocket.moc"
