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

//#define QNATIVESOCKETENGINE_DEBUG
#include "qnativesocketengine_p_p.h"
#include "private/qnet_unix_p.h"
#include "qiodevice.h"
#include "qhostaddress.h"
#include "qelapsedtimer.h"
#include "qvarlengtharray.h"
#include "qnetworkinterface.h"
#include "qendian.h"
#ifdef Q_OS_WASM
#include <private/qeventdispatcher_wasm_p.h>
#endif
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#ifndef QT_NO_IPV6IFNAME
#ifdef Q_OS_LINUX
#include <linux/if.h>
#else // Q_OS_LINUX
#include <net/if.h>
#endif // !Q_OS_LINUX
#endif
#ifdef QT_LINUXBASE
#include <arpa/inet.h>
#endif
#ifdef Q_OS_BSD4
#include <net/if_dl.h>
#endif
#ifdef Q_OS_INTEGRITY
#include <sys/uio.h>
#endif

#if defined QNATIVESOCKETENGINE_DEBUG
#include <private/qdebug_p.h>
#endif

#include <netinet/tcp.h>
#ifndef QT_NO_SCTP
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/sctp.h>
#endif
#ifdef Q_OS_BSD4
#  include <net/if_dl.h>
#endif

QT_BEGIN_NAMESPACE

/*
    Extracts the port and address from a sockaddr, and stores them in
    \a port and \a addr if they are non-null.
*/
static inline void qt_socket_getPortAndAddress(const qt_sockaddr *s, quint16 *port, QHostAddress *addr)
{
    if (s->a.sa_family == AF_INET6) {
        Q_IPV6ADDR tmp;
        memcpy(&tmp, &s->a6.sin6_addr, sizeof(tmp));
        if (addr) {
            QHostAddress tmpAddress;
            tmpAddress.setAddress(tmp);
            *addr = tmpAddress;
#if QT_CONFIG(networkinterface)
            if (s->a6.sin6_scope_id)
                addr->setScopeId(QNetworkInterface::interfaceNameFromIndex(s->a6.sin6_scope_id));
#endif
        }
        if (port)
            *port = ntohs(s->a6.sin6_port);
        return;
    }

    if (port)
        *port = ntohs(s->a4.sin_port);
    if (addr) {
        QHostAddress tmpAddress;
        tmpAddress.setAddress(ntohl(s->a4.sin_addr.s_addr));
        *addr = tmpAddress;
    }
}

static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt,
                                    QAbstractSocket::NetworkLayerProtocol socketProtocol, int &level, int &n)
{
    n = -1;
    level = SOL_SOCKET; // default

    switch (opt) {
    case QNativeSocketEngine::NonBlockingSocketOption:  // fcntl, not setsockopt
    case QNativeSocketEngine::BindExclusively:          // not handled on Unix
    case QNativeSocketEngine::MaxStreamsSocketOption:
        Q_UNREACHABLE();

    case QNativeSocketEngine::BroadcastSocketOption:
        n = SO_BROADCAST;
        break;
    case QNativeSocketEngine::ReceiveBufferSocketOption:
        n = SO_RCVBUF;
        break;
    case QNativeSocketEngine::SendBufferSocketOption:
        n = SO_SNDBUF;
        break;
    case QNativeSocketEngine::AddressReusable:
        n = SO_REUSEADDR;
        break;
    case QNativeSocketEngine::ReceiveOutOfBandData:
        n = SO_OOBINLINE;
        break;
    case QNativeSocketEngine::LowDelayOption:
        level = IPPROTO_TCP;
        n = TCP_NODELAY;
        break;
    case QNativeSocketEngine::KeepAliveOption:
        n = SO_KEEPALIVE;
        break;
    case QNativeSocketEngine::MulticastTtlOption:
        if (socketProtocol == QAbstractSocket::IPv6Protocol || socketProtocol == QAbstractSocket::AnyIPProtocol) {
            level = IPPROTO_IPV6;
            n = IPV6_MULTICAST_HOPS;
        } else
        {
            level = IPPROTO_IP;
            n = IP_MULTICAST_TTL;
        }
        break;
    case QNativeSocketEngine::MulticastLoopbackOption:
        if (socketProtocol == QAbstractSocket::IPv6Protocol || socketProtocol == QAbstractSocket::AnyIPProtocol) {
            level = IPPROTO_IPV6;
            n = IPV6_MULTICAST_LOOP;
        } else
        {
            level = IPPROTO_IP;
            n = IP_MULTICAST_LOOP;
        }
        break;
    case QNativeSocketEngine::TypeOfServiceOption:
        if (socketProtocol == QAbstractSocket::IPv4Protocol) {
            level = IPPROTO_IP;
            n = IP_TOS;
        }
        break;
    case QNativeSocketEngine::ReceivePacketInformation:
        if (socketProtocol == QAbstractSocket::IPv6Protocol || socketProtocol == QAbstractSocket::AnyIPProtocol) {
            level = IPPROTO_IPV6;
            n = IPV6_RECVPKTINFO;
        } else if (socketProtocol == QAbstractSocket::IPv4Protocol) {
            level = IPPROTO_IP;
#ifdef IP_PKTINFO
            n = IP_PKTINFO;
#elif defined(IP_RECVDSTADDR)
            // variant found in QNX and FreeBSD; it will get us only the
            // destination address, not the interface; we need IP_RECVIF for that.
            n = IP_RECVDSTADDR;
#endif
        }
        break;
    case QNativeSocketEngine::ReceiveHopLimit:
        if (socketProtocol == QAbstractSocket::IPv6Protocol || socketProtocol == QAbstractSocket::AnyIPProtocol) {
            level = IPPROTO_IPV6;
            n = IPV6_RECVHOPLIMIT;
        } else if (socketProtocol == QAbstractSocket::IPv4Protocol) {
#ifdef IP_RECVTTL               // IP_RECVTTL is a non-standard extension supported on some OS
            level = IPPROTO_IP;
            n = IP_RECVTTL;
#endif
        }
        break;

    case QNativeSocketEngine::PathMtuInformation:
        if (socketProtocol == QAbstractSocket::IPv6Protocol || socketProtocol == QAbstractSocket::AnyIPProtocol) {
#ifdef IPV6_MTU
            level = IPPROTO_IPV6;
            n = IPV6_MTU;
#endif
        } else {
#ifdef IP_MTU
            level = IPPROTO_IP;
            n = IP_MTU;
#endif
        }
        break;
    }
}

/*! \internal

    Creates and returns a new socket descriptor of type \a socketType
    and \a socketProtocol.  Returns -1 on failure.
*/
bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType,
                                         QAbstractSocket::NetworkLayerProtocol &socketProtocol)
{
#ifndef QT_NO_SCTP
    int protocol = (socketType == QAbstractSocket::SctpSocket) ? IPPROTO_SCTP : 0;
#else
    if (socketType == QAbstractSocket::SctpSocket) {
        setError(QAbstractSocket::UnsupportedSocketOperationError,
                 ProtocolUnsupportedErrorString);
#if defined (QNATIVESOCKETENGINE_DEBUG)
        qDebug("QNativeSocketEnginePrivate::createNewSocket(%d, %d): unsupported protocol",
               socketType, socketProtocol);
#endif
        return false;
    }
    int protocol = 0;
#endif // QT_NO_SCTP
    int domain = (socketProtocol == QAbstractSocket::IPv6Protocol
                  || socketProtocol == QAbstractSocket::AnyIPProtocol) ? AF_INET6 : AF_INET;
    int type = (socketType == QAbstractSocket::UdpSocket) ? SOCK_DGRAM : SOCK_STREAM;

    int socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK);
    if (socket < 0 && socketProtocol == QAbstractSocket::AnyIPProtocol && errno == EAFNOSUPPORT) {
        domain = AF_INET;
        socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK);
        socketProtocol = QAbstractSocket::IPv4Protocol;
    }

    if (socket < 0) {
        int ecopy = errno;
        switch (ecopy) {
        case EPROTONOSUPPORT:
        case EAFNOSUPPORT:
        case EINVAL:
            setError(QAbstractSocket::UnsupportedSocketOperationError, ProtocolUnsupportedErrorString);
            break;
        case ENFILE:
        case EMFILE:
        case ENOBUFS:
        case ENOMEM:
            setError(QAbstractSocket::SocketResourceError, ResourceErrorString);
            break;
        case EACCES:
            setError(QAbstractSocket::SocketAccessError, AccessErrorString);
            break;
        default:
            break;
        }

#if defined (QNATIVESOCKETENGINE_DEBUG)
        qDebug("QNativeSocketEnginePrivate::createNewSocket(%d, %d) == false (%s)",
               socketType, socketProtocol,
               strerror(ecopy));
#endif

        return false;
    }

#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEnginePrivate::createNewSocket(%d, %d) == true",
           socketType, socketProtocol);
#endif

    socketDescriptor = socket;
    this->socketProtocol = socketProtocol;
    this->socketType = socketType;
    return true;
}

/*
    Returns the value of the socket option \a opt.
*/
int QNativeSocketEnginePrivate::option(QNativeSocketEngine::SocketOption opt) const
{
    Q_Q(const QNativeSocketEngine);
    if (!q->isValid())
        return -1;

    // handle non-getsockopt and specific cases first
    switch (opt) {
    case QNativeSocketEngine::BindExclusively:
    case QNativeSocketEngine::NonBlockingSocketOption:
    case QNativeSocketEngine::BroadcastSocketOption:
        return -1;
    case QNativeSocketEngine::MaxStreamsSocketOption: {
#ifndef QT_NO_SCTP
        sctp_initmsg sctpInitMsg;
        QT_SOCKOPTLEN_T sctpInitMsgSize = sizeof(sctpInitMsg);
        if (::getsockopt(socketDescriptor, SOL_SCTP, SCTP_INITMSG, &sctpInitMsg,
                         &sctpInitMsgSize) == 0)
            return int(qMin(sctpInitMsg.sinit_num_ostreams, sctpInitMsg.sinit_max_instreams));
#endif
        return -1;
    }

    case QNativeSocketEngine::PathMtuInformation:
#if defined(IPV6_PATHMTU) && !defined(IPV6_MTU)
        // Prefer IPV6_MTU (handled by convertToLevelAndOption), if available
        // (Linux); fall back to IPV6_PATHMTU otherwise (FreeBSD):
        if (socketProtocol == QAbstractSocket::IPv6Protocol) {
            ip6_mtuinfo mtuinfo;
            QT_SOCKOPTLEN_T len = sizeof(mtuinfo);
            if (::getsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &len) == 0)
                return int(mtuinfo.ip6m_mtu);
            return -1;
        }
#endif
        break;

    default:
        break;
    }

    int n, level;
    int v = 0;
    QT_SOCKOPTLEN_T len = sizeof(v);

    convertToLevelAndOption(opt, socketProtocol, level, n);
    if (n != -1 && ::getsockopt(socketDescriptor, level, n, (char *) &v, &len) != -1)
        return len == 1 ? qFromUnaligned<quint8>(&v) : v;

    return -1;
}


/*
    Sets the socket option \a opt to \a v.
*/
bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt, int v)
{
    Q_Q(QNativeSocketEngine);
    if (!q->isValid())
        return false;

    // handle non-setsockopt and specific cases first
    switch (opt) {
    case QNativeSocketEngine::NonBlockingSocketOption: {
        // Make the socket nonblocking.
#if !defined(Q_OS_VXWORKS)
        int flags = ::fcntl(socketDescriptor, F_GETFL, 0);
        if (flags == -1) {
#ifdef QNATIVESOCKETENGINE_DEBUG
            perror("QNativeSocketEnginePrivate::setOption(): fcntl(F_GETFL) failed");
#endif
            return false;
        }
        if (::fcntl(socketDescriptor, F_SETFL, flags | O_NONBLOCK) == -1) {
#ifdef QNATIVESOCKETENGINE_DEBUG
            perror("QNativeSocketEnginePrivate::setOption(): fcntl(F_SETFL) failed");
#endif
            return false;
        }
#else // Q_OS_VXWORKS
        int onoff = 1;

        if (qt_safe_ioctl(socketDescriptor, FIONBIO, &onoff) < 0) {

#ifdef QNATIVESOCKETENGINE_DEBUG
            perror("QNativeSocketEnginePrivate::setOption(): ioctl(FIONBIO, 1) failed");
#endif
            return false;
        }
#endif // Q_OS_VXWORKS
        return true;
    }
    case QNativeSocketEngine::BindExclusively:
        return true;

    case QNativeSocketEngine::MaxStreamsSocketOption: {
#ifndef QT_NO_SCTP
        sctp_initmsg sctpInitMsg;
        QT_SOCKOPTLEN_T sctpInitMsgSize = sizeof(sctpInitMsg);
        if (::getsockopt(socketDescriptor, SOL_SCTP, SCTP_INITMSG, &sctpInitMsg,
                         &sctpInitMsgSize) == 0) {
            sctpInitMsg.sinit_num_ostreams = sctpInitMsg.sinit_max_instreams = uint16_t(v);
            return ::setsockopt(socketDescriptor, SOL_SCTP, SCTP_INITMSG, &sctpInitMsg,
                                sctpInitMsgSize) == 0;
        }
#endif
        return false;
    }

    default:
        break;
    }

    int n, level;
    convertToLevelAndOption(opt, socketProtocol, level, n);
#if defined(SO_REUSEPORT) && !defined(Q_OS_LINUX)
    if (opt == QNativeSocketEngine::AddressReusable) {
        // on OS X, SO_REUSEADDR isn't sufficient to allow multiple binds to the
        // same port (which is useful for multicast UDP). SO_REUSEPORT is, but
        // we most definitely do not want to use this for TCP. See QTBUG-6305.
        if (socketType == QAbstractSocket::UdpSocket)
            n = SO_REUSEPORT;
    }
#endif

    if (n == -1)
        return false;
    return ::setsockopt(socketDescriptor, level, n, (char *) &v, sizeof(v)) == 0;
}

bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &addr, quint16 port)
{
#ifdef QNATIVESOCKETENGINE_DEBUG
    qDebug() << "QNativeSocketEnginePrivate::nativeConnect() " << socketDescriptor;
#endif

    qt_sockaddr aa;
    QT_SOCKLEN_T sockAddrSize;
    setPortAndAddress(port, addr, &aa, &sockAddrSize);

    int connectResult = qt_safe_connect(socketDescriptor, &aa.a, sockAddrSize);
#if defined (QNATIVESOCKETENGINE_DEBUG)
    int ecopy = errno;
#endif
    if (connectResult == -1) {
        switch (errno) {
        case EISCONN:
            socketState = QAbstractSocket::ConnectedState;
            break;
        case ECONNREFUSED:
        case EINVAL:
            setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString);
            socketState = QAbstractSocket::UnconnectedState;
            break;
        case ETIMEDOUT:
            setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString);
            break;
        case EHOSTUNREACH:
            setError(QAbstractSocket::NetworkError, HostUnreachableErrorString);
            socketState = QAbstractSocket::UnconnectedState;
            break;
        case ENETUNREACH:
            setError(QAbstractSocket::NetworkError, NetworkUnreachableErrorString);
            socketState = QAbstractSocket::UnconnectedState;
            break;
        case EADDRINUSE:
            setError(QAbstractSocket::NetworkError, AddressInuseErrorString);
            break;
        case EINPROGRESS:
        case EALREADY:
            setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString);
            socketState = QAbstractSocket::ConnectingState;
            break;
        case EAGAIN:
            setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString);
            break;
        case EACCES:
        case EPERM:
            setError(QAbstractSocket::SocketAccessError, AccessErrorString);
            socketState = QAbstractSocket::UnconnectedState;
            break;
        case EAFNOSUPPORT:
        case EBADF:
        case EFAULT:
        case ENOTSOCK:
            socketState = QAbstractSocket::UnconnectedState;
        default:
            break;
        }

        if (socketState != QAbstractSocket::ConnectedState) {
#if defined (QNATIVESOCKETENGINE_DEBUG)
            qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == false (%s)",
                   addr.toString().toLatin1().constData(), port,
                   socketState == QAbstractSocket::ConnectingState
                   ? "Connection in progress" : strerror(ecopy));
#endif
            return false;
        }
    }

#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == true",
           addr.toString().toLatin1().constData(), port);
#endif

    socketState = QAbstractSocket::ConnectedState;
    return true;
}

bool QNativeSocketEnginePrivate::nativeBind(const QHostAddress &address, quint16 port)
{
    qt_sockaddr aa;
    QT_SOCKLEN_T sockAddrSize;
    setPortAndAddress(port, address, &aa, &sockAddrSize);

#ifdef IPV6_V6ONLY
    if (aa.a.sa_family == AF_INET6) {
        int ipv6only = 0;
        if (address.protocol() == QAbstractSocket::IPv6Protocol)
            ipv6only = 1;
        //default value of this socket option varies depending on unix variant (or system configuration on BSD), so always set it explicitly
        ::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, sizeof(ipv6only) );
    }
#endif

    int bindResult = QT_SOCKET_BIND(socketDescriptor, &aa.a, sockAddrSize);
    if (bindResult < 0 && errno == EAFNOSUPPORT && address.protocol() == QAbstractSocket::AnyIPProtocol) {
        // retry with v4
        aa.a4.sin_family = AF_INET;
        aa.a4.sin_port = htons(port);
        aa.a4.sin_addr.s_addr = htonl(address.toIPv4Address());
        sockAddrSize = sizeof(aa.a4);
        bindResult = QT_SOCKET_BIND(socketDescriptor, &aa.a, sockAddrSize);
    }

    if (bindResult < 0) {
#if defined (QNATIVESOCKETENGINE_DEBUG)
        int ecopy = errno;
#endif
        switch(errno) {
        case EADDRINUSE:
            setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString);
            break;
        case EACCES:
            setError(QAbstractSocket::SocketAccessError, AddressProtectedErrorString);
            break;
        case EINVAL:
            setError(QAbstractSocket::UnsupportedSocketOperationError, OperationUnsupportedErrorString);
            break;
        case EADDRNOTAVAIL:
            setError(QAbstractSocket::SocketAddressNotAvailableError, AddressNotAvailableErrorString);
            break;
        default:
            break;
        }

#if defined (QNATIVESOCKETENGINE_DEBUG)
        qDebug("QNativeSocketEnginePrivate::nativeBind(%s, %i) == false (%s)",
               address.toString().toLatin1().constData(), port, strerror(ecopy));
#endif

        return false;
    }

#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEnginePrivate::nativeBind(%s, %i) == true",
           address.toString().toLatin1().constData(), port);
#endif
    socketState = QAbstractSocket::BoundState;
    return true;
}

bool QNativeSocketEnginePrivate::nativeListen(int backlog)
{
    if (qt_safe_listen(socketDescriptor, backlog) < 0) {
#if defined (QNATIVESOCKETENGINE_DEBUG)
        int ecopy = errno;
#endif
        switch (errno) {
        case EADDRINUSE:
            setError(QAbstractSocket::AddressInUseError,
                     PortInuseErrorString);
            break;
        default:
            break;
        }

#if defined (QNATIVESOCKETENGINE_DEBUG)
        qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == false (%s)",
               backlog, strerror(ecopy));
#endif
        return false;
    }

#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == true", backlog);
#endif

    socketState = QAbstractSocket::ListeningState;
    return true;
}

qintptr QNativeSocketEnginePrivate::nativeAccept()
{
    int acceptedDescriptor = qt_safe_accept(socketDescriptor, nullptr, nullptr);
    if (acceptedDescriptor == -1) {
        switch (errno) {
        case EBADF:
        case EOPNOTSUPP:
            setError(QAbstractSocket::UnsupportedSocketOperationError, InvalidSocketErrorString);
            break;
        case ECONNABORTED:
            setError(QAbstractSocket::NetworkError, RemoteHostClosedErrorString);
            break;
        case EFAULT:
        case ENOTSOCK:
            setError(QAbstractSocket::SocketResourceError, NotSocketErrorString);
            break;
        case EPROTONOSUPPORT:
#if !defined(Q_OS_OPENBSD)
        case EPROTO:
#endif
        case EAFNOSUPPORT:
        case EINVAL:
            setError(QAbstractSocket::UnsupportedSocketOperationError, ProtocolUnsupportedErrorString);
            break;
        case ENFILE:
        case EMFILE:
        case ENOBUFS:
        case ENOMEM:
            setError(QAbstractSocket::SocketResourceError, ResourceErrorString);
            break;
        case EACCES:
        case EPERM:
            setError(QAbstractSocket::SocketAccessError, AccessErrorString);
            break;
#if EAGAIN != EWOULDBLOCK
        case EWOULDBLOCK:
#endif
        case EAGAIN:
            setError(QAbstractSocket::TemporaryError, TemporaryErrorString);
            break;
        default:
            setError(QAbstractSocket::UnknownSocketError, UnknownSocketErrorString);
            break;
        }
    }

    return qintptr(acceptedDescriptor);
}

#ifndef QT_NO_NETWORKINTERFACE

static bool multicastMembershipHelper(QNativeSocketEnginePrivate *d,
                                      int how6,
                                      int how4,
                                      const QHostAddress &groupAddress,
                                      const QNetworkInterface &interface)
{
    int level = 0;
    int sockOpt = 0;
    void *sockArg;
    int sockArgSize;

    ip_mreq mreq4;
    ipv6_mreq mreq6;

    if (groupAddress.protocol() == QAbstractSocket::IPv6Protocol) {
        level = IPPROTO_IPV6;
        sockOpt = how6;
        sockArg = &mreq6;
        sockArgSize = sizeof(mreq6);
        memset(&mreq6, 0, sizeof(mreq6));
        Q_IPV6ADDR ip6 = groupAddress.toIPv6Address();
        memcpy(&mreq6.ipv6mr_multiaddr, &ip6, sizeof(ip6));
        mreq6.ipv6mr_interface = interface.index();
    } else if (groupAddress.protocol() == QAbstractSocket::IPv4Protocol) {
        level = IPPROTO_IP;
        sockOpt = how4;
        sockArg = &mreq4;
        sockArgSize = sizeof(mreq4);
        memset(&mreq4, 0, sizeof(mreq4));
        mreq4.imr_multiaddr.s_addr = htonl(groupAddress.toIPv4Address());

        if (interface.isValid()) {
            const QList<QNetworkAddressEntry> addressEntries = interface.addressEntries();
            bool found = false;
            for (const QNetworkAddressEntry &entry : addressEntries) {
                const QHostAddress ip = entry.ip();
                if (ip.protocol() == QAbstractSocket::IPv4Protocol) {
                    mreq4.imr_interface.s_addr = htonl(ip.toIPv4Address());
                    found = true;
                    break;
                }
            }
            if (!found) {
                d->setError(QAbstractSocket::NetworkError,
                            QNativeSocketEnginePrivate::NetworkUnreachableErrorString);
                return false;
            }
        } else {
            mreq4.imr_interface.s_addr = INADDR_ANY;
        }
    } else {
        // unreachable
        d->setError(QAbstractSocket::UnsupportedSocketOperationError,
                    QNativeSocketEnginePrivate::ProtocolUnsupportedErrorString);
        return false;
    }

    int res = setsockopt(d->socketDescriptor, level, sockOpt, sockArg, sockArgSize);
    if (res == -1) {
        switch (errno) {
        case ENOPROTOOPT:
            d->setError(QAbstractSocket::UnsupportedSocketOperationError,
                        QNativeSocketEnginePrivate::OperationUnsupportedErrorString);
            break;
        case EADDRNOTAVAIL:
            d->setError(QAbstractSocket::SocketAddressNotAvailableError,
                        QNativeSocketEnginePrivate::AddressNotAvailableErrorString);
            break;
        default:
            d->setError(QAbstractSocket::UnknownSocketError,
                        QNativeSocketEnginePrivate::UnknownSocketErrorString);
            break;
        }
        return false;
    }
    return true;
}

bool QNativeSocketEnginePrivate::nativeJoinMulticastGroup(const QHostAddress &groupAddress,
                                                          const QNetworkInterface &interface)
{
    return multicastMembershipHelper(this,
                                     IPV6_JOIN_GROUP,
                                     IP_ADD_MEMBERSHIP,
                                     groupAddress,
                                     interface);
}

bool QNativeSocketEnginePrivate::nativeLeaveMulticastGroup(const QHostAddress &groupAddress,
                                                           const QNetworkInterface &interface)
{
    return multicastMembershipHelper(this,
                                     IPV6_LEAVE_GROUP,
                                     IP_DROP_MEMBERSHIP,
                                     groupAddress,
                                     interface);
}

QNetworkInterface QNativeSocketEnginePrivate::nativeMulticastInterface() const
{
    if (socketProtocol == QAbstractSocket::IPv6Protocol || socketProtocol == QAbstractSocket::AnyIPProtocol) {
        uint v;
        QT_SOCKOPTLEN_T sizeofv = sizeof(v);
        if (::getsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF, &v, &sizeofv) == -1)
            return QNetworkInterface();
        return QNetworkInterface::interfaceFromIndex(v);
    }

#if defined(Q_OS_SOLARIS)
    struct in_addr v = { 0, 0, 0, 0};
#else
    struct in_addr v = { 0 };
#endif
    QT_SOCKOPTLEN_T sizeofv = sizeof(v);
    if (::getsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, &v, &sizeofv) == -1)
        return QNetworkInterface();
    if (v.s_addr != 0 && sizeofv >= QT_SOCKOPTLEN_T(sizeof(v))) {
        QHostAddress ipv4(ntohl(v.s_addr));
        QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces();
        for (int i = 0; i < ifaces.size(); ++i) {
            const QNetworkInterface &iface = ifaces.at(i);
            QList<QNetworkAddressEntry> entries = iface.addressEntries();
            for (int j = 0; j < entries.size(); ++j) {
                const QNetworkAddressEntry &entry = entries.at(j);
                if (entry.ip() == ipv4)
                    return iface;
            }
        }
    }
    return QNetworkInterface();
}

bool QNativeSocketEnginePrivate::nativeSetMulticastInterface(const QNetworkInterface &iface)
{
    if (socketProtocol == QAbstractSocket::IPv6Protocol || socketProtocol == QAbstractSocket::AnyIPProtocol) {
        uint v = iface.index();
        return (::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF, &v, sizeof(v)) != -1);
    }

    struct in_addr v;
    if (iface.isValid()) {
        QList<QNetworkAddressEntry> entries = iface.addressEntries();
        for (int i = 0; i < entries.size(); ++i) {
            const QNetworkAddressEntry &entry = entries.at(i);
            const QHostAddress &ip = entry.ip();
            if (ip.protocol() == QAbstractSocket::IPv4Protocol) {
                v.s_addr = htonl(ip.toIPv4Address());
                int r = ::setsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, &v, sizeof(v));
                if (r != -1)
                    return true;
            }
        }
        return false;
    }

    v.s_addr = INADDR_ANY;
    return (::setsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, &v, sizeof(v)) != -1);
}

#endif // QT_NO_NETWORKINTERFACE

qint64 QNativeSocketEnginePrivate::nativeBytesAvailable() const
{
    int nbytes = 0;
    // gives shorter than true amounts on Unix domain sockets.
    qint64 available = -1;

#if defined (SO_NREAD)
    if (socketType == QAbstractSocket::UdpSocket) {
        socklen_t sz = sizeof nbytes;
        if (!::getsockopt(socketDescriptor, SOL_SOCKET, SO_NREAD, &nbytes, &sz))
            available = nbytes;
    }
#endif

    if (available == -1 && qt_safe_ioctl(socketDescriptor, FIONREAD, (char *) &nbytes) >= 0)
        available = nbytes;

#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEnginePrivate::nativeBytesAvailable() == %lli", available);
#endif
    return available > 0 ? available : 0;
}

bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const
{
    // Peek 1 bytes into the next message.
    ssize_t readBytes;
    char c;
    QT_EINTR_LOOP(readBytes, ::recv(socketDescriptor, &c, 1, MSG_PEEK));

    // If there's no error, or if our buffer was too small, there must be a
    // pending datagram.
    bool result = (readBytes != -1) || errno == EMSGSIZE;

#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEnginePrivate::nativeHasPendingDatagrams() == %s",
           result ? "true" : "false");
#endif
    return result;
}

qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const
{
    ssize_t recvResult = -1;
#ifdef Q_OS_LINUX
    // Linux can return the actual datagram size if we use MSG_TRUNC
    char c;
    QT_EINTR_LOOP(recvResult, ::recv(socketDescriptor, &c, 1, MSG_PEEK | MSG_TRUNC));
#elif defined(SO_NREAD)
    // macOS can return the actual datagram size if we use SO_NREAD
    int value;
    socklen_t valuelen = sizeof(value);
    recvResult = getsockopt(socketDescriptor, SOL_SOCKET, SO_NREAD, &value, &valuelen);
    if (recvResult != -1)
        recvResult = value;
#else
    // We need to grow the buffer to fit the entire datagram.
    // We start at 1500 bytes (the MTU for Ethernet V2), which should catch
    // almost all uses (effective MTU for UDP under IPv4 is 1468), except
    // for localhost datagrams and those reassembled by the IP layer.
    char udpMessagePeekBuffer[1500];
    struct msghdr msg;
    struct iovec vec;

    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = &vec;
    msg.msg_iovlen = 1;
    vec.iov_base = udpMessagePeekBuffer;
    vec.iov_len = sizeof(udpMessagePeekBuffer);

    for (;;) {
        // the data written to udpMessagePeekBuffer is discarded, so
        // this function is still reentrant although it might not look
        // so.
        recvResult = ::recvmsg(socketDescriptor, &msg, MSG_PEEK);
        if (recvResult == -1 && errno == EINTR)
            continue;

        // was the result truncated?
        if ((msg.msg_flags & MSG_TRUNC) == 0)
            break;

        // grow by 16 times
        msg.msg_iovlen *= 16;
        if (msg.msg_iov != &vec)
            delete[] msg.msg_iov;
        msg.msg_iov = new struct iovec[msg.msg_iovlen];
        std::fill_n(msg.msg_iov, msg.msg_iovlen, vec);
    }

    if (msg.msg_iov != &vec)
        delete[] msg.msg_iov;
#endif

#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEnginePrivate::nativePendingDatagramSize() == %zd", recvResult);
#endif

    return qint64(recvResult);
}

qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxSize, QIpPacketHeader *header,
                                                         QAbstractSocketEngine::PacketHeaderOptions options)
{
    // we use quintptr to force the alignment
    quintptr cbuf[(CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))
#if !defined(IP_PKTINFO) && defined(IP_RECVIF) && defined(Q_OS_BSD4)
                   + CMSG_SPACE(sizeof(sockaddr_dl))
#endif
#ifndef QT_NO_SCTP
                   + CMSG_SPACE(sizeof(struct sctp_sndrcvinfo))
#endif
                   + sizeof(quintptr) - 1) / sizeof(quintptr)];

    struct msghdr msg;
    struct iovec vec;
    qt_sockaddr aa;
    char c;
    memset(&msg, 0, sizeof(msg));
    memset(&aa, 0, sizeof(aa));

    // we need to receive at least one byte, even if our user isn't interested in it
    vec.iov_base = maxSize ? data : &c;
    vec.iov_len = maxSize ? maxSize : 1;
    msg.msg_iov = &vec;
    msg.msg_iovlen = 1;
    if (options & QAbstractSocketEngine::WantDatagramSender) {
        msg.msg_name = &aa;
        msg.msg_namelen = sizeof(aa);
    }
    if (options & (QAbstractSocketEngine::WantDatagramHopLimit | QAbstractSocketEngine::WantDatagramDestination
                   | QAbstractSocketEngine::WantStreamNumber)) {
        msg.msg_control = cbuf;
        msg.msg_controllen = sizeof(cbuf);
    }

    ssize_t recvResult = 0;
    do {
        recvResult = ::recvmsg(socketDescriptor, &msg, 0);
    } while (recvResult == -1 && errno == EINTR);

    if (recvResult == -1) {
        switch (errno) {
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
        case EWOULDBLOCK:
#endif
        case EAGAIN:
            // No datagram was available for reading
            recvResult = -2;
            break;
        case ECONNREFUSED:
            setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString);
            break;
        default:
            setError(QAbstractSocket::NetworkError, ReceiveDatagramErrorString);
        }
        if (header)
            header->clear();
    } else if (options != QAbstractSocketEngine::WantNone) {
        Q_ASSERT(header);
        qt_socket_getPortAndAddress(&aa, &header->senderPort, &header->senderAddress);
        header->destinationPort = localPort;
        header->endOfRecord = (msg.msg_flags & MSG_EOR) != 0;

        // parse the ancillary data
        struct cmsghdr *cmsgptr;
        QT_WARNING_PUSH
        QT_WARNING_DISABLE_CLANG("-Wsign-compare")
        for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != nullptr;
             cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
            QT_WARNING_POP
            if (cmsgptr->cmsg_level == IPPROTO_IPV6 && cmsgptr->cmsg_type == IPV6_PKTINFO
                    && cmsgptr->cmsg_len >= CMSG_LEN(sizeof(in6_pktinfo))) {
                in6_pktinfo *info = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cmsgptr));

                header->destinationAddress.setAddress(reinterpret_cast<quint8 *>(&info->ipi6_addr));
                header->ifindex = info->ipi6_ifindex;
                if (header->ifindex)
                    header->destinationAddress.setScopeId(QString::number(info->ipi6_ifindex));
            }

#ifdef IP_PKTINFO
            if (cmsgptr->cmsg_level == IPPROTO_IP && cmsgptr->cmsg_type == IP_PKTINFO
                    && cmsgptr->cmsg_len >= CMSG_LEN(sizeof(in_pktinfo))) {
                in_pktinfo *info = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cmsgptr));

                header->destinationAddress.setAddress(ntohl(info->ipi_addr.s_addr));
                header->ifindex = info->ipi_ifindex;
            }
#else
#  ifdef IP_RECVDSTADDR
            if (cmsgptr->cmsg_level == IPPROTO_IP && cmsgptr->cmsg_type == IP_RECVDSTADDR
                    && cmsgptr->cmsg_len >= CMSG_LEN(sizeof(in_addr))) {
                in_addr *addr = reinterpret_cast<in_addr *>(CMSG_DATA(cmsgptr));

                header->destinationAddress.setAddress(ntohl(addr->s_addr));
            }
#  endif
#  if defined(IP_RECVIF) && defined(Q_OS_BSD4)
            if (cmsgptr->cmsg_level == IPPROTO_IP && cmsgptr->cmsg_type == IP_RECVIF
                    && cmsgptr->cmsg_len >= CMSG_LEN(sizeof(sockaddr_dl))) {
                sockaddr_dl *sdl = reinterpret_cast<sockaddr_dl *>(CMSG_DATA(cmsgptr));
                header->ifindex = sdl->sdl_index;
            }
#  endif
#endif

            if (cmsgptr->cmsg_len == CMSG_LEN(sizeof(int))
                    && ((cmsgptr->cmsg_level == IPPROTO_IPV6 && cmsgptr->cmsg_type == IPV6_HOPLIMIT)
                        || (cmsgptr->cmsg_level == IPPROTO_IP && cmsgptr->cmsg_type == IP_TTL))) {
                static_assert(sizeof(header->hopLimit) == sizeof(int));
                memcpy(&header->hopLimit, CMSG_DATA(cmsgptr), sizeof(header->hopLimit));
            }

#ifndef QT_NO_SCTP
            if (cmsgptr->cmsg_level == IPPROTO_SCTP && cmsgptr->cmsg_type == SCTP_SNDRCV
                && cmsgptr->cmsg_len >= CMSG_LEN(sizeof(sctp_sndrcvinfo))) {
                sctp_sndrcvinfo *rcvInfo = reinterpret_cast<sctp_sndrcvinfo *>(CMSG_DATA(cmsgptr));

                header->streamNumber = int(rcvInfo->sinfo_stream);
            }
#endif
        }
    }

#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEnginePrivate::nativeReceiveDatagram(%p \"%s\", %lli, %s, %i) == %lli",
           data, QtDebugUtils::toPrintable(data, recvResult, 16).constData(), maxSize,
           (recvResult != -1 && options != QAbstractSocketEngine::WantNone)
           ? header->senderAddress.toString().toLatin1().constData() : "(unknown)",
           (recvResult != -1 && options != QAbstractSocketEngine::WantNone)
           ? header->senderPort : 0, (qint64) recvResult);
#endif

    return qint64((maxSize || recvResult < 0) ? recvResult : Q_INT64_C(0));
}

qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 len, const QIpPacketHeader &header)
{
    // we use quintptr to force the alignment
    quintptr cbuf[(CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))
#ifndef QT_NO_SCTP
                   + CMSG_SPACE(sizeof(struct sctp_sndrcvinfo))
#endif
                   + sizeof(quintptr) - 1) / sizeof(quintptr)];

    struct cmsghdr *cmsgptr = reinterpret_cast<struct cmsghdr *>(cbuf);
    struct msghdr msg;
    struct iovec vec;
    qt_sockaddr aa;

    memset(&msg, 0, sizeof(msg));
    memset(&aa, 0, sizeof(aa));
    vec.iov_base = const_cast<char *>(data);
    vec.iov_len = len;
    msg.msg_iov = &vec;
    msg.msg_iovlen = 1;
    msg.msg_control = &cbuf;

    if (header.destinationPort != 0) {
        msg.msg_name = &aa.a;
        setPortAndAddress(header.destinationPort, header.destinationAddress,
                          &aa, &msg.msg_namelen);
    }

    if (msg.msg_namelen == sizeof(aa.a6)) {
        if (header.hopLimit != -1) {
            msg.msg_controllen += CMSG_SPACE(sizeof(int));
            cmsgptr->cmsg_len = CMSG_LEN(sizeof(int));
            cmsgptr->cmsg_level = IPPROTO_IPV6;
            cmsgptr->cmsg_type = IPV6_HOPLIMIT;
            memcpy(CMSG_DATA(cmsgptr), &header.hopLimit, sizeof(int));
            cmsgptr = reinterpret_cast<cmsghdr *>(reinterpret_cast<char *>(cmsgptr) + CMSG_SPACE(sizeof(int)));
        }
        if (header.ifindex != 0 || !header.senderAddress.isNull()) {
            struct in6_pktinfo *data = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cmsgptr));
            memset(data, 0, sizeof(*data));
            msg.msg_controllen += CMSG_SPACE(sizeof(*data));
            cmsgptr->cmsg_len = CMSG_LEN(sizeof(*data));
            cmsgptr->cmsg_level = IPPROTO_IPV6;
            cmsgptr->cmsg_type = IPV6_PKTINFO;
            data->ipi6_ifindex = header.ifindex;

            QIPv6Address tmp = header.senderAddress.toIPv6Address();
            memcpy(&data->ipi6_addr, &tmp, sizeof(tmp));
            cmsgptr = reinterpret_cast<cmsghdr *>(reinterpret_cast<char *>(cmsgptr) + CMSG_SPACE(sizeof(*data)));
        }
    } else {
        if (header.hopLimit != -1) {
            msg.msg_controllen += CMSG_SPACE(sizeof(int));
            cmsgptr->cmsg_len = CMSG_LEN(sizeof(int));
            cmsgptr->cmsg_level = IPPROTO_IP;
            cmsgptr->cmsg_type = IP_TTL;
            memcpy(CMSG_DATA(cmsgptr), &header.hopLimit, sizeof(int));
            cmsgptr = reinterpret_cast<cmsghdr *>(reinterpret_cast<char *>(cmsgptr) + CMSG_SPACE(sizeof(int)));
        }

#if defined(IP_PKTINFO) || defined(IP_SENDSRCADDR)
        if (header.ifindex != 0 || !header.senderAddress.isNull()) {
#  ifdef IP_PKTINFO
            struct in_pktinfo *data = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cmsgptr));
            memset(data, 0, sizeof(*data));
            cmsgptr->cmsg_type = IP_PKTINFO;
            data->ipi_ifindex = header.ifindex;
            data->ipi_addr.s_addr = htonl(header.senderAddress.toIPv4Address());
#  elif defined(IP_SENDSRCADDR)
            struct in_addr *data = reinterpret_cast<in_addr *>(CMSG_DATA(cmsgptr));
            cmsgptr->cmsg_type = IP_SENDSRCADDR;
            data->s_addr = htonl(header.senderAddress.toIPv4Address());
#  endif
            cmsgptr->cmsg_level = IPPROTO_IP;
            msg.msg_controllen += CMSG_SPACE(sizeof(*data));
            cmsgptr->cmsg_len = CMSG_LEN(sizeof(*data));
            cmsgptr = reinterpret_cast<cmsghdr *>(reinterpret_cast<char *>(cmsgptr) + CMSG_SPACE(sizeof(*data)));
        }
#endif
    }

#ifndef QT_NO_SCTP
    if (header.streamNumber != -1) {
        struct sctp_sndrcvinfo *data = reinterpret_cast<sctp_sndrcvinfo *>(CMSG_DATA(cmsgptr));
        memset(data, 0, sizeof(*data));
        msg.msg_controllen += CMSG_SPACE(sizeof(sctp_sndrcvinfo));
        cmsgptr->cmsg_len = CMSG_LEN(sizeof(sctp_sndrcvinfo));
        cmsgptr->cmsg_level = IPPROTO_SCTP;
        cmsgptr->cmsg_type =  SCTP_SNDRCV;
        data->sinfo_stream = uint16_t(header.streamNumber);
        cmsgptr = reinterpret_cast<cmsghdr *>(reinterpret_cast<char *>(cmsgptr) + CMSG_SPACE(sizeof(*data)));
    }
#endif

    if (msg.msg_controllen == 0)
        msg.msg_control = nullptr;
    ssize_t sentBytes = qt_safe_sendmsg(socketDescriptor, &msg, 0);

    if (sentBytes < 0) {
        switch (errno) {
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
        case EWOULDBLOCK:
#endif
        case EAGAIN:
            sentBytes = -2;
            break;
        case EMSGSIZE:
            setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString);
            break;
        case ECONNRESET:
            setError(QAbstractSocket::RemoteHostClosedError, RemoteHostClosedErrorString);
            break;
        default:
            setError(QAbstractSocket::NetworkError, SendDatagramErrorString);
        }
    }

#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEngine::sendDatagram(%p \"%s\", %lli, \"%s\", %i) == %lli", data,
           QtDebugUtils::toPrintable(data, len, 16).constData(), len,
           header.destinationAddress.toString().toLatin1().constData(),
           header.destinationPort, (qint64) sentBytes);
#endif

    return qint64(sentBytes);
}

bool QNativeSocketEnginePrivate::fetchConnectionParameters()
{
    localPort = 0;
    localAddress.clear();
    peerPort = 0;
    peerAddress.clear();
    inboundStreamCount = outboundStreamCount = 0;

    if (socketDescriptor == -1)
        return false;

    qt_sockaddr sa;
    QT_SOCKLEN_T sockAddrSize = sizeof(sa);

    // Determine local address
    memset(&sa, 0, sizeof(sa));
    if (::getsockname(socketDescriptor, &sa.a, &sockAddrSize) == 0) {
        qt_socket_getPortAndAddress(&sa, &localPort, &localAddress);

        // Determine protocol family
        switch (sa.a.sa_family) {
        case AF_INET:
            socketProtocol = QAbstractSocket::IPv4Protocol;
            break;
        case AF_INET6:
            socketProtocol = QAbstractSocket::IPv6Protocol;
            break;
        default:
            socketProtocol = QAbstractSocket::UnknownNetworkLayerProtocol;
            break;
        }

    } else if (errno == EBADF) {
        setError(QAbstractSocket::UnsupportedSocketOperationError, InvalidSocketErrorString);
        return false;
    }

#if defined (IPV6_V6ONLY)
    // determine if local address is dual mode
    // On linux, these are returned as "::" (==AnyIPv6)
    // On OSX, these are returned as "::FFFF:0.0.0.0" (==AnyIPv4)
    // in either case, the IPV6_V6ONLY option is cleared
    int ipv6only = 0;
    socklen_t optlen = sizeof(ipv6only);
    if (socketProtocol == QAbstractSocket::IPv6Protocol
        && (localAddress == QHostAddress::AnyIPv4 || localAddress == QHostAddress::AnyIPv6)
        && !getsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, &optlen )) {
            if (optlen != sizeof(ipv6only))
                qWarning("unexpected size of IPV6_V6ONLY socket option");
            if (!ipv6only) {
                socketProtocol = QAbstractSocket::AnyIPProtocol;
                localAddress = QHostAddress::Any;
            }
    }
#endif

    // Determine the remote address
    bool connected = ::getpeername(socketDescriptor, &sa.a, &sockAddrSize) == 0;
    if (connected) {
        qt_socket_getPortAndAddress(&sa, &peerPort, &peerAddress);
        inboundStreamCount = outboundStreamCount = 1;
    }

    // Determine the socket type (UDP/TCP/SCTP)
    int value = 0;
    QT_SOCKOPTLEN_T valueSize = sizeof(int);
    if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_TYPE, &value, &valueSize) == 0) {
        if (value == SOCK_STREAM) {
#ifndef QT_NO_SCTP
            if (option(QNativeSocketEngine::MaxStreamsSocketOption) != -1) {
                socketType = QAbstractSocket::SctpSocket;
                if (connected) {
                    sctp_status sctpStatus;
                    QT_SOCKOPTLEN_T sctpStatusSize = sizeof(sctpStatus);
                    sctp_event_subscribe sctpEvents;

                    memset(&sctpEvents, 0, sizeof(sctpEvents));
                    sctpEvents.sctp_data_io_event = 1;
                    if (::getsockopt(socketDescriptor, SOL_SCTP, SCTP_STATUS, &sctpStatus,
                                     &sctpStatusSize) == 0 &&
                        ::setsockopt(socketDescriptor, SOL_SCTP, SCTP_EVENTS, &sctpEvents,
                                     sizeof(sctpEvents)) == 0) {
                        inboundStreamCount = int(sctpStatus.sstat_instrms);
                        outboundStreamCount = int(sctpStatus.sstat_outstrms);
                    } else {
                        setError(QAbstractSocket::UnsupportedSocketOperationError,
                                 InvalidSocketErrorString);
                        return false;
                    }
                }
            } else {
                socketType = QAbstractSocket::TcpSocket;
            }
#else
                socketType = QAbstractSocket::TcpSocket;
#endif
        } else {
            if (value == SOCK_DGRAM)
                socketType = QAbstractSocket::UdpSocket;
            else
                socketType = QAbstractSocket::UnknownSocketType;
        }
    }
#if defined (QNATIVESOCKETENGINE_DEBUG)
    QString socketProtocolStr = QStringLiteral("UnknownProtocol");
    if (socketProtocol == QAbstractSocket::IPv4Protocol) socketProtocolStr = QStringLiteral("IPv4Protocol");
    else if (socketProtocol == QAbstractSocket::IPv6Protocol || socketProtocol == QAbstractSocket::AnyIPProtocol) socketProtocolStr = QStringLiteral("IPv6Protocol");

    QString socketTypeStr = QStringLiteral("UnknownSocketType");
    if (socketType == QAbstractSocket::TcpSocket) socketTypeStr = QStringLiteral("TcpSocket");
    else if (socketType == QAbstractSocket::UdpSocket) socketTypeStr = QStringLiteral("UdpSocket");
    else if (socketType == QAbstractSocket::SctpSocket) socketTypeStr = QStringLiteral("SctpSocket");

    qDebug("QNativeSocketEnginePrivate::fetchConnectionParameters() local == %s:%i,"
           " peer == %s:%i, socket == %s - %s, inboundStreamCount == %i, outboundStreamCount == %i",
           localAddress.toString().toLatin1().constData(), localPort,
           peerAddress.toString().toLatin1().constData(), peerPort,socketTypeStr.toLatin1().constData(),
           socketProtocolStr.toLatin1().constData(), inboundStreamCount, outboundStreamCount);
#endif
    return true;
}

void QNativeSocketEnginePrivate::nativeClose()
{
#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEngine::nativeClose()");
#endif

    qt_safe_close(socketDescriptor);
}

qint64 QNativeSocketEnginePrivate::nativeWrite(const char *data, qint64 len)
{
    Q_Q(QNativeSocketEngine);

    ssize_t writtenBytes;
    writtenBytes = qt_safe_write_nosignal(socketDescriptor, data, len);

    if (writtenBytes < 0) {
        switch (errno) {
        case EPIPE:
        case ECONNRESET:
            writtenBytes = -1;
            setError(QAbstractSocket::RemoteHostClosedError, RemoteHostClosedErrorString);
            q->close();
            break;
        case EAGAIN:
            writtenBytes = 0;
            break;
        case EMSGSIZE:
            setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString);
            break;
        default:
            break;
        }
    }

#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEnginePrivate::nativeWrite(%p \"%s\", %llu) == %i", data,
           QtDebugUtils::toPrintable(data, len, 16).constData(), len, (int) writtenBytes);
#endif

    return qint64(writtenBytes);
}
/*
*/
qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxSize)
{
    Q_Q(QNativeSocketEngine);
    if (!q->isValid()) {
        qWarning("QNativeSocketEngine::nativeRead: Invalid socket");
        return -1;
    }

    ssize_t r = 0;
    r = qt_safe_read(socketDescriptor, data, maxSize);

    if (r < 0) {
        r = -1;
        switch (errno) {
#if EWOULDBLOCK-0 && EWOULDBLOCK != EAGAIN
        case EWOULDBLOCK:
#endif
        case EAGAIN:
            // No data was available for reading
            r = -2;
            break;
        case ECONNRESET:
#if defined(Q_OS_VXWORKS)
        case ESHUTDOWN:
#endif
            r = 0;
            break;
        case ETIMEDOUT:
            socketError = QAbstractSocket::SocketTimeoutError;
            break;
        default:
            socketError = QAbstractSocket::NetworkError;
            break;
        }

        if (r == -1) {
            hasSetSocketError = true;
            socketErrorString = qt_error_string();
        }
    }

#if defined (QNATIVESOCKETENGINE_DEBUG)
    qDebug("QNativeSocketEnginePrivate::nativeRead(%p \"%s\", %llu) == %zd", data,
           QtDebugUtils::toPrintable(data, r, 16).constData(), maxSize, r);
#endif

    return qint64(r);
}

int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) const
{
    bool dummy;
    return nativeSelect(timeout, selectForRead, !selectForRead, &dummy, &dummy);
}

#ifndef Q_OS_WASM

int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool checkWrite,
                       bool *selectForRead, bool *selectForWrite) const
{
    pollfd pfd = qt_make_pollfd(socketDescriptor, 0);

    if (checkRead)
        pfd.events |= POLLIN;

    if (checkWrite)
        pfd.events |= POLLOUT;

    const int ret = qt_poll_msecs(&pfd, 1, timeout);

    if (ret <= 0)
        return ret;

    if (pfd.revents & POLLNVAL) {
        errno = EBADF;
        return -1;
    }

    static const short read_flags = POLLIN | POLLHUP | POLLERR;
    static const short write_flags = POLLOUT | POLLERR;

    *selectForRead = ((pfd.revents & read_flags) != 0);
    *selectForWrite = ((pfd.revents & write_flags) != 0);

    return ret;
}

#else

int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool checkWrite,
                        bool *selectForRead, bool *selectForWrite) const
{
    *selectForRead = checkRead;
    *selectForWrite = checkWrite;
    bool socketDisconnect = false;
    QEventDispatcherWasm::socketSelect(timeout, socketDescriptor, checkRead, checkWrite,selectForRead, selectForWrite, &socketDisconnect);

    // The disconnect/close handling code in QAbstractsScket::canReadNotification()
    // does not detect remote disconnect properly; do that here as a workardound.
    if (socketDisconnect)
        receiver->closeNotification();

    return 1;
}

#endif // Q_OS_WASM

QT_END_NAMESPACE
