diff options
Diffstat (limited to 'src/network/socket')
36 files changed, 18935 insertions, 0 deletions
diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp new file mode 100644 index 0000000000..7af71ccc8b --- /dev/null +++ b/src/network/socket/qabstractsocket.cpp @@ -0,0 +1,2920 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QABSTRACTSOCKET_DEBUG + +/*! + \class QAbstractSocket + + \brief The QAbstractSocket class provides the base functionality + common to all socket types. + + \reentrant + \ingroup network + \inmodule QtNetwork + + QAbstractSocket is the base class for QTcpSocket and QUdpSocket + and contains all common functionality of these two classes. If + you need a socket, you have two options: + + \list + \i Instantiate QTcpSocket or QUdpSocket. + \i Create a native socket descriptor, instantiate + QAbstractSocket, and call setSocketDescriptor() to wrap the + native socket. + \endlist + + TCP (Transmission Control Protocol) is a reliable, + stream-oriented, connection-oriented transport protocol. UDP + (User Datagram Protocol) is an unreliable, datagram-oriented, + connectionless protocol. In practice, this means that TCP is + better suited for continuous transmission of data, whereas the + more lightweight UDP can be used when reliability isn't + important. + + QAbstractSocket's API unifies most of the differences between the + two protocols. For example, although UDP is connectionless, + connectToHost() establishes a virtual connection for UDP sockets, + enabling you to use QAbstractSocket in more or less the same way + regardless of the underlying protocol. Internally, + QAbstractSocket remembers the address and port passed to + connectToHost(), and functions like read() and write() use these + values. + + At any time, QAbstractSocket has a state (returned by + state()). The initial state is UnconnectedState. After + calling connectToHost(), the socket first enters + HostLookupState. If the host is found, QAbstractSocket enters + ConnectingState and emits the hostFound() signal. When the + connection has been established, it enters ConnectedState and + emits connected(). If an error occurs at any stage, error() is + emitted. Whenever the state changes, stateChanged() is emitted. + For convenience, isValid() returns true if the socket is ready for + reading and writing, but note that the socket's state must be + ConnectedState before reading and writing can occur. + + Read or write data by calling read() or write(), or use the + convenience functions readLine() and readAll(). QAbstractSocket + also inherits getChar(), putChar(), and ungetChar() from + QIODevice, which work on single bytes. The bytesWritten() signal + is emitted when data has been written to the socket (i.e., when + the client has read the data). Note that Qt does not limit the + write buffer size. You can monitor its size by listening to this + signal. + + The readyRead() signal is emitted every time a new chunk of data + has arrived. bytesAvailable() then returns the number of bytes + that are available for reading. Typically, you would connect the + readyRead() signal to a slot and read all available data there. + If you don't read all the data at once, the remaining data will + still be available later, and any new incoming data will be + appended to QAbstractSocket's internal read buffer. To limit the + size of the read buffer, call setReadBufferSize(). + + To close the socket, call disconnectFromHost(). QAbstractSocket enters + QAbstractSocket::ClosingState. After all pending data has been written to + the socket, QAbstractSocket actually closes the socket, enters + QAbstractSocket::ClosedState, and emits disconnected(). If you want to + abort a connection immediately, discarding all pending data, call abort() + instead. If the remote host closes the connection, QAbstractSocket will + emit error(QAbstractSocket::RemoteHostClosedError), during which the socket + state will still be ConnectedState, and then the disconnected() signal + will be emitted. + + The port and address of the connected peer is fetched by calling + peerPort() and peerAddress(). peerName() returns the host name of + the peer, as passed to connectToHost(). localPort() and + localAddress() return the port and address of the local socket. + + QAbstractSocket provides a set of functions that suspend the + calling thread until certain signals are emitted. These functions + can be used to implement blocking sockets: + + \list + \o waitForConnected() blocks until a connection has been established. + + \o waitForReadyRead() blocks until new data is available for + reading. + + \o waitForBytesWritten() blocks until one payload of data has been + written to the socket. + + \o waitForDisconnected() blocks until the connection has closed. + \endlist + + We show an example: + + \snippet doc/src/snippets/network/tcpwait.cpp 0 + + If \l{QIODevice::}{waitForReadyRead()} returns false, the + connection has been closed or an error has occurred. + + Programming with a blocking socket is radically different from + programming with a non-blocking socket. A blocking socket doesn't + require an event loop and typically leads to simpler code. + However, in a GUI application, blocking sockets should only be + used in non-GUI threads, to avoid freezing the user interface. + See the \l network/fortuneclient and \l network/blockingfortuneclient + examples for an overview of both approaches. + + \note We discourage the use of the blocking functions together + with signals. One of the two possibilities should be used. + + QAbstractSocket can be used with QTextStream and QDataStream's + stream operators (operator<<() and operator>>()). There is one + issue to be aware of, though: You must make sure that enough data + is available before attempting to read it using operator>>(). + + \sa QFtp, QNetworkAccessManager, QTcpServer +*/ + +/*! + \fn void QAbstractSocket::hostFound() + + This signal is emitted after connectToHost() has been called and + the host lookup has succeeded. + + \note Since Qt 4.6.3 QAbstractSocket may emit hostFound() + directly from the connectToHost() call since a DNS result could have been + cached. + + \sa connected() +*/ + +/*! + \fn void QAbstractSocket::connected() + + This signal is emitted after connectToHost() has been called and + a connection has been successfully established. + + \note On some operating systems the connected() signal may + be directly emitted from the connectToHost() call for connections + to the localhost. + + \sa connectToHost(), disconnected() +*/ + +/*! + \fn void QAbstractSocket::disconnected() + + This signal is emitted when the socket has been disconnected. + + \warning If you need to delete the sender() of this signal in a slot connected + to it, use the \l{QObject::deleteLater()}{deleteLater()} function. + + \sa connectToHost(), disconnectFromHost(), abort() +*/ + +/*! + \fn void QAbstractSocket::error(QAbstractSocket::SocketError socketError) + + This signal is emitted after an error occurred. The \a socketError + parameter describes the type of error that occurred. + + QAbstractSocket::SocketError is not a registered metatype, so for queued + connections, you will have to register it with Q_DECLARE_METATYPE() and + qRegisterMetaType(). + + \sa error(), errorString(), {Creating Custom Qt Types} +*/ + +/*! + \fn void QAbstractSocket::stateChanged(QAbstractSocket::SocketState socketState) + + This signal is emitted whenever QAbstractSocket's state changes. + The \a socketState parameter is the new state. + + QAbstractSocket::SocketState is not a registered metatype, so for queued + connections, you will have to register it with Q_REGISTER_METATYPE() and + qRegisterMetaType(). + + \sa state(), {Creating Custom Qt Types} +*/ + +/*! + \fn void QAbstractSocket::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) + \since 4.3 + + This signal can be emitted when a \a proxy that requires + authentication is used. The \a authenticator object can then be + filled in with the required details to allow authentication and + continue the connection. + + \note It is not possible to use a QueuedConnection to connect to + this signal, as the connection will fail if the authenticator has + not been filled in with new information when the signal returns. + + \sa QAuthenticator, QNetworkProxy +*/ + +/*! + \enum QAbstractSocket::NetworkLayerProtocol + + This enum describes the network layer protocol values used in Qt. + + \value IPv4Protocol IPv4 + \value IPv6Protocol IPv6 + \value UnknownNetworkLayerProtocol Other than IPv4 and IPv6 + + \sa QHostAddress::protocol() +*/ + +/*! + \enum QAbstractSocket::SocketType + + This enum describes the transport layer protocol. + + \value TcpSocket TCP + \value UdpSocket UDP + \value UnknownSocketType Other than TCP and UDP + + \sa QAbstractSocket::socketType() +*/ + +/*! + \enum QAbstractSocket::SocketError + + This enum describes the socket errors that can occur. + + \value ConnectionRefusedError The connection was refused by the + peer (or timed out). + \value RemoteHostClosedError The remote host closed the + connection. Note that the client socket (i.e., this socket) + will be closed after the remote close notification has + been sent. + \value HostNotFoundError The host address was not found. + \value SocketAccessError The socket operation failed because the + application lacked the required privileges. + \value SocketResourceError The local system ran out of resources + (e.g., too many sockets). + \value SocketTimeoutError The socket operation timed out. + \value DatagramTooLargeError The datagram was larger than the + operating system's limit (which can be as low as 8192 + bytes). + \value NetworkError An error occurred with the network (e.g., the + network cable was accidentally plugged out). + \value AddressInUseError The address specified to QUdpSocket::bind() is + already in use and was set to be exclusive. + \value SocketAddressNotAvailableError The address specified to + QUdpSocket::bind() does not belong to the host. + \value UnsupportedSocketOperationError The requested socket operation is + not supported by the local operating system (e.g., lack of + IPv6 support). + \value ProxyAuthenticationRequiredError The socket is using a proxy, and + the proxy requires authentication. + \value SslHandshakeFailedError The SSL/TLS handshake failed, so + the connection was closed (only used in QSslSocket) + \value UnfinishedSocketOperationError Used by QAbstractSocketEngine only, + The last operation attempted has not finished yet (still in progress in + the background). + \value ProxyConnectionRefusedError Could not contact the proxy server because + the connection to that server was denied + \value ProxyConnectionClosedError The connection to the proxy server was closed + unexpectedly (before the connection to the final peer was established) + \value ProxyConnectionTimeoutError The connection to the proxy server timed out + or the proxy server stopped responding in the authentication phase. + \value ProxyNotFoundError The proxy address set with setProxy() (or the application + proxy) was not found. + \value ProxyProtocolError The connection negotiation with the proxy server + because the response from the proxy server could not be understood. + + \value UnknownSocketError An unidentified error occurred. + \sa QAbstractSocket::error() +*/ + +/*! + \enum QAbstractSocket::SocketState + + This enum describes the different states in which a socket can be. + + \value UnconnectedState The socket is not connected. + \value HostLookupState The socket is performing a host name lookup. + \value ConnectingState The socket has started establishing a connection. + \value ConnectedState A connection is established. + \value BoundState The socket is bound to an address and port (for servers). + \value ClosingState The socket is about to close (data may still + be waiting to be written). + \value ListeningState For internal use only. + \omitvalue Idle + \omitvalue HostLookup + \omitvalue Connecting + \omitvalue Connected + \omitvalue Closing + \omitvalue Connection + + \sa QAbstractSocket::state() +*/ + +/*! + \enum QAbstractSocket::SocketOption + \since 4.6 + + This enum represents the options that can be set on a socket. + If desired, they can be set after having received the connected() signal from + the socket or after having received a new socket from a QTcpServer. + + \value LowDelayOption Try to optimize the socket for low latency. For a QTcpSocket + this would set the TCP_NODELAY option and disable Nagle's algorithm. Set this to 1 + to enable. + \value KeepAliveOption Set this to 1 to enable the SO_KEEPALIVE socket option + + \value MulticastTtlOption Set this to an integer value to set IP_MULTICAST_TTL (TTL for multicast datagrams) socket option. + + \value MulticastLoopbackOption Set this to 1 to enable the IP_MULTICAST_LOOP (multicast loopback) socket option. + + \sa QAbstractSocket::setSocketOption(), QAbstractSocket::socketOption() +*/ + +#include "qabstractsocket.h" +#include "qabstractsocket_p.h" + +#include "private/qhostinfo_p.h" +#include "private/qnetworksession_p.h" + +#include <qabstracteventdispatcher.h> +#include <qhostaddress.h> +#include <qhostinfo.h> +#include <qmetaobject.h> +#include <qpointer.h> +#include <qtimer.h> +#include <qelapsedtimer.h> +#include <qscopedvaluerollback.h> + +#ifndef QT_NO_OPENSSL +#include <QtNetwork/qsslsocket.h> +#endif + +#include <private/qthread_p.h> + +#ifdef QABSTRACTSOCKET_DEBUG +#include <qdebug.h> +#endif + +#include <time.h> + +#define Q_CHECK_SOCKETENGINE(returnValue) do { \ + if (!d->socketEngine) { \ + return returnValue; \ + } } while (0) + +#ifndef QABSTRACTSOCKET_BUFFERSIZE +#define QABSTRACTSOCKET_BUFFERSIZE 32768 +#endif +#define QT_CONNECT_TIMEOUT 30000 +#define QT_TRANSFER_TIMEOUT 120000 + +QT_BEGIN_NAMESPACE + +#if defined QABSTRACTSOCKET_DEBUG +QT_BEGIN_INCLUDE_NAMESPACE +#include <qstring.h> +#include <ctype.h> +QT_END_INCLUDE_NAMESPACE + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxLength) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(int(uchar(c)))) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1(); + } + } + + if (len < maxLength) + out += "..."; + + return out; +} +#endif + +static bool isProxyError(QAbstractSocket::SocketError error) +{ + switch (error) { + case QAbstractSocket::ProxyAuthenticationRequiredError: + case QAbstractSocket::ProxyConnectionRefusedError: + case QAbstractSocket::ProxyConnectionClosedError: + case QAbstractSocket::ProxyConnectionTimeoutError: + case QAbstractSocket::ProxyNotFoundError: + case QAbstractSocket::ProxyProtocolError: + return true; + default: + return false; + } +} + +/*! \internal + + Constructs a QAbstractSocketPrivate. Initializes all members. +*/ +QAbstractSocketPrivate::QAbstractSocketPrivate() + : readSocketNotifierCalled(false), + readSocketNotifierState(false), + readSocketNotifierStateSet(false), + emittedReadyRead(false), + emittedBytesWritten(false), + abortCalled(false), + closeCalled(false), + pendingClose(false), + port(0), + localPort(0), + peerPort(0), + socketEngine(0), + cachedSocketDescriptor(-1), + readBufferMaxSize(0), + readBuffer(QABSTRACTSOCKET_BUFFERSIZE), + writeBuffer(QABSTRACTSOCKET_BUFFERSIZE), + isBuffered(false), + blockingTimeout(30000), + connectTimer(0), + disconnectTimer(0), + connectTimeElapsed(0), + hostLookupId(-1), + socketType(QAbstractSocket::UnknownSocketType), + state(QAbstractSocket::UnconnectedState), + socketError(QAbstractSocket::UnknownSocketError) +{ +} + +/*! \internal + + Destructs the QAbstractSocket. If the socket layer is open, it + will be reset. +*/ +QAbstractSocketPrivate::~QAbstractSocketPrivate() +{ +} + +/*! \internal + + Resets the socket layer, clears the read and write buffers and + deletes any socket notifiers. +*/ +void QAbstractSocketPrivate::resetSocketLayer() +{ +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::resetSocketLayer()"); +#endif + + if (socketEngine) { + socketEngine->close(); + socketEngine->disconnect(); + delete socketEngine; + socketEngine = 0; + cachedSocketDescriptor = -1; + } + if (connectTimer) + connectTimer->stop(); + if (disconnectTimer) + disconnectTimer->stop(); +} + +/*! \internal + + Initializes the socket layer to by of type \a type, using the + network layer protocol \a protocol. Resets the socket layer first + if it's already initialized. Sets up the socket notifiers. +*/ +bool QAbstractSocketPrivate::initSocketLayer(QAbstractSocket::NetworkLayerProtocol protocol) +{ +#ifdef QT_NO_NETWORKPROXY + // this is here to avoid a duplication of the call to createSocketEngine below + static const QNetworkProxy &proxyInUse = *(QNetworkProxy *)0; +#endif + + Q_Q(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + QString typeStr; + if (q->socketType() == QAbstractSocket::TcpSocket) typeStr = QLatin1String("TcpSocket"); + else if (q->socketType() == QAbstractSocket::UdpSocket) typeStr = QLatin1String("UdpSocket"); + else typeStr = QLatin1String("UnknownSocketType"); + QString protocolStr; + if (protocol == QAbstractSocket::IPv4Protocol) protocolStr = QLatin1String("IPv4Protocol"); + else if (protocol == QAbstractSocket::IPv6Protocol) protocolStr = QLatin1String("IPv6Protocol"); + else protocolStr = QLatin1String("UnknownNetworkLayerProtocol"); +#endif + + resetSocketLayer(); + socketEngine = QAbstractSocketEngine::createSocketEngine(q->socketType(), proxyInUse, q); +#ifndef QT_NO_BEARERMANAGEMENT + //copy network session down to the socket engine (if it has been set) + socketEngine->setProperty("_q_networksession", q->property("_q_networksession")); +#endif + if (!socketEngine) { + socketError = QAbstractSocket::UnsupportedSocketOperationError; + q->setErrorString(QAbstractSocket::tr("Operation on socket is not supported")); + return false; + } + if (!socketEngine->initialize(q->socketType(), protocol)) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::initSocketLayer(%s, %s) failed (%s)", + typeStr.toLatin1().constData(), protocolStr.toLatin1().constData(), + socketEngine->errorString().toLatin1().constData()); +#endif + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + return false; + } + + if (threadData->eventDispatcher) + socketEngine->setReceiver(this); + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::initSocketLayer(%s, %s) success", + typeStr.toLatin1().constData(), protocolStr.toLatin1().constData()); +#endif + return true; +} + +/*! \internal + + Slot connected to the read socket notifier. This slot is called + when new data is available for reading, or when the socket has + been closed. Handles recursive calls. +*/ +bool QAbstractSocketPrivate::canReadNotification() +{ + Q_Q(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification()"); +#endif + + // Prevent recursive calls + if (readSocketNotifierCalled) { + if (!readSocketNotifierStateSet) { + readSocketNotifierStateSet = true; + readSocketNotifierState = socketEngine->isReadNotificationEnabled(); + socketEngine->setReadNotificationEnabled(false); + } + } + QScopedValueRollback<bool> rsncrollback(readSocketNotifierCalled); + readSocketNotifierCalled = true; + + if (!isBuffered) + socketEngine->setReadNotificationEnabled(false); + + // If buffered, read data from the socket into the read buffer + qint64 newBytes = 0; + if (isBuffered) { + // Return if there is no space in the buffer + if (readBufferMaxSize && readBuffer.size() >= readBufferMaxSize) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification() buffer is full"); +#endif + return false; + } + + // If reading from the socket fails after getting a read + // notification, close the socket. + newBytes = readBuffer.size(); + if (!readFromSocket()) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification() disconnecting socket"); +#endif + q->disconnectFromHost(); + return false; + } + newBytes = readBuffer.size() - newBytes; + + // If read buffer is full, disable the read socket notifier. + if (readBufferMaxSize && readBuffer.size() == readBufferMaxSize) { + socketEngine->setReadNotificationEnabled(false); + } + } + + // only emit readyRead() when not recursing, and only if there is data available + bool hasData = newBytes > 0 +#ifndef QT_NO_UDPSOCKET + || (!isBuffered && socketType != QAbstractSocket::TcpSocket && socketEngine && socketEngine->hasPendingDatagrams()) +#endif + || (!isBuffered && socketType == QAbstractSocket::TcpSocket && socketEngine) + ; + + if (!emittedReadyRead && hasData) { + QScopedValueRollback<bool> r(emittedReadyRead); + emittedReadyRead = true; + emit q->readyRead(); + } + + // If we were closed as a result of the readyRead() signal, + // return. + if (state == QAbstractSocket::UnconnectedState || state == QAbstractSocket::ClosingState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification() socket is closing - returning"); +#endif + return true; + } + + if (!hasData && socketEngine) + socketEngine->setReadNotificationEnabled(true); + + // reset the read socket notifier state if we reentered inside the + // readyRead() connected slot. + if (readSocketNotifierStateSet && socketEngine && + readSocketNotifierState != socketEngine->isReadNotificationEnabled()) { + socketEngine->setReadNotificationEnabled(readSocketNotifierState); + readSocketNotifierStateSet = false; + } + return true; +} + +/*! \internal + + Slot connected to the write socket notifier. It's called during a + delayed connect or when the socket is ready for writing. +*/ +bool QAbstractSocketPrivate::canWriteNotification() +{ +#if defined (Q_OS_WIN) + if (socketEngine && socketEngine->isWriteNotificationEnabled()) + socketEngine->setWriteNotificationEnabled(false); +#endif + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canWriteNotification() flushing"); +#endif + int tmp = writeBuffer.size(); + flush(); + + if (socketEngine) { +#if defined (Q_OS_WIN) + if (!writeBuffer.isEmpty()) + socketEngine->setWriteNotificationEnabled(true); +#else + if (writeBuffer.isEmpty() && socketEngine->bytesToWrite() == 0) + socketEngine->setWriteNotificationEnabled(false); +#endif + } + + return (writeBuffer.size() < tmp); +} + +/*! \internal + + Slot connected to a notification of connection status + change. Either we finished connecting or we failed to connect. +*/ +void QAbstractSocketPrivate::connectionNotification() +{ + // If in connecting state, check if the connection has been + // established, otherwise flush pending data. + if (state == QAbstractSocket::ConnectingState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::connectionNotification() testing connection"); +#endif + _q_testConnection(); + } +} + +/*! \internal + + Writes pending data in the write buffers to the socket. The + function writes as much as it can without blocking. + + It is usually invoked by canWriteNotification after one or more + calls to write(). + + Emits bytesWritten(). +*/ +bool QAbstractSocketPrivate::flush() +{ + Q_Q(QAbstractSocket); + if (!socketEngine || !socketEngine->isValid() || (writeBuffer.isEmpty() + && socketEngine->bytesToWrite() == 0)) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::flush() nothing to do: valid ? %s, writeBuffer.isEmpty() ? %s", + socketEngine->isValid() ? "yes" : "no", writeBuffer.isEmpty() ? "yes" : "no"); +#endif + + // this covers the case when the buffer was empty, but we had to wait for the socket engine to finish + if (state == QAbstractSocket::ClosingState) + q->disconnectFromHost(); + + return false; + } + + int nextSize = writeBuffer.nextDataBlockSize(); + const char *ptr = writeBuffer.readPointer(); + + // Attempt to write it all in one chunk. + qint64 written = socketEngine->write(ptr, nextSize); + if (written < 0) { + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug() << "QAbstractSocketPrivate::flush() write error, aborting." << socketEngine->errorString(); +#endif + emit q->error(socketError); + // an unexpected error so close the socket. + q->abort(); + return false; + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::flush() %lld bytes written to the network", + written); +#endif + + // Remove what we wrote so far. + writeBuffer.free(written); + if (written > 0) { + // Don't emit bytesWritten() recursively. + if (!emittedBytesWritten) { + QScopedValueRollback<bool> r(emittedBytesWritten); + emittedBytesWritten = true; + emit q->bytesWritten(written); + } + } + + if (writeBuffer.isEmpty() && socketEngine && socketEngine->isWriteNotificationEnabled() + && !socketEngine->bytesToWrite()) + socketEngine->setWriteNotificationEnabled(false); + if (state == QAbstractSocket::ClosingState) + q->disconnectFromHost(); + + return true; +} + +#ifndef QT_NO_NETWORKPROXY +/*! \internal + + Resolve the proxy to its final value. +*/ +void QAbstractSocketPrivate::resolveProxy(const QString &hostname, quint16 port) +{ + QHostAddress parsed; + if (hostname == QLatin1String("localhost") + || hostname.startsWith(QLatin1String("localhost.")) + || (parsed.setAddress(hostname) + && (parsed == QHostAddress::LocalHost + || parsed == QHostAddress::LocalHostIPv6))) { + proxyInUse = QNetworkProxy::NoProxy; + return; + } + + QList<QNetworkProxy> proxies; + + if (proxy.type() != QNetworkProxy::DefaultProxy) { + // a non-default proxy was set with setProxy + proxies << proxy; + } else { + // try the application settings instead + QNetworkProxyQuery query(hostname, port, QString(), + socketType == QAbstractSocket::TcpSocket ? + QNetworkProxyQuery::TcpSocket : + QNetworkProxyQuery::UdpSocket); + proxies = QNetworkProxyFactory::proxyForQuery(query); + } + + // return the first that we can use + foreach (const QNetworkProxy &p, proxies) { + if (socketType == QAbstractSocket::UdpSocket && + (p.capabilities() & QNetworkProxy::UdpTunnelingCapability) == 0) + continue; + + if (socketType == QAbstractSocket::TcpSocket && + (p.capabilities() & QNetworkProxy::TunnelingCapability) == 0) + continue; + + proxyInUse = p; + return; + } + + // no proxy found + // DefaultProxy here will raise an error + proxyInUse = QNetworkProxy(); +} + +/*! + \internal + + Starts the connection to \a host, like _q_startConnecting below, + but without hostname resolution. +*/ +void QAbstractSocketPrivate::startConnectingByName(const QString &host) +{ + Q_Q(QAbstractSocket); + if (state == QAbstractSocket::ConnectingState || state == QAbstractSocket::ConnectedState) + return; + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::startConnectingByName(host == %s)", qPrintable(host)); +#endif + + // ### Let the socket engine drive this? + state = QAbstractSocket::ConnectingState; + emit q->stateChanged(state); + + connectTimeElapsed = 0; + + if (initSocketLayer(QAbstractSocket::UnknownNetworkLayerProtocol)) { + if (socketEngine->connectToHostByName(host, port) || + socketEngine->state() == QAbstractSocket::ConnectingState) { + cachedSocketDescriptor = socketEngine->socketDescriptor(); + + return; + } + + // failed to connect + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + } + + state = QAbstractSocket::UnconnectedState; + emit q->error(socketError); + emit q->stateChanged(state); +} + +#endif + +/*! \internal + + Slot connected to QHostInfo::lookupHost() in connectToHost(). This + function starts the process of connecting to any number of + candidate IP addresses for the host, if it was found. Calls + _q_connectToNextAddress(). +*/ +void QAbstractSocketPrivate::_q_startConnecting(const QHostInfo &hostInfo) +{ + Q_Q(QAbstractSocket); + if (state != QAbstractSocket::HostLookupState) + return; + + if (hostLookupId != -1 && hostLookupId != hostInfo.lookupId()) { + qWarning("QAbstractSocketPrivate::_q_startConnecting() received hostInfo for wrong lookup ID %d expected %d", hostInfo.lookupId(), hostLookupId); + } + + addresses = hostInfo.addresses(); + +#if defined(QABSTRACTSOCKET_DEBUG) + QString s = QLatin1String("{"); + for (int i = 0; i < addresses.count(); ++i) { + if (i != 0) s += QLatin1String(", "); + s += addresses.at(i).toString(); + } + s += QLatin1Char('}'); + qDebug("QAbstractSocketPrivate::_q_startConnecting(hostInfo == %s)", s.toLatin1().constData()); +#endif + + // Try all addresses twice. + addresses += addresses; + + // If there are no addresses in the host list, report this to the + // user. + if (addresses.isEmpty()) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_startConnecting(), host not found"); +#endif + state = QAbstractSocket::UnconnectedState; + socketError = QAbstractSocket::HostNotFoundError; + q->setErrorString(QAbstractSocket::tr("Host not found")); + emit q->stateChanged(state); + emit q->error(QAbstractSocket::HostNotFoundError); + return; + } + + // Enter Connecting state (see also sn_write, which is called by + // the write socket notifier after connect()) + state = QAbstractSocket::ConnectingState; + emit q->stateChanged(state); + + // Report the successful host lookup + emit q->hostFound(); + + // Reset the total time spent connecting. + connectTimeElapsed = 0; + + // The addresses returned by the lookup will be tested one after + // another by _q_connectToNextAddress(). + _q_connectToNextAddress(); +} + +/*! \internal + + Called by a queued or direct connection from _q_startConnecting() or + _q_testConnection(), this function takes the first address of the + pending addresses list and tries to connect to it. If the + connection succeeds, QAbstractSocket will emit + connected(). Otherwise, error(ConnectionRefusedError) or + error(SocketTimeoutError) is emitted. +*/ +void QAbstractSocketPrivate::_q_connectToNextAddress() +{ + Q_Q(QAbstractSocket); + do { + // Check for more pending addresses + if (addresses.isEmpty()) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), all addresses failed."); +#endif + state = QAbstractSocket::UnconnectedState; + if (socketEngine) { + if ((socketEngine->error() == QAbstractSocket::UnknownSocketError +#ifdef Q_OS_AIX + // On AIX, the second connect call will result in EINVAL and not + // ECONNECTIONREFUSED; although the meaning is the same. + || socketEngine->error() == QAbstractSocket::UnsupportedSocketOperationError +#endif + ) && socketEngine->state() == QAbstractSocket::ConnectingState) { + socketError = QAbstractSocket::ConnectionRefusedError; + q->setErrorString(QAbstractSocket::tr("Connection refused")); + } else { + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + } + } else { +// socketError = QAbstractSocket::ConnectionRefusedError; +// q->setErrorString(QAbstractSocket::tr("Connection refused")); + } + emit q->stateChanged(state); + emit q->error(socketError); + return; + } + + // Pick the first host address candidate + host = addresses.takeFirst(); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), connecting to %s:%i, %d left to try", + host.toString().toLatin1().constData(), port, addresses.count()); +#endif + +#if defined(QT_NO_IPV6) + if (host.protocol() == QAbstractSocket::IPv6Protocol) { + // If we have no IPv6 support, then we will not be able to + // connect. So we just pretend we didn't see this address. +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), skipping IPv6 entry"); +#endif + continue; + } +#endif + + if (!initSocketLayer(host.protocol())) { + // hope that the next address is better +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), failed to initialize sock layer"); +#endif + continue; + } + + // Tries to connect to the address. If it succeeds immediately + // (localhost address on BSD or any UDP connect), emit + // connected() and return. + if (socketEngine->connectToHost(host, port)) { + //_q_testConnection(); + fetchConnectionParameters(); + return; + } + + // cache the socket descriptor even if we're not fully connected yet + cachedSocketDescriptor = socketEngine->socketDescriptor(); + + // Check that we're in delayed connection state. If not, try + // the next address + if (socketEngine->state() != QAbstractSocket::ConnectingState) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), connection failed (%s)", + socketEngine->errorString().toLatin1().constData()); +#endif + continue; + } + + // Start the connect timer. + if (threadData->eventDispatcher) { + if (!connectTimer) { + connectTimer = new QTimer(q); + QObject::connect(connectTimer, SIGNAL(timeout()), + q, SLOT(_q_abortConnectionAttempt()), + Qt::DirectConnection); + } + connectTimer->start(QT_CONNECT_TIMEOUT); + } + + // Wait for a write notification that will eventually call + // _q_testConnection(). + socketEngine->setWriteNotificationEnabled(true); + break; + } while (state != QAbstractSocket::ConnectedState); +} + +/*! \internal + + Tests if a connection has been established. If it has, connected() + is emitted. Otherwise, _q_connectToNextAddress() is invoked. +*/ +void QAbstractSocketPrivate::_q_testConnection() +{ + if (socketEngine) { + if (threadData->eventDispatcher) { + if (connectTimer) + connectTimer->stop(); + } + + if (socketEngine->state() == QAbstractSocket::ConnectedState) { + // Fetch the parameters if our connection is completed; + // otherwise, fall out and try the next address. + fetchConnectionParameters(); + if (pendingClose) { + q_func()->disconnectFromHost(); + pendingClose = false; + } + return; + } + + // don't retry the other addresses if we had a proxy error + if (isProxyError(socketEngine->error())) + addresses.clear(); + } + + if (threadData->eventDispatcher) { + if (connectTimer) + connectTimer->stop(); + } + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_testConnection() connection failed," + " checking for alternative addresses"); +#endif + _q_connectToNextAddress(); +} + +/*! \internal + + This function is called after a certain number of seconds has + passed while waiting for a connection. It simply tests the + connection, and continues to the next address if the connection + failed. +*/ +void QAbstractSocketPrivate::_q_abortConnectionAttempt() +{ + Q_Q(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_abortConnectionAttempt() (timed out)"); +#endif + if (socketEngine) + socketEngine->setWriteNotificationEnabled(false); + + connectTimer->stop(); + + if (addresses.isEmpty()) { + state = QAbstractSocket::UnconnectedState; + socketError = QAbstractSocket::SocketTimeoutError; + q->setErrorString(QAbstractSocket::tr("Connection timed out")); + emit q->stateChanged(state); + emit q->error(socketError); + } else { + _q_connectToNextAddress(); + } +} + +void QAbstractSocketPrivate::_q_forceDisconnect() +{ + Q_Q(QAbstractSocket); + if (socketEngine && socketEngine->isValid() && state == QAbstractSocket::ClosingState) { + socketEngine->close(); + q->disconnectFromHost(); + } +} + +/*! \internal + + Reads data from the socket layer into the read buffer. Returns + true on success; otherwise false. +*/ +bool QAbstractSocketPrivate::readFromSocket() +{ + Q_Q(QAbstractSocket); + // Find how many bytes we can read from the socket layer. + qint64 bytesToRead = socketEngine->bytesAvailable(); + if (bytesToRead == 0) { + // Under heavy load, certain conditions can trigger read notifications + // for socket notifiers on which there is no activity. If we continue + // to read 0 bytes from the socket, we will trigger behavior similar + // to that which signals a remote close. When we hit this condition, + // we try to read 4k of data from the socket, which will give us either + // an EAGAIN/EWOULDBLOCK if the connection is alive (i.e., the remote + // host has _not_ disappeared). + bytesToRead = 4096; + } + if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) + bytesToRead = readBufferMaxSize - readBuffer.size(); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::readFromSocket() about to read %d bytes", + int(bytesToRead)); +#endif + + // Read from the socket, store data in the read buffer. + char *ptr = readBuffer.reserve(bytesToRead); + qint64 readBytes = socketEngine->read(ptr, bytesToRead); + if (readBytes == -2) { + // No bytes currently available for reading. + readBuffer.chop(bytesToRead); + return true; + } + readBuffer.chop(int(bytesToRead - (readBytes < 0 ? qint64(0) : readBytes))); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::readFromSocket() got %d bytes, buffer size = %d", + int(readBytes), readBuffer.size()); +#endif + + if (!socketEngine->isValid()) { + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + emit q->error(socketError); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::readFromSocket() read failed: %s", + q->errorString().toLatin1().constData()); +#endif + resetSocketLayer(); + return false; + } + + return true; +} + +/*! \internal + + Sets up the internal state after the connection has succeeded. +*/ +void QAbstractSocketPrivate::fetchConnectionParameters() +{ + Q_Q(QAbstractSocket); + + peerName = hostName; + if (socketEngine) { + socketEngine->setReadNotificationEnabled(true); + socketEngine->setWriteNotificationEnabled(true); + localPort = socketEngine->localPort(); + peerPort = socketEngine->peerPort(); + localAddress = socketEngine->localAddress(); + peerAddress = socketEngine->peerAddress(); + cachedSocketDescriptor = socketEngine->socketDescriptor(); + } + + state = QAbstractSocket::ConnectedState; + emit q->stateChanged(state); + emit q->connected(); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::fetchConnectionParameters() connection to %s:%i established", + host.toString().toLatin1().constData(), port); +#endif +} + + +void QAbstractSocketPrivate::pauseSocketNotifiers(QAbstractSocket *socket) +{ + QAbstractSocketEngine *socketEngine = socket->d_func()->socketEngine; + if (!socketEngine) + return; + socket->d_func()->prePauseReadSocketNotifierState = socketEngine->isReadNotificationEnabled(); + socket->d_func()->prePauseWriteSocketNotifierState = socketEngine->isWriteNotificationEnabled(); + socket->d_func()->prePauseExceptionSocketNotifierState = socketEngine->isExceptionNotificationEnabled(); + socketEngine->setReadNotificationEnabled(false); + socketEngine->setWriteNotificationEnabled(false); + socketEngine->setExceptionNotificationEnabled(false); +} + +void QAbstractSocketPrivate::resumeSocketNotifiers(QAbstractSocket *socket) +{ + QAbstractSocketEngine *socketEngine = socket->d_func()->socketEngine; + if (!socketEngine) + return; + socketEngine->setReadNotificationEnabled(socket->d_func()->prePauseReadSocketNotifierState); + socketEngine->setWriteNotificationEnabled(socket->d_func()->prePauseWriteSocketNotifierState); + socketEngine->setExceptionNotificationEnabled(socket->d_func()->prePauseExceptionSocketNotifierState); +} + +QAbstractSocketEngine* QAbstractSocketPrivate::getSocketEngine(QAbstractSocket *socket) +{ + return socket->d_func()->socketEngine; +} + + +/*! \internal + + Constructs a new abstract socket of type \a socketType. The \a + parent argument is passed to QObject's constructor. +*/ +QAbstractSocket::QAbstractSocket(SocketType socketType, + QAbstractSocketPrivate &dd, QObject *parent) + : QIODevice(dd, parent) +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::QAbstractSocket(%sSocket, QAbstractSocketPrivate == %p, parent == %p)", + socketType == TcpSocket ? "Tcp" : socketType == UdpSocket + ? "Udp" : "Unknown", &dd, parent); +#endif + d->socketType = socketType; +} + +/*! + Creates a new abstract socket of type \a socketType. The \a + parent argument is passed to QObject's constructor. + + \sa socketType(), QTcpSocket, QUdpSocket +*/ +QAbstractSocket::QAbstractSocket(SocketType socketType, QObject *parent) + : QIODevice(*new QAbstractSocketPrivate, parent) +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::QAbstractSocket(%p)", parent); +#endif + d->socketType = socketType; +} + +/*! + Destroys the socket. +*/ +QAbstractSocket::~QAbstractSocket() +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::~QAbstractSocket()"); +#endif + if (d->state != UnconnectedState) + abort(); +} + +/*! + Returns true if the socket is valid and ready for use; otherwise + returns false. + + \bold{Note:} The socket's state must be ConnectedState before reading and + writing can occur. + + \sa state() +*/ +bool QAbstractSocket::isValid() const +{ + return d_func()->socketEngine ? d_func()->socketEngine->isValid() : isOpen(); +} + +/*! + Attempts to make a connection to \a hostName on the given \a port. + + The socket is opened in the given \a openMode and first enters + HostLookupState, then performs a host name lookup of \a hostName. + If the lookup succeeds, hostFound() is emitted and QAbstractSocket + enters ConnectingState. It then attempts to connect to the address + or addresses returned by the lookup. Finally, if a connection is + established, QAbstractSocket enters ConnectedState and + emits connected(). + + At any point, the socket can emit error() to signal that an error + occurred. + + \a hostName may be an IP address in string form (e.g., + "43.195.83.32"), or it may be a host name (e.g., + "example.com"). QAbstractSocket will do a lookup only if + required. \a port is in native byte order. + + \sa state(), peerName(), peerAddress(), peerPort(), waitForConnected() +*/ +void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, + OpenMode openMode) +{ + QMetaObject::invokeMethod(this, "connectToHostImplementation", + Qt::DirectConnection, + Q_ARG(QString, hostName), + Q_ARG(quint16, port), + Q_ARG(OpenMode, openMode)); +} + +/*! + \since 4.1 + + Contains the implementation of connectToHost(). + + Attempts to make a connection to \a hostName on the given \a + port. The socket is opened in the given \a openMode. +*/ +void QAbstractSocket::connectToHostImplementation(const QString &hostName, quint16 port, + OpenMode openMode) +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::connectToHost(\"%s\", %i, %i)...", qPrintable(hostName), port, + (int) openMode); +#endif + + if (d->state == ConnectedState || d->state == ConnectingState + || d->state == ClosingState || d->state == HostLookupState) { + qWarning("QAbstractSocket::connectToHost() called when already looking up or connecting/connected to \"%s\"", qPrintable(hostName)); + return; + } + + d->hostName = hostName; + d->port = port; + d->state = UnconnectedState; + d->readBuffer.clear(); + d->writeBuffer.clear(); + d->abortCalled = false; + d->closeCalled = false; + d->pendingClose = false; + d->localPort = 0; + d->peerPort = 0; + d->localAddress.clear(); + d->peerAddress.clear(); + d->peerName = hostName; + if (d->hostLookupId != -1) { + QHostInfo::abortHostLookup(d->hostLookupId); + d->hostLookupId = -1; + } + +#ifndef QT_NO_NETWORKPROXY + // Get the proxy information + d->resolveProxy(hostName, port); + if (d->proxyInUse.type() == QNetworkProxy::DefaultProxy) { + // failed to setup the proxy + d->socketError = QAbstractSocket::UnsupportedSocketOperationError; + setErrorString(QAbstractSocket::tr("Operation on socket is not supported")); + emit error(d->socketError); + return; + } +#endif + + if (openMode & QIODevice::Unbuffered) + d->isBuffered = false; // Unbuffered QTcpSocket + else if (!d_func()->isBuffered) + openMode |= QAbstractSocket::Unbuffered; // QUdpSocket + + QIODevice::open(openMode); + d->state = HostLookupState; + emit stateChanged(d->state); + + QHostAddress temp; + if (temp.setAddress(hostName)) { + QHostInfo info; + info.setAddresses(QList<QHostAddress>() << temp); + d->_q_startConnecting(info); +#ifndef QT_NO_NETWORKPROXY + } else if (d->proxyInUse.capabilities() & QNetworkProxy::HostNameLookupCapability) { + // the proxy supports connection by name, so use it + d->startConnectingByName(hostName); + return; +#endif + } else { + if (d->threadData->eventDispatcher) { + // this internal API for QHostInfo either immediately gives us the desired + // QHostInfo from cache or later calls the _q_startConnecting slot. + bool immediateResultValid = false; + QHostInfo hostInfo = qt_qhostinfo_lookup(hostName, + this, + SLOT(_q_startConnecting(QHostInfo)), + &immediateResultValid, + &d->hostLookupId); + if (immediateResultValid) { + d->hostLookupId = -1; + d->_q_startConnecting(hostInfo); + } + } + } + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::connectToHost(\"%s\", %i) == %s%s", hostName.toLatin1().constData(), port, + (d->state == ConnectedState) ? "true" : "false", + (d->state == ConnectingState || d->state == HostLookupState) + ? " (connection in progress)" : ""); +#endif +} + +/*! \overload + + Attempts to make a connection to \a address on port \a port. +*/ +void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, + OpenMode openMode) +{ +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::connectToHost([%s], %i, %i)...", + address.toString().toLatin1().constData(), port, (int) openMode); +#endif + connectToHost(address.toString(), port, openMode); +} + +/*! + Returns the number of bytes that are waiting to be written. The + bytes are written when control goes back to the event loop or + when flush() is called. + + \sa bytesAvailable(), flush() +*/ +qint64 QAbstractSocket::bytesToWrite() const +{ + Q_D(const QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::bytesToWrite() == %i", d->writeBuffer.size()); +#endif + return (qint64)d->writeBuffer.size(); +} + +/*! + Returns the number of incoming bytes that are waiting to be read. + + \sa bytesToWrite(), read() +*/ +qint64 QAbstractSocket::bytesAvailable() const +{ + Q_D(const QAbstractSocket); + qint64 available = QIODevice::bytesAvailable(); + + available += (qint64) d->readBuffer.size(); + + if (!d->isBuffered && d->socketEngine && d->socketEngine->isValid()) + available += d->socketEngine->bytesAvailable(); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::bytesAvailable() == %llu", available); +#endif + return available; +} + +/*! + Returns the host port number (in native byte order) of the local + socket if available; otherwise returns 0. + + \sa localAddress(), peerPort(), setLocalPort() +*/ +quint16 QAbstractSocket::localPort() const +{ + Q_D(const QAbstractSocket); + return d->localPort; +} + +/*! + Returns the host address of the local socket if available; + otherwise returns QHostAddress::Null. + + This is normally the main IP address of the host, but can be + QHostAddress::LocalHost (127.0.0.1) for connections to the + local host. + + \sa localPort(), peerAddress(), setLocalAddress() +*/ +QHostAddress QAbstractSocket::localAddress() const +{ + Q_D(const QAbstractSocket); + return d->localAddress; +} + +/*! + Returns the port of the connected peer if the socket is in + ConnectedState; otherwise returns 0. + + \sa peerAddress(), localPort(), setPeerPort() +*/ +quint16 QAbstractSocket::peerPort() const +{ + Q_D(const QAbstractSocket); + return d->peerPort; +} + +/*! + Returns the address of the connected peer if the socket is in + ConnectedState; otherwise returns QHostAddress::Null. + + \sa peerName(), peerPort(), localAddress(), setPeerAddress() +*/ +QHostAddress QAbstractSocket::peerAddress() const +{ + Q_D(const QAbstractSocket); + return d->peerAddress; +} + +/*! + Returns the name of the peer as specified by connectToHost(), or + an empty QString if connectToHost() has not been called. + + \sa peerAddress(), peerPort(), setPeerName() +*/ +QString QAbstractSocket::peerName() const +{ + Q_D(const QAbstractSocket); + return d->peerName.isEmpty() ? d->hostName : d->peerName; +} + +/*! + Returns true if a line of data can be read from the socket; + otherwise returns false. + + \sa readLine() +*/ +bool QAbstractSocket::canReadLine() const +{ + bool hasLine = d_func()->readBuffer.canReadLine(); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::canReadLine() == %s, buffer size = %d, size = %d", hasLine ? "true" : "false", + d_func()->readBuffer.size(), d_func()->buffer.size()); +#endif + return hasLine || QIODevice::canReadLine(); +} + +/*! + Returns the native socket descriptor of the QAbstractSocket object + if this is available; otherwise returns -1. + + If the socket is using QNetworkProxy, the returned descriptor + may not be usable with native socket functions. + + The socket descriptor is not available when QAbstractSocket is in + UnconnectedState. + + \sa setSocketDescriptor() +*/ +int QAbstractSocket::socketDescriptor() const +{ + Q_D(const QAbstractSocket); + return d->cachedSocketDescriptor; +} + +/*! + Initializes QAbstractSocket with the native socket descriptor \a + socketDescriptor. Returns true if \a socketDescriptor is accepted + as a valid socket descriptor; otherwise returns false. + The socket is opened in the mode specified by \a openMode, and + enters the socket state specified by \a socketState. + + \bold{Note:} It is not possible to initialize two abstract sockets + with the same native socket descriptor. + + \sa socketDescriptor() +*/ +bool QAbstractSocket::setSocketDescriptor(int socketDescriptor, SocketState socketState, + OpenMode openMode) +{ + Q_D(QAbstractSocket); +#ifndef QT_NO_OPENSSL + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->setSocketDescriptor(socketDescriptor, socketState, openMode); +#endif + + d->resetSocketLayer(); + d->socketEngine = QAbstractSocketEngine::createSocketEngine(socketDescriptor, this); +#ifndef QT_NO_BEARERMANAGEMENT + //copy network session down to the socket engine (if it has been set) + d->socketEngine->setProperty("_q_networksession", property("_q_networksession")); +#endif + if (!d->socketEngine) { + d->socketError = UnsupportedSocketOperationError; + setErrorString(tr("Operation on socket is not supported")); + return false; + } + bool result = d->socketEngine->initialize(socketDescriptor, socketState); + if (!result) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + return false; + } + + if (d->threadData->eventDispatcher) + d->socketEngine->setReceiver(d); + + QIODevice::open(openMode); + + if (d->state != socketState) { + d->state = socketState; + emit stateChanged(d->state); + } + + d->pendingClose = false; + d->socketEngine->setReadNotificationEnabled(true); + d->localPort = d->socketEngine->localPort(); + d->peerPort = d->socketEngine->peerPort(); + d->localAddress = d->socketEngine->localAddress(); + d->peerAddress = d->socketEngine->peerAddress(); + d->cachedSocketDescriptor = socketDescriptor; + + return true; +} + +/*! + \since 4.6 + Sets the given \a option to the value described by \a value. + + \sa socketOption() +*/ +void QAbstractSocket::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value) +{ +#ifndef QT_NO_OPENSSL + if (QSslSocket *sslSocket = qobject_cast<QSslSocket*>(this)) { + sslSocket->setSocketOption(option, value); + return; + } +#endif + + if (!d_func()->socketEngine) + return; + + switch (option) { + case LowDelayOption: + d_func()->socketEngine->setOption(QAbstractSocketEngine::LowDelayOption, value.toInt()); + break; + + case KeepAliveOption: + d_func()->socketEngine->setOption(QAbstractSocketEngine::KeepAliveOption, value.toInt()); + break; + + case MulticastTtlOption: + d_func()->socketEngine->setOption(QAbstractSocketEngine::MulticastTtlOption, value.toInt()); + break; + + case MulticastLoopbackOption: + d_func()->socketEngine->setOption(QAbstractSocketEngine::MulticastLoopbackOption, value.toInt()); + break; + } +} + +/*! + \since 4.6 + Returns the value of the \a option option. + + \sa setSocketOption() +*/ +QVariant QAbstractSocket::socketOption(QAbstractSocket::SocketOption option) +{ +#ifndef QT_NO_OPENSSL + if (QSslSocket *sslSocket = qobject_cast<QSslSocket*>(this)) { + return sslSocket->socketOption(option); + } +#endif + + if (!d_func()->socketEngine) + return QVariant(); + + int ret = -1; + switch (option) { + case LowDelayOption: + ret = d_func()->socketEngine->option(QAbstractSocketEngine::LowDelayOption); + break; + + case KeepAliveOption: + ret = d_func()->socketEngine->option(QAbstractSocketEngine::KeepAliveOption); + break; + + case MulticastTtlOption: + ret = d_func()->socketEngine->option(QAbstractSocketEngine::MulticastTtlOption); + break; + case MulticastLoopbackOption: + ret = d_func()->socketEngine->option(QAbstractSocketEngine::MulticastLoopbackOption); + break; + } + if (ret == -1) + return QVariant(); + else + return QVariant(ret); +} + + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +/*! + Waits until the socket is connected, up to \a msecs + milliseconds. If the connection has been established, this + function returns true; otherwise it returns false. In the case + where it returns false, you can call error() to determine + the cause of the error. + + The following example waits up to one second for a connection + to be established: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 0 + + If msecs is -1, this function will not time out. + + \note This function may wait slightly longer than \a msecs, + depending on the time it takes to complete the host lookup. + + \note Multiple calls to this functions do not accumulate the time. + If the function times out, the connecting process will be aborted. + + \sa connectToHost(), connected() +*/ +bool QAbstractSocket::waitForConnected(int msecs) +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i)", msecs); +#endif + + if (state() == ConnectedState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) already connected", msecs); +#endif + return true; + } + +#ifndef QT_NO_OPENSSL + // Manual polymorphism; this function is not virtual, but has an overload + // in QSslSocket. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->waitForConnected(msecs); +#endif + + bool wasPendingClose = d->pendingClose; + d->pendingClose = false; + QElapsedTimer stopWatch; + stopWatch.start(); + + if (d->state == HostLookupState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) doing host name lookup", msecs); +#endif + QHostInfo::abortHostLookup(d->hostLookupId); + d->hostLookupId = -1; +#ifndef QT_NO_BEARERMANAGEMENT + QSharedPointer<QNetworkSession> networkSession; + QVariant v(property("_q_networksession")); + if (v.isValid()) { + networkSession = qvariant_cast< QSharedPointer<QNetworkSession> >(v); + d->_q_startConnecting(QHostInfoPrivate::fromName(d->hostName, networkSession)); + } else +#endif + d->_q_startConnecting(QHostInfo::fromName(d->hostName)); + } + if (state() == UnconnectedState) + return false; // connect not im progress anymore! + + bool timedOut = true; +#if defined (QABSTRACTSOCKET_DEBUG) + int attempt = 1; +#endif + while (state() == ConnectingState && (msecs == -1 || stopWatch.elapsed() < msecs)) { + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + if (msecs != -1 && timeout > QT_CONNECT_TIMEOUT) + timeout = QT_CONNECT_TIMEOUT; +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) waiting %.2f secs for connection attempt #%i", + msecs, timeout / 1000.0, attempt++); +#endif + timedOut = false; + + if (d->socketEngine && d->socketEngine->waitForWrite(timeout, &timedOut) && !timedOut) { + d->_q_testConnection(); + } else { + d->_q_connectToNextAddress(); + } + } + + if ((timedOut && state() != ConnectedState) || state() == ConnectingState) { + d->socketError = SocketTimeoutError; + d->state = UnconnectedState; + emit stateChanged(d->state); + d->resetSocketLayer(); + setErrorString(tr("Socket operation timed out")); + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) == %s", msecs, + state() == ConnectedState ? "true" : "false"); +#endif + if (state() != ConnectedState) + return false; + if (wasPendingClose) + disconnectFromHost(); + return true; +} + +/*! + This function blocks until new data is available for reading and the + \l{QIODevice::}{readyRead()} signal has been emitted. The function + will timeout after \a msecs milliseconds; the default timeout is + 30000 milliseconds. + + The function returns true if the readyRead() signal is emitted and + there is new data available for reading; otherwise it returns false + (if an error occurred or the operation timed out). + + \sa waitForBytesWritten() +*/ +bool QAbstractSocket::waitForReadyRead(int msecs) +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForReadyRead(%i)", msecs); +#endif + + // require calling connectToHost() before waitForReadyRead() + if (state() == UnconnectedState) { + /* If all you have is a QIODevice pointer to an abstractsocket, you cannot check + this, so you cannot avoid this warning. */ +// qWarning("QAbstractSocket::waitForReadyRead() is not allowed in UnconnectedState"); + return false; + } + + QElapsedTimer stopWatch; + stopWatch.start(); + + // handle a socket in connecting state + if (state() == HostLookupState || state() == ConnectingState) { + if (!waitForConnected(msecs)) + return false; + } + + Q_ASSERT(d->socketEngine); + forever { + bool readyToRead = false; + bool readyToWrite = false; + if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite, true, !d->writeBuffer.isEmpty(), + qt_timeout_value(msecs, stopWatch.elapsed()))) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForReadyRead(%i) failed (%i, %s)", + msecs, d->socketError, errorString().toLatin1().constData()); +#endif + emit error(d->socketError); + if (d->socketError != SocketTimeoutError) + close(); + return false; + } + + if (readyToRead) { + if (d->canReadNotification()) + return true; + } + + if (readyToWrite) + d->canWriteNotification(); + + if (state() != ConnectedState) + return false; + } + return false; +} + +/*! \reimp + */ +bool QAbstractSocket::waitForBytesWritten(int msecs) +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten(%i)", msecs); +#endif + + // require calling connectToHost() before waitForBytesWritten() + if (state() == UnconnectedState) { + qWarning("QAbstractSocket::waitForBytesWritten() is not allowed in UnconnectedState"); + return false; + } + + if (d->writeBuffer.isEmpty()) + return false; + + QElapsedTimer stopWatch; + stopWatch.start(); + + // handle a socket in connecting state + if (state() == HostLookupState || state() == ConnectingState) { + if (!waitForConnected(msecs)) + return false; + } + + forever { + bool readyToRead = false; + bool readyToWrite = false; + if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite, true, !d->writeBuffer.isEmpty(), + qt_timeout_value(msecs, stopWatch.elapsed()))) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten(%i) failed (%i, %s)", + msecs, d->socketError, errorString().toLatin1().constData()); +#endif + emit error(d->socketError); + if (d->socketError != SocketTimeoutError) + close(); + return false; + } + + if (readyToRead) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten calls canReadNotification"); +#endif + if(!d->canReadNotification()) + return false; + } + + + if (readyToWrite) { + if (d->canWriteNotification()) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten returns true"); +#endif + return true; + } + } + + if (state() != ConnectedState) + return false; + } + return false; +} + +/*! + Waits until the socket has disconnected, up to \a msecs + milliseconds. If the connection has been disconnected, this + function returns true; otherwise it returns false. In the case + where it returns false, you can call error() to determine + the cause of the error. + + The following example waits up to one second for a connection + to be closed: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 1 + + If msecs is -1, this function will not time out. + + \sa disconnectFromHost(), close() +*/ +bool QAbstractSocket::waitForDisconnected(int msecs) +{ + Q_D(QAbstractSocket); +#ifndef QT_NO_OPENSSL + // Manual polymorphism; this function is not virtual, but has an overload + // in QSslSocket. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->waitForDisconnected(msecs); +#endif + + // require calling connectToHost() before waitForDisconnected() + if (state() == UnconnectedState) { + qWarning("QAbstractSocket::waitForDisconnected() is not allowed in UnconnectedState"); + return false; + } + + QElapsedTimer stopWatch; + stopWatch.start(); + + // handle a socket in connecting state + if (state() == HostLookupState || state() == ConnectingState) { + if (!waitForConnected(msecs)) + return false; + if (state() == UnconnectedState) + return true; + } + + forever { + bool readyToRead = false; + bool readyToWrite = false; + if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite, state() == ConnectedState, + !d->writeBuffer.isEmpty(), + qt_timeout_value(msecs, stopWatch.elapsed()))) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForReadyRead(%i) failed (%i, %s)", + msecs, d->socketError, errorString().toLatin1().constData()); +#endif + emit error(d->socketError); + if (d->socketError != SocketTimeoutError) + close(); + return false; + } + + if (readyToRead) + d->canReadNotification(); + if (readyToWrite) + d->canWriteNotification(); + + if (state() == UnconnectedState) + return true; + } + return false; +} + +/*! + Aborts the current connection and resets the socket. Unlike disconnectFromHost(), + this function immediately closes the socket, discarding any pending data in the + write buffer. + + \sa disconnectFromHost(), close() +*/ +void QAbstractSocket::abort() +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::abort()"); +#endif + if (d->state == UnconnectedState) + return; +#ifndef QT_NO_OPENSSL + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) { + socket->abort(); + return; + } +#endif + if (d->connectTimer) { + d->connectTimer->stop(); + delete d->connectTimer; + d->connectTimer = 0; + } + + d->writeBuffer.clear(); + d->abortCalled = true; + close(); +} + +/*! \reimp +*/ +bool QAbstractSocket::isSequential() const +{ + return true; +} + +/*! \reimp + + Returns true if no more data is currently + available for reading; otherwise returns false. + + This function is most commonly used when reading data from the + socket in a loop. For example: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 2 + + \sa bytesAvailable(), readyRead() + */ +bool QAbstractSocket::atEnd() const +{ + return QIODevice::atEnd() && (!isOpen() || d_func()->readBuffer.isEmpty()); +} + +/*! + This function writes as much as possible from the internal write buffer to + the underlying network socket, without blocking. If any data was written, + this function returns true; otherwise false is returned. + + Call this function if you need QAbstractSocket to start sending buffered + data immediately. The number of bytes successfully written depends on the + operating system. In most cases, you do not need to call this function, + because QAbstractSocket will start sending data automatically once control + goes back to the event loop. In the absence of an event loop, call + waitForBytesWritten() instead. + + \sa write(), waitForBytesWritten() +*/ +// Note! docs copied to QSslSocket::flush() +bool QAbstractSocket::flush() +{ + Q_D(QAbstractSocket); +#ifndef QT_NO_OPENSSL + // Manual polymorphism; flush() isn't virtual, but QSslSocket overloads + // it. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->flush(); +#endif + Q_CHECK_SOCKETENGINE(false); + return d->flush(); +} + +/*! \reimp +*/ +qint64 QAbstractSocket::readData(char *data, qint64 maxSize) +{ + Q_D(QAbstractSocket); + + // This is for a buffered QTcpSocket + if (d->isBuffered && d->readBuffer.isEmpty()) + // if we're still connected, return 0 indicating there may be more data in the future + // if we're not connected, return -1 indicating EOF + return d->state == QAbstractSocket::ConnectedState ? qint64(0) : qint64(-1); + + // short cut for a char read if we have something in the buffer + if (maxSize == 1 && !d->readBuffer.isEmpty()) { + *data = d->readBuffer.getChar(); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p '%c (0x%.2x)', 1) == 1 [char buffer]", + data, isprint(int(uchar(*data))) ? *data : '?', *data); +#endif + if (d->readBuffer.isEmpty() && d->socketEngine && d->socketEngine->isValid()) + d->socketEngine->setReadNotificationEnabled(true); + return 1; + } + + // Special case for an Unbuffered QTcpSocket + // Re-filling the buffer. + if (d->socketType == TcpSocket + && !d->isBuffered + && d->readBuffer.size() < maxSize + && d->readBufferMaxSize > 0 + && maxSize < d->readBufferMaxSize + && d->socketEngine + && d->socketEngine->isValid()) { + // Our buffer is empty and a read() was requested for a byte amount that is smaller + // than the readBufferMaxSize. This means that we should fill our buffer since we want + // such small reads come from the buffer and not always go to the costly socket engine read() + qint64 bytesToRead = d->socketEngine->bytesAvailable(); + if (bytesToRead > 0) { + char *ptr = d->readBuffer.reserve(bytesToRead); + qint64 readBytes = d->socketEngine->read(ptr, bytesToRead); + if (readBytes == -2) { + // No bytes currently available for reading. + d->readBuffer.chop(bytesToRead); + } else { + d->readBuffer.chop(int(bytesToRead - (readBytes < 0 ? qint64(0) : readBytes))); + } + } + } + + // First try to satisfy the read from the buffer + qint64 bytesToRead = qMin(qint64(d->readBuffer.size()), maxSize); + qint64 readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = d->readBuffer.readPointer(); + int bytesToReadFromThisBlock = qMin(int(bytesToRead - readSoFar), + d->readBuffer.nextDataBlockSize()); + memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + d->readBuffer.free(bytesToReadFromThisBlock); + } + + if (d->socketEngine && !d->socketEngine->isReadNotificationEnabled() && d->socketEngine->isValid()) + d->socketEngine->setReadNotificationEnabled(true); + + if (readSoFar > 0) { + // we read some data from buffer. + // Just return, readyRead will be emitted again +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p '%c (0x%.2x)', %lli) == %lli [buffer]", + data, isprint(int(uchar(*data))) ? *data : '?', *data, maxSize, readSoFar); +#endif + + if (d->readBuffer.isEmpty() && d->socketEngine) + d->socketEngine->setReadNotificationEnabled(true); + return readSoFar; + } + + // This code path is for Unbuffered QTcpSocket or for connected UDP + + if (!d->isBuffered) { + if (!d->socketEngine) + return -1; // no socket engine is probably EOF + if (!d->socketEngine->isValid()) + return -1; // This is for unbuffered TCP when we already had been disconnected + if (d->state != QAbstractSocket::ConnectedState) + return -1; // This is for unbuffered TCP if we're not connected yet + qint64 readBytes = d->socketEngine->read(data, maxSize); + if (readBytes == -2) { + // -2 from the engine means no bytes available (EAGAIN) so read more later + return 0; + } else if (readBytes < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + d->resetSocketLayer(); + d->state = QAbstractSocket::UnconnectedState; + } else if (!d->socketEngine->isReadNotificationEnabled()) { + // Only do this when there was no error + d->socketEngine->setReadNotificationEnabled(true); + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p \"%s\", %lli) == %lld [engine]", + data, qt_prettyDebug(data, 32, readBytes).data(), maxSize, + readBytes); +#endif + return readBytes; + } + + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p \"%s\", %lli) == %lld [unreachable]", + data, qt_prettyDebug(data, qMin<qint64>(32, readSoFar), readSoFar).data(), + maxSize, readSoFar); +#endif + return readSoFar; +} + +/*! \reimp +*/ +qint64 QAbstractSocket::readLineData(char *data, qint64 maxlen) +{ + return QIODevice::readLineData(data, maxlen); +} + +/*! \reimp +*/ +qint64 QAbstractSocket::writeData(const char *data, qint64 size) +{ + Q_D(QAbstractSocket); + if (d->state == QAbstractSocket::UnconnectedState) { + d->socketError = QAbstractSocket::UnknownSocketError; + setErrorString(tr("Socket is not connected")); + return -1; + } + + if (!d->isBuffered && d->socketType == TcpSocket && d->writeBuffer.isEmpty()) { + // This code is for the new Unbuffered QTcpSocket use case + qint64 written = d->socketEngine->write(data, size); + if (written < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + return written; + } else if (written < size) { + // Buffer what was not written yet + char *ptr = d->writeBuffer.reserve(size - written); + memcpy(ptr, data + written, size - written); + if (d->socketEngine) + d->socketEngine->setWriteNotificationEnabled(true); + } + return size; // size=actually written + what has been buffered + } else if (!d->isBuffered && d->socketType != TcpSocket) { + // This is for a QUdpSocket that was connect()ed + qint64 written = d->socketEngine->write(data, size); + if (written < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + } else if (!d->writeBuffer.isEmpty()) { + d->socketEngine->setWriteNotificationEnabled(true); + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::writeData(%p \"%s\", %lli) == %lli", data, + qt_prettyDebug(data, qMin((int)size, 32), size).data(), + size, written); +#endif + if (written >= 0) + emit bytesWritten(written); + return written; + } + + // This is the code path for normal buffered QTcpSocket or + // unbuffered QTcpSocket when there was already something in the + // write buffer and therefore we could not do a direct engine write. + // We just write to our write buffer and enable the write notifier + // The write notifier then flush()es the buffer. + + char *ptr = d->writeBuffer.reserve(size); + if (size == 1) + *ptr = *data; + else + memcpy(ptr, data, size); + + qint64 written = size; + + if (d->socketEngine && !d->writeBuffer.isEmpty()) + d->socketEngine->setWriteNotificationEnabled(true); + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::writeData(%p \"%s\", %lli) == %lli", data, + qt_prettyDebug(data, qMin((int)size, 32), size).data(), + size, written); +#endif + return written; +} + +/*! + \since 4.1 + + Sets the port on the local side of a connection to \a port. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the localPort() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + Note that this function does not bind the local port of the socket + prior to a connection (e.g., QUdpSocket::bind()). + + \sa localAddress(), setLocalAddress(), setPeerPort() +*/ +void QAbstractSocket::setLocalPort(quint16 port) +{ + Q_D(QAbstractSocket); + d->localPort = port; +} + +/*! + \since 4.1 + + Sets the address on the local side of a connection to + \a address. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the localAddress() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + Note that this function does not bind the local address of the socket + prior to a connection (e.g., QUdpSocket::bind()). + + \sa localAddress(), setLocalPort(), setPeerAddress() +*/ +void QAbstractSocket::setLocalAddress(const QHostAddress &address) +{ + Q_D(QAbstractSocket); + d->localAddress = address; +} + +/*! + \since 4.1 + + Sets the port of the remote side of the connection to + \a port. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the peerPort() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + \sa peerPort(), setPeerAddress(), setLocalPort() +*/ +void QAbstractSocket::setPeerPort(quint16 port) +{ + Q_D(QAbstractSocket); + d->peerPort = port; +} + +/*! + \since 4.1 + + Sets the address of the remote side of the connection + to \a address. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the peerAddress() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + \sa peerAddress(), setPeerPort(), setLocalAddress() +*/ +void QAbstractSocket::setPeerAddress(const QHostAddress &address) +{ + Q_D(QAbstractSocket); + d->peerAddress = address; +} + +/*! + \since 4.1 + + Sets the host name of the remote peer to \a name. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the peerName() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + \sa peerName() +*/ +void QAbstractSocket::setPeerName(const QString &name) +{ + Q_D(QAbstractSocket); + d->peerName = name; +} + +/*! + Closes the I/O device for the socket, disconnects the socket's connection with the + host, closes the socket, and resets the name, address, port number and underlying + socket descriptor. + + See QIODevice::close() for a description of the actions that occur when an I/O + device is closed. + + \sa abort() +*/ +void QAbstractSocket::close() +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::close()"); +#endif + QIODevice::close(); + if (d->state != UnconnectedState) { + d->closeCalled = true; + disconnectFromHost(); + } + + d->localPort = 0; + d->peerPort = 0; + d->localAddress.clear(); + d->peerAddress.clear(); + d->peerName.clear(); + d->cachedSocketDescriptor = -1; +} + +/*! + Attempts to close the socket. If there is pending data waiting to + be written, QAbstractSocket will enter ClosingState and wait + until all data has been written. Eventually, it will enter + UnconnectedState and emit the disconnected() signal. + + \sa connectToHost() +*/ +void QAbstractSocket::disconnectFromHost() +{ + QMetaObject::invokeMethod(this, "disconnectFromHostImplementation", + Qt::DirectConnection); +} + +/*! + \since 4.1 + + Contains the implementation of disconnectFromHost(). +*/ +void QAbstractSocket::disconnectFromHostImplementation() +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost()"); +#endif + + if (d->state == UnconnectedState) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() was called on an unconnected socket"); +#endif + return; + } + + if (!d->abortCalled && (d->state == ConnectingState || d->state == HostLookupState)) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() but we're still connecting"); +#endif + d->pendingClose = true; + return; + } + +#ifdef QT3_SUPPORT + emit connectionClosed(); // compat signal +#endif + + // Disable and delete read notification + if (d->socketEngine) + d->socketEngine->setReadNotificationEnabled(false); + + if (d->abortCalled) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() aborting immediately"); +#endif + if (d->state == HostLookupState) { + QHostInfo::abortHostLookup(d->hostLookupId); + d->hostLookupId = -1; + } + } else { + // Perhaps emit closing() + if (d->state != ClosingState) { + d->state = ClosingState; +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() emits stateChanged()(ClosingState)"); +#endif + emit stateChanged(d->state); + } else { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() return from delayed close"); +#endif + } + + // Wait for pending data to be written. + if (d->socketEngine && d->socketEngine->isValid() && (d->writeBuffer.size() > 0 + || d->socketEngine->bytesToWrite() > 0)) { + // hack: when we are waiting for the socket engine to write bytes (only + // possible when using Socks5 or HTTP socket engine), then close + // anyway after 2 seconds. This is to prevent a timeout on Mac, where we + // sometimes just did not get the write notifier from the underlying + // CFSocket and no progress was made. + if (d->writeBuffer.size() == 0 && d->socketEngine->bytesToWrite() > 0) { + if (!d->disconnectTimer) { + d->disconnectTimer = new QTimer(this); + connect(d->disconnectTimer, SIGNAL(timeout()), this, + SLOT(_q_forceDisconnect()), Qt::DirectConnection); + } + if (!d->disconnectTimer->isActive()) + d->disconnectTimer->start(2000); + } + d->socketEngine->setWriteNotificationEnabled(true); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() delaying disconnect"); +#endif + return; + } else { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() disconnecting immediately"); +#endif + } + } + + SocketState previousState = d->state; + d->resetSocketLayer(); + d->state = UnconnectedState; + emit stateChanged(d->state); + emit readChannelFinished(); // we got an EOF + +#ifdef QT3_SUPPORT + emit delayedCloseFinished(); // compat signal +#endif + // only emit disconnected if we were connected before + if (previousState == ConnectedState || previousState == ClosingState) + emit disconnected(); + + d->localPort = 0; + d->peerPort = 0; + d->localAddress.clear(); + d->peerAddress.clear(); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() disconnected!"); +#endif + + if (d->closeCalled) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() closed!"); +#endif + d->readBuffer.clear(); + d->writeBuffer.clear(); + QIODevice::close(); + } +} + +/*! + Returns the size of the internal read buffer. This limits the + amount of data that the client can receive before you call read() + or readAll(). + + A read buffer size of 0 (the default) means that the buffer has + no size limit, ensuring that no data is lost. + + \sa setReadBufferSize(), read() +*/ +qint64 QAbstractSocket::readBufferSize() const +{ + return d_func()->readBufferMaxSize; +} + +/*! + Sets the size of QAbstractSocket's internal read buffer to be \a + size bytes. + + If the buffer size is limited to a certain size, QAbstractSocket + won't buffer more than this size of data. Exceptionally, a buffer + size of 0 means that the read buffer is unlimited and all + incoming data is buffered. This is the default. + + This option is useful if you only read the data at certain points + in time (e.g., in a real-time streaming application) or if you + want to protect your socket against receiving too much data, + which may eventually cause your application to run out of memory. + + Only QTcpSocket uses QAbstractSocket's internal buffer; QUdpSocket + does not use any buffering at all, but rather relies on the + implicit buffering provided by the operating system. + Because of this, calling this function on QUdpSocket has no + effect. + + \sa readBufferSize(), read() +*/ +void QAbstractSocket::setReadBufferSize(qint64 size) +{ + Q_D(QAbstractSocket); + +#ifndef QT_NO_OPENSSL + // Manual polymorphism; setReadBufferSize() isn't virtual, but QSslSocket overloads + // it. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) { + socket->setReadBufferSize(size); + return; + } +#endif + + if (d->readBufferMaxSize == size) + return; + d->readBufferMaxSize = size; + if (!d->readSocketNotifierCalled && d->socketEngine) { + // ensure that the read notification is enabled if we've now got + // room in the read buffer + // but only if we're not inside canReadNotification -- that will take care on its own + if ((size == 0 || d->readBuffer.size() < size) && d->state == QAbstractSocket::ConnectedState) // Do not change the notifier unless we are connected. + d->socketEngine->setReadNotificationEnabled(true); + } +} + +/*! + Returns the state of the socket. + + \sa error() +*/ +QAbstractSocket::SocketState QAbstractSocket::state() const +{ + return d_func()->state; +} + +/*! + Sets the state of the socket to \a state. + + \sa state() +*/ +void QAbstractSocket::setSocketState(SocketState state) +{ + d_func()->state = state; +} + +/*! + Returns the socket type (TCP, UDP, or other). + + \sa QTcpSocket, QUdpSocket +*/ +QAbstractSocket::SocketType QAbstractSocket::socketType() const +{ + return d_func()->socketType; +} + +/*! + Returns the type of error that last occurred. + + \sa state(), errorString() +*/ +QAbstractSocket::SocketError QAbstractSocket::error() const +{ + return d_func()->socketError; +} + +/*! + Sets the type of error that last occurred to \a socketError. + + \sa setSocketState(), setErrorString() +*/ +void QAbstractSocket::setSocketError(SocketError socketError) +{ + d_func()->socketError = socketError; +} + +#ifndef QT_NO_NETWORKPROXY +/*! + \since 4.1 + + Sets the explicit network proxy for this socket to \a networkProxy. + + To disable the use of a proxy for this socket, use the + QNetworkProxy::NoProxy proxy type: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 3 + + The default value for the proxy is QNetworkProxy::DefaultProxy, + which means the socket will use the application settings: if a + proxy is set with QNetworkProxy::setApplicationProxy, it will use + that; otherwise, if a factory is set with + QNetworkProxyFactory::setApplicationProxyFactory, it will query + that factory with type QNetworkProxyQuery::TcpSocket. + + \sa proxy(), QNetworkProxy, QNetworkProxyFactory::queryProxy() +*/ +void QAbstractSocket::setProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QAbstractSocket); + d->proxy = networkProxy; +} + +/*! + \since 4.1 + + Returns the network proxy for this socket. + By default QNetworkProxy::DefaultProxy is used, which means this + socket will query the default proxy settings for the application. + + \sa setProxy(), QNetworkProxy, QNetworkProxyFactory +*/ +QNetworkProxy QAbstractSocket::proxy() const +{ + Q_D(const QAbstractSocket); + return d->proxy; +} +#endif // QT_NO_NETWORKPROXY + +#ifdef QT3_SUPPORT +/*! + \enum QAbstractSocket::Error + \compat + + Use QAbstractSocket::SocketError instead. + + \value ErrConnectionRefused Use QAbstractSocket::ConnectionRefusedError instead. + \value ErrHostNotFound Use QAbstractSocket::HostNotFoundError instead. + \value ErrSocketRead Use QAbstractSocket::UnknownSocketError instead. +*/ + +/*! + \typedef QAbstractSocket::State + \compat + + Use QAbstractSocket::SocketState instead. + + \table + \header \o Qt 3 enum value \o Qt 4 enum value + \row \o \c Idle \o \l UnconnectedState + \row \o \c HostLookup \o \l HostLookupState + \row \o \c Connecting \o \l ConnectingState + \row \o \c Connected \o \l ConnectedState + \row \o \c Closing \o \l ClosingState + \row \o \c Connection \o \l ConnectedState + \endtable +*/ + +/*! + \fn int QAbstractSocket::socket() const + + Use socketDescriptor() instead. +*/ + +/*! + \fn void QAbstractSocket::setSocket(int socket) + + Use setSocketDescriptor() instead. +*/ + +/*! + \fn Q_ULONG QAbstractSocket::waitForMore(int msecs, bool *timeout = 0) const + + Use waitForReadyRead() instead. + + \oldcode + bool timeout; + Q_ULONG numBytes = socket->waitForMore(30000, &timeout); + \newcode + qint64 numBytes = 0; + if (socket->waitForReadyRead(msecs)) + numBytes = socket->bytesAvailable(); + bool timeout = (error() == QAbstractSocket::SocketTimeoutError); + \endcode + + \sa waitForReadyRead(), bytesAvailable(), error(), SocketTimeoutError +*/ + +/*! + \fn void QAbstractSocket::connectionClosed() + + Use disconnected() instead. +*/ + +/*! + \fn void QAbstractSocket::delayedCloseFinished() + + Use disconnected() instead. +*/ +#endif // QT3_SUPPORT + +#ifndef QT_NO_DEBUG_STREAM +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, QAbstractSocket::SocketError error) +{ + switch (error) { + case QAbstractSocket::ConnectionRefusedError: + debug << "QAbstractSocket::ConnectionRefusedError"; + break; + case QAbstractSocket::RemoteHostClosedError: + debug << "QAbstractSocket::RemoteHostClosedError"; + break; + case QAbstractSocket::HostNotFoundError: + debug << "QAbstractSocket::HostNotFoundError"; + break; + case QAbstractSocket::SocketAccessError: + debug << "QAbstractSocket::SocketAccessError"; + break; + case QAbstractSocket::SocketResourceError: + debug << "QAbstractSocket::SocketResourceError"; + break; + case QAbstractSocket::SocketTimeoutError: + debug << "QAbstractSocket::SocketTimeoutError"; + break; + case QAbstractSocket::DatagramTooLargeError: + debug << "QAbstractSocket::DatagramTooLargeError"; + break; + case QAbstractSocket::NetworkError: + debug << "QAbstractSocket::NetworkError"; + break; + case QAbstractSocket::AddressInUseError: + debug << "QAbstractSocket::AddressInUseError"; + break; + case QAbstractSocket::SocketAddressNotAvailableError: + debug << "QAbstractSocket::SocketAddressNotAvailableError"; + break; + case QAbstractSocket::UnsupportedSocketOperationError: + debug << "QAbstractSocket::UnsupportedSocketOperationError"; + break; + case QAbstractSocket::UnfinishedSocketOperationError: + debug << "QAbstractSocket::UnfinishedSocketOperationError"; + break; + case QAbstractSocket::ProxyAuthenticationRequiredError: + debug << "QAbstractSocket::ProxyAuthenticationRequiredError"; + break; + case QAbstractSocket::UnknownSocketError: + debug << "QAbstractSocket::UnknownSocketError"; + break; + case QAbstractSocket::ProxyConnectionRefusedError: + debug << "QAbstractSocket::ProxyConnectionRefusedError"; + break; + case QAbstractSocket::ProxyConnectionClosedError: + debug << "QAbstractSocket::ProxyConnectionClosedError"; + break; + case QAbstractSocket::ProxyConnectionTimeoutError: + debug << "QAbstractSocket::ProxyConnectionTimeoutError"; + break; + case QAbstractSocket::ProxyNotFoundError: + debug << "QAbstractSocket::ProxyNotFoundError"; + break; + case QAbstractSocket::ProxyProtocolError: + debug << "QAbstractSocket::ProxyProtocolError"; + break; + default: + debug << "QAbstractSocket::SocketError(" << int(error) << ')'; + break; + } + return debug; +} + +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, QAbstractSocket::SocketState state) +{ + switch (state) { + case QAbstractSocket::UnconnectedState: + debug << "QAbstractSocket::UnconnectedState"; + break; + case QAbstractSocket::HostLookupState: + debug << "QAbstractSocket::HostLookupState"; + break; + case QAbstractSocket::ConnectingState: + debug << "QAbstractSocket::ConnectingState"; + break; + case QAbstractSocket::ConnectedState: + debug << "QAbstractSocket::ConnectedState"; + break; + case QAbstractSocket::BoundState: + debug << "QAbstractSocket::BoundState"; + break; + case QAbstractSocket::ListeningState: + debug << "QAbstractSocket::ListeningState"; + break; + case QAbstractSocket::ClosingState: + debug << "QAbstractSocket::ClosingState"; + break; + default: + debug << "QAbstractSocket::SocketState(" << int(state) << ')'; + break; + } + return debug; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qabstractsocket.cpp" diff --git a/src/network/socket/qabstractsocket.h b/src/network/socket/qabstractsocket.h new file mode 100644 index 0000000000..24f5478911 --- /dev/null +++ b/src/network/socket/qabstractsocket.h @@ -0,0 +1,260 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSOCKET_H +#define QABSTRACTSOCKET_H + +#include <QtCore/qiodevice.h> +#include <QtCore/qobject.h> +#ifndef QT_NO_DEBUG_STREAM +#include <QtCore/qdebug.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QHostAddress; +#ifndef QT_NO_NETWORKPROXY +class QNetworkProxy; +#endif +class QAbstractSocketPrivate; +class QAuthenticator; + +class Q_NETWORK_EXPORT QAbstractSocket : public QIODevice +{ + Q_OBJECT + Q_ENUMS(SocketType NetworkLayerProtocol SocketError SocketState SocketOption) +public: + enum SocketType { + TcpSocket, + UdpSocket, + UnknownSocketType = -1 + }; + enum NetworkLayerProtocol { + IPv4Protocol, + IPv6Protocol, + UnknownNetworkLayerProtocol = -1 + }; + enum SocketError { + ConnectionRefusedError, + RemoteHostClosedError, + HostNotFoundError, + SocketAccessError, + SocketResourceError, + SocketTimeoutError, /* 5 */ + DatagramTooLargeError, + NetworkError, + AddressInUseError, + SocketAddressNotAvailableError, + UnsupportedSocketOperationError, /* 10 */ + UnfinishedSocketOperationError, + ProxyAuthenticationRequiredError, + SslHandshakeFailedError, + ProxyConnectionRefusedError, + ProxyConnectionClosedError, /* 15 */ + ProxyConnectionTimeoutError, + ProxyNotFoundError, + ProxyProtocolError, + + UnknownSocketError = -1 + }; + enum SocketState { + UnconnectedState, + HostLookupState, + ConnectingState, + ConnectedState, + BoundState, + ListeningState, + ClosingState +#ifdef QT3_SUPPORT + , + Idle = UnconnectedState, + HostLookup = HostLookupState, + Connecting = ConnectingState, + Connected = ConnectedState, + Closing = ClosingState, + Connection = ConnectedState +#endif + }; + enum SocketOption { + LowDelayOption, // TCP_NODELAY + KeepAliveOption, // SO_KEEPALIVE + MulticastTtlOption, // IP_MULTICAST_TTL + MulticastLoopbackOption // IP_MULTICAST_LOOPBACK + }; + + QAbstractSocket(SocketType socketType, QObject *parent); + virtual ~QAbstractSocket(); + + // ### Qt 5: Make connectToHost() and disconnectFromHost() virtual. + void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite); + void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite); + void disconnectFromHost(); + + bool isValid() const; + + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + + bool canReadLine() const; + + quint16 localPort() const; + QHostAddress localAddress() const; + quint16 peerPort() const; + QHostAddress peerAddress() const; + QString peerName() const; + + // ### Qt 5: Make setReadBufferSize() virtual + qint64 readBufferSize() const; + void setReadBufferSize(qint64 size); + + void abort(); + + // ### Qt 5: Make socketDescriptor() and setSocketDescriptor() virtual. + int socketDescriptor() const; + bool setSocketDescriptor(int socketDescriptor, SocketState state = ConnectedState, + OpenMode openMode = ReadWrite); + + // ### Qt 5: Make virtual? + void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value); + QVariant socketOption(QAbstractSocket::SocketOption option); + + SocketType socketType() const; + SocketState state() const; + SocketError error() const; + + // from QIODevice + void close(); + bool isSequential() const; + bool atEnd() const; + bool flush(); + + // for synchronous access + // ### Qt 5: Make waitForConnected() and waitForDisconnected() virtual. + bool waitForConnected(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + bool waitForBytesWritten(int msecs = 30000); + bool waitForDisconnected(int msecs = 30000); + +#ifndef QT_NO_NETWORKPROXY + void setProxy(const QNetworkProxy &networkProxy); + QNetworkProxy proxy() const; +#endif + +Q_SIGNALS: + void hostFound(); + void connected(); + void disconnected(); + void stateChanged(QAbstractSocket::SocketState); + void error(QAbstractSocket::SocketError); +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); +#endif + +protected Q_SLOTS: + void connectToHostImplementation(const QString &hostName, quint16 port, OpenMode mode = ReadWrite); + void disconnectFromHostImplementation(); + +protected: + qint64 readData(char *data, qint64 maxlen); + qint64 readLineData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + + void setSocketState(SocketState state); + void setSocketError(SocketError socketError); + void setLocalPort(quint16 port); + void setLocalAddress(const QHostAddress &address); + void setPeerPort(quint16 port); + void setPeerAddress(const QHostAddress &address); + void setPeerName(const QString &name); + + QAbstractSocket(SocketType socketType, QAbstractSocketPrivate &dd, QObject *parent = 0); + +private: + Q_DECLARE_PRIVATE(QAbstractSocket) + Q_DISABLE_COPY(QAbstractSocket) + + Q_PRIVATE_SLOT(d_func(), void _q_connectToNextAddress()) + Q_PRIVATE_SLOT(d_func(), void _q_startConnecting(const QHostInfo &)) + Q_PRIVATE_SLOT(d_func(), void _q_abortConnectionAttempt()) + Q_PRIVATE_SLOT(d_func(), void _q_testConnection()) + Q_PRIVATE_SLOT(d_func(), void _q_forceDisconnect()) + +#ifdef QT3_SUPPORT +public: + enum Error { + ErrConnectionRefused = ConnectionRefusedError, + ErrHostNotFound = HostNotFoundError, + ErrSocketRead = UnknownSocketError + }; + inline QT3_SUPPORT int socket() const { return socketDescriptor(); } + inline QT3_SUPPORT void setSocket(int socket) { setSocketDescriptor(socket); } + inline QT3_SUPPORT qulonglong waitForMore(int msecs, bool *timeout = 0) const + { + QAbstractSocket *that = const_cast<QAbstractSocket *>(this); + if (that->waitForReadyRead(msecs)) + return qulonglong(bytesAvailable()); + if (error() == SocketTimeoutError && timeout) + *timeout = true; + return 0; + } + typedef SocketState State; +Q_SIGNALS: + QT_MOC_COMPAT void connectionClosed(); // same as disconnected() + QT_MOC_COMPAT void delayedCloseFinished(); // same as disconnected() + + +#endif +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketError); +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketState); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTSOCKET_H diff --git a/src/network/socket/qabstractsocket_p.h b/src/network/socket/qabstractsocket_p.h new file mode 100644 index 0000000000..7662f47fbf --- /dev/null +++ b/src/network/socket/qabstractsocket_p.h @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSOCKET_P_H +#define QABSTRACTSOCKET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QAbstractSocket class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtNetwork/qabstractsocket.h" +#include "QtCore/qbytearray.h" +#include "QtCore/qlist.h" +#include "QtCore/qtimer.h" +#include "private/qringbuffer_p.h" +#include "private/qiodevice_p.h" +#include "private/qabstractsocketengine_p.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +class QHostInfo; + +class QAbstractSocketPrivate : public QIODevicePrivate, public QAbstractSocketEngineReceiver +{ + Q_DECLARE_PUBLIC(QAbstractSocket) +public: + QAbstractSocketPrivate(); + virtual ~QAbstractSocketPrivate(); + + // from QAbstractSocketEngineReceiver + inline void readNotification() { canReadNotification(); } + inline void writeNotification() { canWriteNotification(); } + inline void exceptionNotification() {} + void connectionNotification(); +#ifndef QT_NO_NETWORKPROXY + inline void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) { + Q_Q(QAbstractSocket); + q->proxyAuthenticationRequired(proxy, authenticator); + } +#endif + + bool canReadNotification(); + bool canWriteNotification(); + + // slots + void _q_connectToNextAddress(); + void _q_startConnecting(const QHostInfo &hostInfo); + void _q_testConnection(); + void _q_abortConnectionAttempt(); + void _q_forceDisconnect(); + + bool readSocketNotifierCalled; + bool readSocketNotifierState; + bool readSocketNotifierStateSet; + + bool emittedReadyRead; + bool emittedBytesWritten; + + bool abortCalled; + bool closeCalled; + bool pendingClose; + + QString hostName; + quint16 port; + QHostAddress host; + QList<QHostAddress> addresses; + + quint16 localPort; + quint16 peerPort; + QHostAddress localAddress; + QHostAddress peerAddress; + QString peerName; + + QAbstractSocketEngine *socketEngine; + int cachedSocketDescriptor; + +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy; + QNetworkProxy proxyInUse; + void resolveProxy(const QString &hostName, quint16 port); +#else + inline void resolveProxy(const QString &, quint16) { } +#endif + inline void resolveProxy(quint16 port) { resolveProxy(QString(), port); } + + void resetSocketLayer(); + bool flush(); + + bool initSocketLayer(QAbstractSocket::NetworkLayerProtocol protocol); + void startConnectingByName(const QString &host); + void fetchConnectionParameters(); + void setupSocketNotifiers(); + bool readFromSocket(); + + qint64 readBufferMaxSize; + QRingBuffer readBuffer; + QRingBuffer writeBuffer; + + bool isBuffered; + int blockingTimeout; + + QTimer *connectTimer; + QTimer *disconnectTimer; + int connectTimeElapsed; + + int hostLookupId; + + QAbstractSocket::SocketType socketType; + QAbstractSocket::SocketState state; + + QAbstractSocket::SocketError socketError; + + bool prePauseReadSocketNotifierState; + bool prePauseWriteSocketNotifierState; + bool prePauseExceptionSocketNotifierState; + static void pauseSocketNotifiers(QAbstractSocket*); + static void resumeSocketNotifiers(QAbstractSocket*); + static QAbstractSocketEngine* getSocketEngine(QAbstractSocket*); +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTSOCKET_P_H diff --git a/src/network/socket/qabstractsocketengine.cpp b/src/network/socket/qabstractsocketengine.cpp new file mode 100644 index 0000000000..c29f936923 --- /dev/null +++ b/src/network/socket/qabstractsocketengine.cpp @@ -0,0 +1,268 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractsocketengine_p.h" + +#ifdef Q_OS_SYMBIAN +#include "qsymbiansocketengine_p.h" +#else +#include "qnativesocketengine_p.h" +#endif + +#include "qmutex.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +class QSocketEngineHandlerList : public QList<QSocketEngineHandler*> +{ +public: + QMutex mutex; +}; + +Q_GLOBAL_STATIC(QSocketEngineHandlerList, socketHandlers) + +QSocketEngineHandler::QSocketEngineHandler() +{ + if (!socketHandlers()) + return; + QMutexLocker locker(&socketHandlers()->mutex); + socketHandlers()->prepend(this); +} + +QSocketEngineHandler::~QSocketEngineHandler() +{ + if (!socketHandlers()) + return; + QMutexLocker locker(&socketHandlers()->mutex); + socketHandlers()->removeAll(this); +} + +QAbstractSocketEnginePrivate::QAbstractSocketEnginePrivate() + : socketError(QAbstractSocket::UnknownSocketError) + , hasSetSocketError(false) + , socketErrorString(QLatin1String(QT_TRANSLATE_NOOP(QSocketLayer, "Unknown error"))) + , socketState(QAbstractSocket::UnconnectedState) + , socketType(QAbstractSocket::UnknownSocketType) + , socketProtocol(QAbstractSocket::UnknownNetworkLayerProtocol) + , localPort(0) + , peerPort(0) + , receiver(0) +{ +} + +QAbstractSocketEngine::QAbstractSocketEngine(QObject *parent) + : QObject(*new QAbstractSocketEnginePrivate(), parent) +{ +} + +QAbstractSocketEngine::QAbstractSocketEngine(QAbstractSocketEnginePrivate &dd, QObject* parent) + : QObject(dd, parent) +{ +} + +QAbstractSocketEngine *QAbstractSocketEngine::createSocketEngine(QAbstractSocket::SocketType socketType, const QNetworkProxy &proxy, QObject *parent) +{ +#ifndef QT_NO_NETWORKPROXY + // proxy type must have been resolved by now + if (proxy.type() == QNetworkProxy::DefaultProxy) + return 0; +#endif + + QMutexLocker locker(&socketHandlers()->mutex); + for (int i = 0; i < socketHandlers()->size(); i++) { + if (QAbstractSocketEngine *ret = socketHandlers()->at(i)->createSocketEngine(socketType, proxy, parent)) + return ret; + } + +#ifndef QT_NO_NETWORKPROXY + // only NoProxy can have reached here + if (proxy.type() != QNetworkProxy::NoProxy) + return 0; +#endif + +#ifdef Q_OS_SYMBIAN + return new QSymbianSocketEngine(parent); +#else + return new QNativeSocketEngine(parent); +#endif +} + +QAbstractSocketEngine *QAbstractSocketEngine::createSocketEngine(int socketDescripter, QObject *parent) +{ + QMutexLocker locker(&socketHandlers()->mutex); + for (int i = 0; i < socketHandlers()->size(); i++) { + if (QAbstractSocketEngine *ret = socketHandlers()->at(i)->createSocketEngine(socketDescripter, parent)) + return ret; + } +#ifdef Q_OS_SYMBIAN + return new QSymbianSocketEngine(parent); +#else + return new QNativeSocketEngine(parent); +#endif +} + +QAbstractSocket::SocketError QAbstractSocketEngine::error() const +{ + return d_func()->socketError; +} + +QString QAbstractSocketEngine::errorString() const +{ + return d_func()->socketErrorString; +} + +void QAbstractSocketEngine::setError(QAbstractSocket::SocketError error, const QString &errorString) const +{ + Q_D(const QAbstractSocketEngine); + d->socketError = error; + d->socketErrorString = errorString; +} + +void QAbstractSocketEngine::setReceiver(QAbstractSocketEngineReceiver *receiver) +{ + d_func()->receiver = receiver; +} + +void QAbstractSocketEngine::readNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->readNotification(); +} + +void QAbstractSocketEngine::writeNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->writeNotification(); +} + +void QAbstractSocketEngine::exceptionNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->exceptionNotification(); +} + +void QAbstractSocketEngine::connectionNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->connectionNotification(); +} + +#ifndef QT_NO_NETWORKPROXY +void QAbstractSocketEngine::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->proxyAuthenticationRequired(proxy, authenticator); +} +#endif + + +QAbstractSocket::SocketState QAbstractSocketEngine::state() const +{ + return d_func()->socketState; +} + +void QAbstractSocketEngine::setState(QAbstractSocket::SocketState state) +{ + d_func()->socketState = state; +} + +QAbstractSocket::SocketType QAbstractSocketEngine::socketType() const +{ + return d_func()->socketType; +} + +void QAbstractSocketEngine::setSocketType(QAbstractSocket::SocketType socketType) +{ + d_func()->socketType = socketType; +} + +QAbstractSocket::NetworkLayerProtocol QAbstractSocketEngine::protocol() const +{ + return d_func()->socketProtocol; +} + +void QAbstractSocketEngine::setProtocol(QAbstractSocket::NetworkLayerProtocol protocol) +{ + d_func()->socketProtocol = protocol; +} + +QHostAddress QAbstractSocketEngine::localAddress() const +{ + return d_func()->localAddress; +} + +void QAbstractSocketEngine::setLocalAddress(const QHostAddress &address) +{ + d_func()->localAddress = address; +} + +quint16 QAbstractSocketEngine::localPort() const +{ + return d_func()->localPort; +} + +void QAbstractSocketEngine::setLocalPort(quint16 port) +{ + d_func()->localPort = port; +} + +QHostAddress QAbstractSocketEngine::peerAddress() const +{ + return d_func()->peerAddress; +} + +void QAbstractSocketEngine::setPeerAddress(const QHostAddress &address) +{ + d_func()->peerAddress = address; +} + +quint16 QAbstractSocketEngine::peerPort() const +{ + return d_func()->peerPort; +} + +void QAbstractSocketEngine::setPeerPort(quint16 port) +{ + d_func()->peerPort = port; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qabstractsocketengine_p.h b/src/network/socket/qabstractsocketengine_p.h new file mode 100644 index 0000000000..ee6dad60d8 --- /dev/null +++ b/src/network/socket/qabstractsocketengine_p.h @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSOCKETENGINE_P_H +#define QABSTRACTSOCKETENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtNetwork/qhostaddress.h" +#include "QtNetwork/qabstractsocket.h" +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +class QAuthenticator; +class QAbstractSocketEnginePrivate; +#ifndef QT_NO_NETWORKINTERFACE +class QNetworkInterface; +#endif +class QNetworkProxy; + +class QAbstractSocketEngineReceiver { +public: + virtual ~QAbstractSocketEngineReceiver(){} + virtual void readNotification()= 0; + virtual void writeNotification()= 0; + virtual void exceptionNotification()= 0; + virtual void connectionNotification()= 0; +#ifndef QT_NO_NETWORKPROXY + virtual void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator)= 0; +#endif +}; + +class Q_AUTOTEST_EXPORT QAbstractSocketEngine : public QObject +{ + Q_OBJECT +public: + + static QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, const QNetworkProxy &, QObject *parent); + static QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent); + + QAbstractSocketEngine(QObject *parent = 0); + + enum SocketOption { + NonBlockingSocketOption, + BroadcastSocketOption, + ReceiveBufferSocketOption, + SendBufferSocketOption, + AddressReusable, + BindExclusively, + ReceiveOutOfBandData, + LowDelayOption, + KeepAliveOption, + MulticastTtlOption, + MulticastLoopbackOption + }; + + virtual bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol) = 0; + + virtual bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState) = 0; + + virtual int socketDescriptor() const = 0; + + virtual bool isValid() const = 0; + + virtual bool connectToHost(const QHostAddress &address, quint16 port) = 0; + virtual bool connectToHostByName(const QString &name, quint16 port) = 0; + virtual bool bind(const QHostAddress &address, quint16 port) = 0; + virtual bool listen() = 0; + virtual int accept() = 0; + virtual void close() = 0; + + virtual qint64 bytesAvailable() const = 0; + + virtual qint64 read(char *data, qint64 maxlen) = 0; + virtual qint64 write(const char *data, qint64 len) = 0; + +#ifndef QT_NO_UDPSOCKET +#ifndef QT_NO_NETWORKINTERFACE + virtual bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) = 0; + virtual bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) = 0; + virtual QNetworkInterface multicastInterface() const = 0; + virtual bool setMulticastInterface(const QNetworkInterface &iface) = 0; +#endif // QT_NO_NETWORKINTERFACE + + virtual qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0) = 0; + virtual qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port) = 0; + virtual bool hasPendingDatagrams() const = 0; + virtual qint64 pendingDatagramSize() const = 0; +#endif // QT_NO_UDPSOCKET + + virtual qint64 bytesToWrite() const = 0; + + virtual int option(SocketOption option) const = 0; + virtual bool setOption(SocketOption option, int value) = 0; + + virtual bool waitForRead(int msecs = 30000, bool *timedOut = 0) = 0; + virtual bool waitForWrite(int msecs = 30000, bool *timedOut = 0) = 0; + virtual bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0) = 0; + + QAbstractSocket::SocketError error() const; + QString errorString() const; + QAbstractSocket::SocketState state() const; + QAbstractSocket::SocketType socketType() const; + QAbstractSocket::NetworkLayerProtocol protocol() const; + + QHostAddress localAddress() const; + quint16 localPort() const; + QHostAddress peerAddress() const; + quint16 peerPort() const; + + virtual bool isReadNotificationEnabled() const = 0; + virtual void setReadNotificationEnabled(bool enable) = 0; + virtual bool isWriteNotificationEnabled() const = 0; + virtual void setWriteNotificationEnabled(bool enable) = 0; + virtual bool isExceptionNotificationEnabled() const = 0; + virtual void setExceptionNotificationEnabled(bool enable) = 0; + +public Q_SLOTS: + void readNotification(); + void writeNotification(); + void exceptionNotification(); + void connectionNotification(); +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); +#endif + +public: + void setReceiver(QAbstractSocketEngineReceiver *receiver); +protected: + QAbstractSocketEngine(QAbstractSocketEnginePrivate &dd, QObject* parent = 0); + + void setError(QAbstractSocket::SocketError error, const QString &errorString) const; + void setState(QAbstractSocket::SocketState state); + void setSocketType(QAbstractSocket::SocketType socketType); + void setProtocol(QAbstractSocket::NetworkLayerProtocol protocol); + void setLocalAddress(const QHostAddress &address); + void setLocalPort(quint16 port); + void setPeerAddress(const QHostAddress &address); + void setPeerPort(quint16 port); + +private: + Q_DECLARE_PRIVATE(QAbstractSocketEngine) + Q_DISABLE_COPY(QAbstractSocketEngine) +}; + +class QAbstractSocketEnginePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QAbstractSocketEngine) +public: + QAbstractSocketEnginePrivate(); + + mutable QAbstractSocket::SocketError socketError; + mutable bool hasSetSocketError; + mutable QString socketErrorString; + QAbstractSocket::SocketState socketState; + QAbstractSocket::SocketType socketType; + QAbstractSocket::NetworkLayerProtocol socketProtocol; + QHostAddress localAddress; + quint16 localPort; + QHostAddress peerAddress; + quint16 peerPort; + QAbstractSocketEngineReceiver *receiver; +}; + + +class Q_AUTOTEST_EXPORT QSocketEngineHandler +{ +protected: + QSocketEngineHandler(); + virtual ~QSocketEngineHandler(); + virtual QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &, QObject *parent) = 0; + virtual QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent) = 0; + +private: + friend class QAbstractSocketEngine; +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTSOCKETENGINE_P_H diff --git a/src/network/socket/qhttpsocketengine.cpp b/src/network/socket/qhttpsocketengine.cpp new file mode 100644 index 0000000000..7846056221 --- /dev/null +++ b/src/network/socket/qhttpsocketengine.cpp @@ -0,0 +1,824 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qhttpsocketengine_p.h" +#include "qtcpsocket.h" +#include "qhostaddress.h" +#include "qurl.h" +#include "qhttp.h" +#include "qelapsedtimer.h" +#include "qnetworkinterface.h" + +#if !defined(QT_NO_NETWORKPROXY) && !defined(QT_NO_HTTP) +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +#define DEBUG + +QHttpSocketEngine::QHttpSocketEngine(QObject *parent) + : QAbstractSocketEngine(*new QHttpSocketEnginePrivate, parent) +{ +} + +QHttpSocketEngine::~QHttpSocketEngine() +{ +} + +bool QHttpSocketEngine::initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QHttpSocketEngine); + if (type != QAbstractSocket::TcpSocket) + return false; + + setProtocol(protocol); + setSocketType(type); + d->socket = new QTcpSocket(this); +#ifndef QT_NO_BEARERMANAGEMENT + d->socket->setProperty("_q_networkSession", property("_q_networkSession")); +#endif + + // Explicitly disable proxying on the proxy socket itself to avoid + // unwanted recursion. + d->socket->setProxy(QNetworkProxy::NoProxy); + + // Intercept all the signals. + connect(d->socket, SIGNAL(connected()), + this, SLOT(slotSocketConnected()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(disconnected()), + this, SLOT(slotSocketDisconnected()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(readyRead()), + this, SLOT(slotSocketReadNotification()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(bytesWritten(qint64)), + this, SLOT(slotSocketBytesWritten()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(slotSocketError(QAbstractSocket::SocketError)), + Qt::DirectConnection); + connect(d->socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + this, SLOT(slotSocketStateChanged(QAbstractSocket::SocketState)), + Qt::DirectConnection); + + return true; +} + +bool QHttpSocketEngine::initialize(int, QAbstractSocket::SocketState) +{ + return false; +} + +void QHttpSocketEngine::setProxy(const QNetworkProxy &proxy) +{ + Q_D(QHttpSocketEngine); + d->proxy = proxy; + QString user = proxy.user(); + if (!user.isEmpty()) + d->authenticator.setUser(user); + QString password = proxy.password(); + if (!password.isEmpty()) + d->authenticator.setPassword(password); +} + +int QHttpSocketEngine::socketDescriptor() const +{ + Q_D(const QHttpSocketEngine); + return d->socket ? d->socket->socketDescriptor() : 0; +} + +bool QHttpSocketEngine::isValid() const +{ + Q_D(const QHttpSocketEngine); + return d->socket; +} + +bool QHttpSocketEngine::connectInternal() +{ + Q_D(QHttpSocketEngine); + + // If the handshake is done, enter ConnectedState state and return true. + if (d->state == Connected) { + qWarning("QHttpSocketEngine::connectToHost: called when already connected"); + setState(QAbstractSocket::ConnectedState); + return true; + } + + if (d->state == ConnectSent && d->socketState != QAbstractSocket::ConnectedState) + setState(QAbstractSocket::UnconnectedState); + + // Handshake isn't done. If unconnected, start connecting. + if (d->state == None && d->socket->state() == QAbstractSocket::UnconnectedState) { + setState(QAbstractSocket::ConnectingState); + d->socket->connectToHost(d->proxy.hostName(), d->proxy.port()); + } + + // If connected (might happen right away, at least for localhost services + // on some BSD systems), there might already be bytes available. + if (bytesAvailable()) + slotSocketReadNotification(); + + return d->socketState == QAbstractSocket::ConnectedState; +} + +bool QHttpSocketEngine::connectToHost(const QHostAddress &address, quint16 port) +{ + Q_D(QHttpSocketEngine); + + setPeerAddress(address); + setPeerPort(port); + d->peerName.clear(); + + return connectInternal(); +} + +bool QHttpSocketEngine::connectToHostByName(const QString &hostname, quint16 port) +{ + Q_D(QHttpSocketEngine); + + setPeerAddress(QHostAddress()); + setPeerPort(port); + d->peerName = hostname; + + return connectInternal(); +} + +bool QHttpSocketEngine::bind(const QHostAddress &, quint16) +{ + return false; +} + +bool QHttpSocketEngine::listen() +{ + return false; +} + +int QHttpSocketEngine::accept() +{ + return 0; +} + +void QHttpSocketEngine::close() +{ + Q_D(QHttpSocketEngine); + if (d->socket) { + d->socket->close(); + delete d->socket; + d->socket = 0; + } +} + +qint64 QHttpSocketEngine::bytesAvailable() const +{ + Q_D(const QHttpSocketEngine); + return d->readBuffer.size() + (d->socket ? d->socket->bytesAvailable() : 0); +} + +qint64 QHttpSocketEngine::read(char *data, qint64 maxlen) +{ + Q_D(QHttpSocketEngine); + qint64 bytesRead = d->socket->read(data, maxlen); + + if (d->socket->state() == QAbstractSocket::UnconnectedState + && d->socket->bytesAvailable() == 0) { + emitReadNotification(); + } + + if (bytesRead == -1) { + // If nothing has been read so far, and the direct socket read + // failed, return the socket's error. Otherwise, fall through and + // return as much as we read so far. + close(); + setError(QAbstractSocket::RemoteHostClosedError, + QLatin1String("Remote host closed")); + setState(QAbstractSocket::UnconnectedState); + return -1; + } + return bytesRead; +} + +qint64 QHttpSocketEngine::write(const char *data, qint64 len) +{ + Q_D(QHttpSocketEngine); + return d->socket->write(data, len); +} + +#ifndef QT_NO_UDPSOCKET +#ifndef QT_NO_NETWORKINTERFACE +bool QHttpSocketEngine::joinMulticastGroup(const QHostAddress &, + const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} + +bool QHttpSocketEngine::leaveMulticastGroup(const QHostAddress &, + const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} + +QNetworkInterface QHttpSocketEngine::multicastInterface() const +{ + return QNetworkInterface(); +} + +bool QHttpSocketEngine::setMulticastInterface(const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} +#endif // QT_NO_NETWORKINTERFACE + +qint64 QHttpSocketEngine::readDatagram(char *, qint64, QHostAddress *, + quint16 *) +{ + return 0; +} + +qint64 QHttpSocketEngine::writeDatagram(const char *, qint64, const QHostAddress &, + quint16) +{ + return 0; +} + +bool QHttpSocketEngine::hasPendingDatagrams() const +{ + return false; +} + +qint64 QHttpSocketEngine::pendingDatagramSize() const +{ + return 0; +} +#endif // QT_NO_UDPSOCKET + +qint64 QHttpSocketEngine::bytesToWrite() const +{ + Q_D(const QHttpSocketEngine); + if (d->socket) { + return d->socket->bytesToWrite(); + } else { + return 0; + } +} + +int QHttpSocketEngine::option(SocketOption option) const +{ + Q_D(const QHttpSocketEngine); + if (d->socket) { + // convert the enum and call the real socket + if (option == QAbstractSocketEngine::LowDelayOption) + return d->socket->socketOption(QAbstractSocket::LowDelayOption).toInt(); + if (option == QAbstractSocketEngine::KeepAliveOption) + return d->socket->socketOption(QAbstractSocket::KeepAliveOption).toInt(); + } + return -1; +} + +bool QHttpSocketEngine::setOption(SocketOption option, int value) +{ + Q_D(QHttpSocketEngine); + if (d->socket) { + // convert the enum and call the real socket + if (option == QAbstractSocketEngine::LowDelayOption) + d->socket->setSocketOption(QAbstractSocket::LowDelayOption, value); + if (option == QAbstractSocketEngine::KeepAliveOption) + d->socket->setSocketOption(QAbstractSocket::KeepAliveOption, value); + return true; + } + return false; +} + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +bool QHttpSocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(const QHttpSocketEngine); + + if (!d->socket || d->socket->state() == QAbstractSocket::UnconnectedState) + return false; + + QElapsedTimer stopWatch; + stopWatch.start(); + + // Wait for more data if nothing is available. + if (!d->socket->bytesAvailable()) { + if (!d->socket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + if (d->socket->state() == QAbstractSocket::UnconnectedState) + return true; + setError(d->socket->error(), d->socket->errorString()); + if (timedOut && d->socket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } + + // If we're not connected yet, wait until we are, or until an error + // occurs. + while (d->state != Connected && d->socket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + // Loop while the protocol handshake is taking place. + } + + // Report any error that may occur. + if (d->state != Connected) { + setError(d->socket->error(), d->socket->errorString()); + if (timedOut && d->socket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + return true; +} + +bool QHttpSocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_D(const QHttpSocketEngine); + + // If we're connected, just forward the call. + if (d->state == Connected) { + if (d->socket->bytesToWrite()) { + if (!d->socket->waitForBytesWritten(msecs)) { + if (d->socket->error() == QAbstractSocket::SocketTimeoutError && timedOut) + *timedOut = true; + return false; + } + } + return true; + } + + QElapsedTimer stopWatch; + stopWatch.start(); + + // If we're not connected yet, wait until we are, and until bytes have + // been received (i.e., the socket has connected, we have sent the + // greeting, and then received the response). + while (d->state != Connected && d->socket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + // Loop while the protocol handshake is taking place. + } + + // Report any error that may occur. + if (d->state != Connected) { +// setError(d->socket->error(), d->socket->errorString()); + if (timedOut && d->socket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + } + + return true; +} + +bool QHttpSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_UNUSED(checkRead); + + if (!checkWrite) { + // Not interested in writing? Then we wait for read notifications. + bool canRead = waitForRead(msecs, timedOut); + if (readyToRead) + *readyToRead = canRead; + return canRead; + } + + // Interested in writing? Then we wait for write notifications. + bool canWrite = waitForWrite(msecs, timedOut); + if (readyToWrite) + *readyToWrite = canWrite; + return canWrite; +} + +bool QHttpSocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QHttpSocketEngine); + return d->readNotificationEnabled; +} + +void QHttpSocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QHttpSocketEngine); + if (d->readNotificationEnabled == enable) + return; + + d->readNotificationEnabled = enable; + if (enable) { + // Enabling read notification can trigger a notification. + if (bytesAvailable()) + slotSocketReadNotification(); + } +} + +bool QHttpSocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QHttpSocketEngine); + return d->writeNotificationEnabled; +} + +void QHttpSocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QHttpSocketEngine); + d->writeNotificationEnabled = enable; + if (enable && d->state == Connected && d->socket->state() == QAbstractSocket::ConnectedState) + QMetaObject::invokeMethod(this, "writeNotification", Qt::QueuedConnection); +} + +bool QHttpSocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QHttpSocketEngine); + return d->exceptNotificationEnabled; +} + +void QHttpSocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QHttpSocketEngine); + d->exceptNotificationEnabled = enable; +} + +void QHttpSocketEngine::slotSocketConnected() +{ + Q_D(QHttpSocketEngine); + + // Send the greeting. + const char method[] = "CONNECT "; + QByteArray peerAddress = d->peerName.isEmpty() ? + d->peerAddress.toString().toLatin1() : + QUrl::toAce(d->peerName); + QByteArray path = peerAddress + ':' + QByteArray::number(d->peerPort); + QByteArray data = method; + data += path; + data += " HTTP/1.1\r\n"; + data += "Proxy-Connection: keep-alive\r\n" + "User-Agent: Mozilla/5.0\r\n" + "Host: " + peerAddress + "\r\n"; + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(d->authenticator); + //qDebug() << "slotSocketConnected: priv=" << priv << (priv ? (int)priv->method : -1); + if (priv && priv->method != QAuthenticatorPrivate::None) { + data += "Proxy-Authorization: " + priv->calculateResponse(method, path); + data += "\r\n"; + } + data += "\r\n"; +// qDebug() << ">>>>>>>> sending request" << this; +// qDebug() << data; +// qDebug() << ">>>>>>>"; + d->socket->write(data); + d->state = ConnectSent; +} + +void QHttpSocketEngine::slotSocketDisconnected() +{ +} + +void QHttpSocketEngine::slotSocketReadNotification() +{ + Q_D(QHttpSocketEngine); + if (d->state != Connected && d->socket->bytesAvailable() == 0) + return; + + if (d->state == Connected) { + // Forward as a read notification. + if (d->readNotificationEnabled) + emitReadNotification(); + return; + } + + readResponseContent: + if (d->state == ReadResponseContent) { + char dummybuffer[4096]; + while (d->pendingResponseData) { + int read = d->socket->read(dummybuffer, qMin(sizeof(dummybuffer), (size_t)d->pendingResponseData)); + if (read >= 0) + dummybuffer[read] = 0; + + if (read == 0) + return; + if (read == -1) { + d->socket->disconnectFromHost(); + emitWriteNotification(); + return; + } + d->pendingResponseData -= read; + } + if (d->pendingResponseData > 0) + return; + d->state = SendAuthentication; + slotSocketConnected(); + return; + } + + // Still in handshake mode. Wait until we've got a full response. + bool done = false; + do { + d->readBuffer += d->socket->readLine(); + } while (!(done = d->readBuffer.endsWith("\r\n\r\n")) && d->socket->canReadLine()); + + if (!done) { + // Wait for more. + return; + } + + if (!d->readBuffer.startsWith("HTTP/1.")) { + // protocol error, this isn't HTTP + d->readBuffer.clear(); + d->socket->close(); + setState(QAbstractSocket::UnconnectedState); + setError(QAbstractSocket::ProxyProtocolError, tr("Did not receive HTTP response from proxy")); + emitConnectionNotification(); + return; + } + + QHttpResponseHeader responseHeader(QString::fromLatin1(d->readBuffer)); + d->readBuffer.clear(); // we parsed the proxy protocol response. from now on direct socket reading will be done + + int statusCode = responseHeader.statusCode(); + if (statusCode == 200) { + d->state = Connected; + setLocalAddress(d->socket->localAddress()); + setLocalPort(d->socket->localPort()); + setState(QAbstractSocket::ConnectedState); + } else if (statusCode == 407) { + if (d->authenticator.isNull()) + d->authenticator.detach(); + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(d->authenticator); + + priv->parseHttpResponse(responseHeader, true); + + if (priv->phase == QAuthenticatorPrivate::Invalid) { + // problem parsing the reply + d->socket->close(); + setState(QAbstractSocket::UnconnectedState); + setError(QAbstractSocket::ProxyProtocolError, tr("Error parsing authentication request from proxy")); + emitConnectionNotification(); + return; + } + + bool willClose; + QString proxyConnectionHeader = responseHeader.value(QLatin1String("Proxy-Connection")); + proxyConnectionHeader = proxyConnectionHeader.toLower(); + if (proxyConnectionHeader == QLatin1String("close")) { + willClose = true; + } else if (proxyConnectionHeader == QLatin1String("keep-alive")) { + willClose = false; + } else { + // no Proxy-Connection header, so use the default + // HTTP 1.1's default behaviour is to keep persistent connections + // HTTP 1.0 or earlier, so we expect the server to close + willClose = (responseHeader.majorVersion() * 0x100 + responseHeader.minorVersion()) <= 0x0100; + } + + if (willClose) { + // the server will disconnect, so let's avoid receiving an error + // especially since the signal below may trigger a new event loop + d->socket->disconnectFromHost(); + d->socket->readAll(); + } + + if (priv->phase == QAuthenticatorPrivate::Done) + emit proxyAuthenticationRequired(d->proxy, &d->authenticator); + + // priv->phase will get reset to QAuthenticatorPrivate::Start if the authenticator got modified in the signal above. + if (priv->phase == QAuthenticatorPrivate::Done) { + setError(QAbstractSocket::ProxyAuthenticationRequiredError, tr("Authentication required")); + d->socket->disconnectFromHost(); + } else { + // close the connection if it isn't already and reconnect using the chosen authentication method + d->state = SendAuthentication; + if (willClose) { + d->socket->connectToHost(d->proxy.hostName(), d->proxy.port()); + } else { + bool ok; + int contentLength = responseHeader.value(QLatin1String("Content-Length")).toInt(&ok); + if (ok && contentLength > 0) { + d->state = ReadResponseContent; + d->pendingResponseData = contentLength; + goto readResponseContent; + } else { + d->state = SendAuthentication; + slotSocketConnected(); + } + } + return; + } + } else { + d->socket->close(); + setState(QAbstractSocket::UnconnectedState); + if (statusCode == 403 || statusCode == 405) { + // 403 Forbidden + // 405 Method Not Allowed + setError(QAbstractSocket::SocketAccessError, tr("Proxy denied connection")); + } else if (statusCode == 404) { + // 404 Not Found: host lookup error + setError(QAbstractSocket::HostNotFoundError, QAbstractSocket::tr("Host not found")); + } else if (statusCode == 503) { + // 503 Service Unavailable: Connection Refused + setError(QAbstractSocket::ConnectionRefusedError, QAbstractSocket::tr("Connection refused")); + } else { + // Some other reply + //qWarning("UNEXPECTED RESPONSE: [%s]", responseHeader.toString().toLatin1().data()); + setError(QAbstractSocket::ProxyProtocolError, tr("Error communicating with HTTP proxy")); + } + } + + // The handshake is done; notify that we're connected (or failed to connect) + emitConnectionNotification(); +} + +void QHttpSocketEngine::slotSocketBytesWritten() +{ + Q_D(QHttpSocketEngine); + if (d->state == Connected && d->writeNotificationEnabled) + emitWriteNotification(); +} + +void QHttpSocketEngine::slotSocketError(QAbstractSocket::SocketError error) +{ + Q_D(QHttpSocketEngine); + d->readBuffer.clear(); + + if (d->state != Connected) { + // we are in proxy handshaking stages + if (error == QAbstractSocket::HostNotFoundError) + setError(QAbstractSocket::ProxyNotFoundError, tr("Proxy server not found")); + else if (error == QAbstractSocket::ConnectionRefusedError) + setError(QAbstractSocket::ProxyConnectionRefusedError, tr("Proxy connection refused")); + else if (error == QAbstractSocket::SocketTimeoutError) + setError(QAbstractSocket::ProxyConnectionTimeoutError, tr("Proxy server connection timed out")); + else if (error == QAbstractSocket::RemoteHostClosedError) + setError(QAbstractSocket::ProxyConnectionClosedError, tr("Proxy connection closed prematurely")); + else + setError(error, d->socket->errorString()); + emitConnectionNotification(); + return; + } + + // We're connected + if (error == QAbstractSocket::SocketTimeoutError) + return; // ignore this error + + d->state = None; + setError(error, d->socket->errorString()); + if (error != QAbstractSocket::RemoteHostClosedError) + qDebug() << "QHttpSocketEngine::slotSocketError: got weird error =" << error; + //read notification needs to always be emitted, otherwise the higher layer doesn't get the disconnected signal + emitReadNotification(); +} + +void QHttpSocketEngine::slotSocketStateChanged(QAbstractSocket::SocketState state) +{ + Q_UNUSED(state); +} + +void QHttpSocketEngine::emitPendingReadNotification() +{ + Q_D(QHttpSocketEngine); + d->readNotificationPending = false; + if (d->readNotificationEnabled) + emit readNotification(); +} + +void QHttpSocketEngine::emitPendingWriteNotification() +{ + Q_D(QHttpSocketEngine); + d->writeNotificationPending = false; + if (d->writeNotificationEnabled) + emit writeNotification(); +} + +void QHttpSocketEngine::emitPendingConnectionNotification() +{ + Q_D(QHttpSocketEngine); + d->connectionNotificationPending = false; + emit connectionNotification(); +} + +void QHttpSocketEngine::emitReadNotification() +{ + Q_D(QHttpSocketEngine); + d->readNotificationActivated = true; + // if there is a connection notification pending we have to emit the readNotification + // incase there is connection error. This is only needed for Windows, but it does not + // hurt in other cases. + if ((d->readNotificationEnabled && !d->readNotificationPending) || d->connectionNotificationPending) { + d->readNotificationPending = true; + QMetaObject::invokeMethod(this, "emitPendingReadNotification", Qt::QueuedConnection); + } +} + +void QHttpSocketEngine::emitWriteNotification() +{ + Q_D(QHttpSocketEngine); + d->writeNotificationActivated = true; + if (d->writeNotificationEnabled && !d->writeNotificationPending) { + d->writeNotificationPending = true; + QMetaObject::invokeMethod(this, "emitPendingWriteNotification", Qt::QueuedConnection); + } +} + +void QHttpSocketEngine::emitConnectionNotification() +{ + Q_D(QHttpSocketEngine); + if (!d->connectionNotificationPending) { + d->connectionNotificationPending = true; + QMetaObject::invokeMethod(this, "emitPendingConnectionNotification", Qt::QueuedConnection); + } +} + +QHttpSocketEnginePrivate::QHttpSocketEnginePrivate() + : readNotificationEnabled(false) + , writeNotificationEnabled(false) + , exceptNotificationEnabled(false) + , readNotificationActivated(false) + , writeNotificationActivated(false) + , readNotificationPending(false) + , writeNotificationPending(false) + , connectionNotificationPending(false) + , pendingResponseData(0) +{ + socket = 0; + state = QHttpSocketEngine::None; +} + +QHttpSocketEnginePrivate::~QHttpSocketEnginePrivate() +{ +} + +QAbstractSocketEngine *QHttpSocketEngineHandler::createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &proxy, + QObject *parent) +{ + if (socketType != QAbstractSocket::TcpSocket) + return 0; + + // proxy type must have been resolved by now + if (proxy.type() != QNetworkProxy::HttpProxy) + return 0; + + // we only accept active sockets + if (!qobject_cast<QAbstractSocket *>(parent)) + return 0; + + QHttpSocketEngine *engine = new QHttpSocketEngine(parent); + engine->setProxy(proxy); + return engine; +} + +QAbstractSocketEngine *QHttpSocketEngineHandler::createSocketEngine(int, QObject *) +{ + return 0; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/socket/qhttpsocketengine_p.h b/src/network/socket/qhttpsocketengine_p.h new file mode 100644 index 0000000000..361ef5c693 --- /dev/null +++ b/src/network/socket/qhttpsocketengine_p.h @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHTTPSOCKETENGINE_P_H +#define QHTTPSOCKETENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractsocketengine_p.h" +#include "qabstractsocket.h" +#include "qnetworkproxy.h" +#include "private/qauthenticator_p.h" + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_NETWORKPROXY) && !defined(QT_NO_HTTP) + +class QTcpSocket; +class QHttpSocketEnginePrivate; + +class Q_AUTOTEST_EXPORT QHttpSocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT +public: + enum HttpState { + None, + ConnectSent, + Connected, + SendAuthentication, + ReadResponseContent + }; + QHttpSocketEngine(QObject *parent = 0); + ~QHttpSocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + void setProxy(const QNetworkProxy &networkProxy); + + int socketDescriptor() const; + + bool isValid() const; + + bool connectInternal(); + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + +#ifndef QT_NO_UDPSOCKET +#ifndef QT_NO_NETWORKINTERFACE + bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &interface); + bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &interface); + QNetworkInterface multicastInterface() const; + bool setMulticastInterface(const QNetworkInterface &iface); +#endif // QT_NO_NETWORKINTERFACE + + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; +#endif // QT_NO_UDPSOCKET + + qint64 bytesToWrite() const; + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + +public slots: + void slotSocketConnected(); + void slotSocketDisconnected(); + void slotSocketReadNotification(); + void slotSocketBytesWritten(); + void slotSocketError(QAbstractSocket::SocketError error); + void slotSocketStateChanged(QAbstractSocket::SocketState state); + +private slots: + void emitPendingReadNotification(); + void emitPendingWriteNotification(); + void emitPendingConnectionNotification(); + +private: + void emitReadNotification(); + void emitWriteNotification(); + void emitConnectionNotification(); + + Q_DECLARE_PRIVATE(QHttpSocketEngine) + Q_DISABLE_COPY(QHttpSocketEngine) + +}; + + +class QHttpSocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QHttpSocketEngine) +public: + QHttpSocketEnginePrivate(); + ~QHttpSocketEnginePrivate(); + + QNetworkProxy proxy; + QString peerName; + QTcpSocket *socket; + QByteArray readBuffer; // only used for parsing the proxy response + QHttpSocketEngine::HttpState state; + QAuthenticator authenticator; + bool readNotificationEnabled; + bool writeNotificationEnabled; + bool exceptNotificationEnabled; + bool readNotificationActivated; + bool writeNotificationActivated; + bool readNotificationPending; + bool writeNotificationPending; + bool connectionNotificationPending; + uint pendingResponseData; +}; + +class Q_AUTOTEST_EXPORT QHttpSocketEngineHandler : public QSocketEngineHandler +{ +public: + virtual QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &, QObject *parent); + virtual QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent); +}; +#endif + +QT_END_NAMESPACE + +#endif // QHTTPSOCKETENGINE_H diff --git a/src/network/socket/qlocalserver.cpp b/src/network/socket/qlocalserver.cpp new file mode 100644 index 0000000000..46822d7bad --- /dev/null +++ b/src/network/socket/qlocalserver.cpp @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LOCALSERVER + +/*! + \class QLocalServer + \since 4.4 + + \brief The QLocalServer class provides a local socket based server. + + This class makes it possible to accept incoming local socket + connections. + + Call listen() to have the server start listening + for incoming connections on a specified key. The + newConnection() signal is then emitted each time a client + connects to the server. + + Call nextPendingConnection() to accept the pending connection + as a connected QLocalSocket. The function returns a pointer to a + QLocalSocket that can be used for communicating with the client. + + If an error occurs, serverError() returns the type of error, and + errorString() can be called to get a human readable description + of what happened. + + When listening for connections, the name which the server is + listening on is available through serverName(). + + Calling close() makes QLocalServer stop listening for incoming connections. + + Although QLocalServer is designed for use with an event loop, it's possible + to use it without one. In that case, you must use waitForNewConnection(), + which blocks until either a connection is available or a timeout expires. + + \sa QLocalSocket, QTcpServer +*/ + +/*! + Create a new local socket server with the given \a parent. + + \sa listen() + */ +QLocalServer::QLocalServer(QObject *parent) + : QObject(*new QLocalServerPrivate, parent) +{ + Q_D(QLocalServer); + d->init(); +} + +/*! + Destroys the QLocalServer object. If the server is listening for + connections, it is automatically closed. + + Any client QLocalSockets that are still connected must either + disconnect or be reparented before the server is deleted. + + \sa close() + */ +QLocalServer::~QLocalServer() +{ + if (isListening()) + close(); +} + +/*! + Stop listening for incoming connections. Existing connections are not + effected, but any new connections will be refused. + + \sa isListening(), listen() + */ +void QLocalServer::close() +{ + Q_D(QLocalServer); + if (!isListening()) + return; + qDeleteAll(d->pendingConnections); + d->pendingConnections.clear(); + d->closeServer(); + d->serverName.clear(); + d->fullServerName.clear(); + d->errorString.clear(); + d->error = QAbstractSocket::UnknownSocketError; +} + +/*! + Returns the human-readable message appropriate to the current error + reported by serverError(). If no suitable string is available, an empty + string is returned. + + \sa serverError() + */ +QString QLocalServer::errorString() const +{ + Q_D(const QLocalServer); + return d->errorString; +} + +/*! + Returns true if the server has a pending connection; otherwise + returns false. + + \sa nextPendingConnection(), setMaxPendingConnections() + */ +bool QLocalServer::hasPendingConnections() const +{ + Q_D(const QLocalServer); + return !(d->pendingConnections.isEmpty()); +} + +/*! + This virtual function is called by QLocalServer when a new connection + is available. \a socketDescriptor is the native socket descriptor for + the accepted connection. + + The base implementation creates a QLocalSocket, sets the socket descriptor + and then stores the QLocalSocket in an internal list of pending + connections. Finally newConnection() is emitted. + + Reimplement this function to alter the server's behavior + when a connection is available. + + \sa newConnection(), nextPendingConnection(), + QLocalSocket::setSocketDescriptor() + */ +void QLocalServer::incomingConnection(quintptr socketDescriptor) +{ + Q_D(QLocalServer); + QLocalSocket *socket = new QLocalSocket(this); + socket->setSocketDescriptor(socketDescriptor); + d->pendingConnections.enqueue(socket); + emit newConnection(); +} + +/*! + Returns true if the server is listening for incoming connections + otherwise false. + + \sa listen(), close() + */ +bool QLocalServer::isListening() const +{ + Q_D(const QLocalServer); + return !(d->serverName.isEmpty()); +} + +/*! + Tells the server to listen for incoming connections on \a name. + If the server is currently listening then it will return false. + Return true on success otherwise false. + + \a name can be a single name and QLocalServer will determine + the correct platform specific path. serverName() will return + the name that is passed into listen. + + Usually you would just pass in a name like "foo", but on Unix this + could also be a path such as "/tmp/foo" and on Windows this could + be a pipe path such as "\\\\.\\pipe\\foo" + + Note: + On Unix if the server crashes without closing listen will fail + with AddressInUseError. To create a new server the file should be removed. + On Windows two local servers can listen to the same pipe at the same + time, but any connections will go to one of the server. + + \sa serverName(), isListening(), close() + */ +bool QLocalServer::listen(const QString &name) +{ + Q_D(QLocalServer); + if (isListening()) { + qWarning("QLocalServer::listen() called when already listening"); + return false; + } + + if (name.isEmpty()) { + d->error = QAbstractSocket::HostNotFoundError; + QString function = QLatin1String("QLocalServer::listen"); + d->errorString = tr("%1: Name error").arg(function); + return false; + } + + if (!d->listen(name)) { + d->serverName.clear(); + d->fullServerName.clear(); + return false; + } + + d->serverName = name; + return true; +} + +/*! + Returns the maximum number of pending accepted connections. + The default is 30. + + \sa setMaxPendingConnections(), hasPendingConnections() + */ +int QLocalServer::maxPendingConnections() const +{ + Q_D(const QLocalServer); + return d->maxPendingConnections; +} + +/*! + \fn void QLocalServer::newConnection() + + This signal is emitted every time a new connection is available. + + \sa hasPendingConnections(), nextPendingConnection() +*/ + +/*! + Returns the next pending connection as a connected QLocalSocket object. + + The socket is created as a child of the server, which means that it is + automatically deleted when the QLocalServer object is destroyed. It is + still a good idea to delete the object explicitly when you are done with + it, to avoid wasting memory. + + 0 is returned if this function is called when there are no pending + connections. + + \sa hasPendingConnections(), newConnection(), incomingConnection() + */ +QLocalSocket *QLocalServer::nextPendingConnection() +{ + Q_D(QLocalServer); + if (d->pendingConnections.isEmpty()) + return 0; + QLocalSocket *nextSocket = d->pendingConnections.dequeue(); +#ifndef QT_LOCALSOCKET_TCP +#ifdef Q_OS_SYMBIAN + if(!d->socketNotifier) + return nextSocket; +#endif + if (d->pendingConnections.size() <= d->maxPendingConnections) +#ifndef Q_OS_WIN + d->socketNotifier->setEnabled(true); +#else + d->connectionEventNotifier->setEnabled(true); +#endif +#endif + return nextSocket; +} + +/*! + \since 4.5 + + Removes any server instance that might cause a call to listen() to fail + and returns true if successful; otherwise returns false. + This function is meant to recover from a crash, when the previous server + instance has not been cleaned up. + + On Windows, this function does nothing; on Unix, it removes the socket file + given by \a name. + + \warning Be careful to avoid removing sockets of running instances. +*/ +bool QLocalServer::removeServer(const QString &name) +{ + return QLocalServerPrivate::removeServer(name); +} + +/*! + Returns the server name if the server is listening for connections; + otherwise returns QString() + + \sa listen(), fullServerName() + */ +QString QLocalServer::serverName() const +{ + Q_D(const QLocalServer); + return d->serverName; +} + +/*! + Returns the full path that the server is listening on. + + Note: This is platform specific + + \sa listen(), serverName() + */ +QString QLocalServer::fullServerName() const +{ + Q_D(const QLocalServer); + return d->fullServerName; +} + +/*! + Returns the type of error that occurred last or NoError. + + \sa errorString() + */ +QAbstractSocket::SocketError QLocalServer::serverError() const +{ + Q_D(const QLocalServer); + return d->error; +} + +/*! + Sets the maximum number of pending accepted connections to + \a numConnections. QLocalServer will accept no more than + \a numConnections incoming connections before nextPendingConnection() + is called. + + Note: Even though QLocalServer will stop accepting new connections + after it has reached its maximum number of pending connections, + the operating system may still keep them in queue which will result + in clients signaling that it is connected. + + \sa maxPendingConnections(), hasPendingConnections() + */ +void QLocalServer::setMaxPendingConnections(int numConnections) +{ + Q_D(QLocalServer); + d->maxPendingConnections = numConnections; +} + +/*! + Waits for at most \a msec milliseconds or until an incoming connection + is available. Returns true if a connection is available; otherwise + returns false. If the operation timed out and \a timedOut is not 0, + *timedOut will be set to true. + + This is a blocking function call. Its use is ill-advised in a + single-threaded GUI application, since the whole application will stop + responding until the function returns. waitForNewConnection() is mostly + useful when there is no event loop available. + + The non-blocking alternative is to connect to the newConnection() signal. + + If msec is -1, this function will not time out. + + \sa hasPendingConnections(), nextPendingConnection() + */ +bool QLocalServer::waitForNewConnection(int msec, bool *timedOut) +{ + Q_D(QLocalServer); + if (timedOut) + *timedOut = false; + + if (!isListening()) + return false; + + d->waitForNewConnection(msec, timedOut); + + return !d->pendingConnections.isEmpty(); +} + +#endif + +QT_END_NAMESPACE + +#include "moc_qlocalserver.cpp" + diff --git a/src/network/socket/qlocalserver.h b/src/network/socket/qlocalserver.h new file mode 100644 index 0000000000..e8dc1c9b70 --- /dev/null +++ b/src/network/socket/qlocalserver.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCALSERVER_H +#define QLOCALSERVER_H + +#include <QtNetwork/qabstractsocket.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_LOCALSERVER + +class QLocalSocket; +class QLocalServerPrivate; + +class Q_NETWORK_EXPORT QLocalServer : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QLocalServer) + +Q_SIGNALS: + void newConnection(); + +public: + QLocalServer(QObject *parent = 0); + ~QLocalServer(); + + void close(); + QString errorString() const; + virtual bool hasPendingConnections() const; + bool isListening() const; + bool listen(const QString &name); + int maxPendingConnections() const; + virtual QLocalSocket *nextPendingConnection(); + QString serverName() const; + QString fullServerName() const; + static bool removeServer(const QString &name); + QAbstractSocket::SocketError serverError() const; + void setMaxPendingConnections(int numConnections); + bool waitForNewConnection(int msec = 0, bool *timedOut = 0); + +protected: + virtual void incomingConnection(quintptr socketDescriptor); + +private: + Q_DISABLE_COPY(QLocalServer) + Q_PRIVATE_SLOT(d_func(), void _q_onNewConnection()) +}; + +#endif // QT_NO_LOCALSERVER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLOCALSERVER_H + diff --git a/src/network/socket/qlocalserver_p.h b/src/network/socket/qlocalserver_p.h new file mode 100644 index 0000000000..1ee5df2558 --- /dev/null +++ b/src/network/socket/qlocalserver_p.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCALSERVER_P_H +#define QLOCALSERVER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLocalServer class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_LOCALSERVER + +#include "qlocalserver.h" +#include "private/qobject_p.h" +#include <qqueue.h> + +#if defined(QT_LOCALSOCKET_TCP) +# include <qtcpserver.h> +#elif defined(Q_OS_WIN) +# include <qt_windows.h> +# include <private/qwineventnotifier_p.h> +#else +# include <private/qabstractsocketengine_p.h> +# include <qsocketnotifier.h> +#endif + +QT_BEGIN_NAMESPACE + +class QLocalServerPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QLocalServer) + +public: + QLocalServerPrivate() : +#if !defined(QT_LOCALSOCKET_TCP) && !defined(Q_OS_WIN) + listenSocket(-1), socketNotifier(0), +#endif + maxPendingConnections(30), error(QAbstractSocket::UnknownSocketError) + { + } + + void init(); + bool listen(const QString &name); + static bool removeServer(const QString &name); + void closeServer(); + void waitForNewConnection(int msec, bool *timedOut); + void _q_onNewConnection(); + +#if defined(QT_LOCALSOCKET_TCP) + + QTcpServer tcpServer; + QMap<quintptr, QTcpSocket*> socketMap; +#elif defined(Q_OS_WIN) + struct Listener { + HANDLE handle; + OVERLAPPED overlapped; + bool connected; + }; + + void setError(const QString &function); + bool addListener(); + + QList<Listener> listeners; + HANDLE eventHandle; + QWinEventNotifier *connectionEventNotifier; +#else + void setError(const QString &function); + + int listenSocket; + QSocketNotifier *socketNotifier; +#endif + + QString serverName; + QString fullServerName; + int maxPendingConnections; + QQueue<QLocalSocket*> pendingConnections; + QString errorString; + QAbstractSocket::SocketError error; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_LOCALSERVER + +#endif // QLOCALSERVER_P_H + diff --git a/src/network/socket/qlocalserver_tcp.cpp b/src/network/socket/qlocalserver_tcp.cpp new file mode 100644 index 0000000000..aeda8635af --- /dev/null +++ b/src/network/socket/qlocalserver_tcp.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" + +#include <qhostaddress.h> +#include <qsettings.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +void QLocalServerPrivate::init() +{ + Q_Q(QLocalServer); + q->connect(&tcpServer, SIGNAL(newConnection()), SLOT(_q_onNewConnection())); +} + +bool QLocalServerPrivate::listen(const QString &requestedServerName) +{ + if (!tcpServer.listen(QHostAddress::LocalHost)) + return false; + + const QLatin1String prefix("QLocalServer/"); + if (requestedServerName.startsWith(prefix)) + fullServerName = requestedServerName; + else + fullServerName = prefix + requestedServerName; + + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + if (settings.contains(fullServerName)) { + qWarning("QLocalServer::listen: server name is already in use."); + tcpServer.close(); + return false; + } + + settings.setValue(fullServerName, tcpServer.serverPort()); + return true; +} + +void QLocalServerPrivate::closeServer() +{ + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + if (fullServerName == QLatin1String("QLocalServer")) + settings.setValue(fullServerName, QVariant()); + else + settings.remove(fullServerName); + tcpServer.close(); +} + +void QLocalServerPrivate::waitForNewConnection(int msec, bool *timedOut) +{ + if (pendingConnections.isEmpty()) + tcpServer.waitForNewConnection(msec, timedOut); + else if (timedOut) + *timedOut = false; +} + +void QLocalServerPrivate::_q_onNewConnection() +{ + Q_Q(QLocalServer); + QTcpSocket* tcpSocket = tcpServer.nextPendingConnection(); + if (!tcpSocket) { + qWarning("QLocalServer: no pending connection"); + return; + } + + tcpSocket->setParent(q); + const quintptr socketDescriptor = tcpSocket->socketDescriptor(); + q->incomingConnection(socketDescriptor); +} + +bool QLocalServerPrivate::removeServer(const QString &name) +{ + const QLatin1String prefix("QLocalServer/"); + QString serverName; + if (name.startsWith(prefix)) + serverName = name; + else + serverName = prefix + name; + + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + if (settings.contains(serverName)) + settings.remove(serverName); + + return true; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qlocalserver_unix.cpp b/src/network/socket/qlocalserver_unix.cpp new file mode 100644 index 0000000000..bc07fcf235 --- /dev/null +++ b/src/network/socket/qlocalserver_unix.cpp @@ -0,0 +1,266 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" +#include "qnet_unix_p.h" + +#ifndef QT_NO_LOCALSERVER + +#include <sys/socket.h> +#include <sys/un.h> + +#include <qdebug.h> +#include <qdir.h> +#include <qdatetime.h> + +#ifdef Q_OS_VXWORKS +# include <selectLib.h> +#endif + +QT_BEGIN_NAMESPACE + +void QLocalServerPrivate::init() +{ +} + +bool QLocalServerPrivate::removeServer(const QString &name) +{ + QString fileName; + if (name.startsWith(QLatin1Char('/'))) { + fileName = name; + } else { + fileName = QDir::cleanPath(QDir::tempPath()); + fileName += QLatin1Char('/') + name; + } + if (QFile::exists(fileName)) + return QFile::remove(fileName); + else + return true; +} + +bool QLocalServerPrivate::listen(const QString &requestedServerName) +{ + Q_Q(QLocalServer); + + // determine the full server path + if (requestedServerName.startsWith(QLatin1Char('/'))) { + fullServerName = requestedServerName; + } else { + fullServerName = QDir::cleanPath(QDir::tempPath()); + fullServerName += QLatin1Char('/') + requestedServerName; + } + serverName = requestedServerName; + + // create the unix socket + listenSocket = qt_safe_socket(PF_UNIX, SOCK_STREAM, 0); + if (-1 == listenSocket) { + setError(QLatin1String("QLocalServer::listen")); + closeServer(); + return false; + } + + // Construct the unix address + struct ::sockaddr_un addr; + addr.sun_family = PF_UNIX; + if (sizeof(addr.sun_path) < (uint)fullServerName.toLatin1().size() + 1) { + setError(QLatin1String("QLocalServer::listen")); + closeServer(); + return false; + } + ::memcpy(addr.sun_path, fullServerName.toLatin1().data(), + fullServerName.toLatin1().size() + 1); + +#ifdef Q_OS_SYMBIAN + // In SYMBIAN OS it can currently happen that accept is called twice, + // once from waitForNewConnection and once via QSocketNotfier activity + // + // As an workaround, we set the socket to non blocking so possible + // subsequent call to accept will not block in any case + // + // This change can be removed once more generic fix to select thread + // synchronization problem is implemented. + int flags = fcntl(listenSocket, F_GETFL, 0); + if (-1 == flags + || -1 == (fcntl(listenSocket, F_SETFL, flags | O_NONBLOCK))) { + setError(QLatin1String("QLocalServer::listen")); + closeServer(); + return false; + } +#endif + + // bind + if(-1 == QT_SOCKET_BIND(listenSocket, (sockaddr *)&addr, sizeof(sockaddr_un))) { + setError(QLatin1String("QLocalServer::listen")); + // if address is in use already, just close the socket, but do not delete the file + if(errno == EADDRINUSE) + QT_CLOSE(listenSocket); + // otherwise, close the socket and delete the file + else + closeServer(); + listenSocket = -1; + return false; + } + + // listen for connections + if (-1 == qt_safe_listen(listenSocket, 50)) { + setError(QLatin1String("QLocalServer::listen")); + closeServer(); + listenSocket = -1; + if (error != QAbstractSocket::AddressInUseError) + QFile::remove(fullServerName); + return false; + } + Q_ASSERT(!socketNotifier); + socketNotifier = new QSocketNotifier(listenSocket, + QSocketNotifier::Read, q); + q->connect(socketNotifier, SIGNAL(activated(int)), + q, SLOT(_q_onNewConnection())); + socketNotifier->setEnabled(maxPendingConnections > 0); + return true; +} + +/*! + \internal + + \sa QLocalServer::closeServer() + */ +void QLocalServerPrivate::closeServer() +{ + if (-1 != listenSocket) + QT_CLOSE(listenSocket); + listenSocket = -1; + + if (socketNotifier) { + socketNotifier->setEnabled(false); // Otherwise, closed socket is checked before deleter runs + socketNotifier->deleteLater(); + socketNotifier = 0; + } + + if (!fullServerName.isEmpty()) + QFile::remove(fullServerName); +} + +/*! + \internal + + We have received a notification that we can read on the listen socket. + Accept the new socket. + */ +void QLocalServerPrivate::_q_onNewConnection() +{ + Q_Q(QLocalServer); + if (-1 == listenSocket) + return; + + ::sockaddr_un addr; + QT_SOCKLEN_T length = sizeof(sockaddr_un); + int connectedSocket = qt_safe_accept(listenSocket, (sockaddr *)&addr, &length); + if(-1 == connectedSocket) { + setError(QLatin1String("QLocalSocket::activated")); + closeServer(); + } else { + socketNotifier->setEnabled(pendingConnections.size() + <= maxPendingConnections); + q->incomingConnection(connectedSocket); + } +} + +void QLocalServerPrivate::waitForNewConnection(int msec, bool *timedOut) +{ + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(listenSocket, &readfds); + + timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + + int result = -1; + result = qt_safe_select(listenSocket + 1, &readfds, 0, 0, (msec == -1) ? 0 : &timeout); + if (-1 == result) { + setError(QLatin1String("QLocalServer::waitForNewConnection")); + closeServer(); + } + if (result > 0) + _q_onNewConnection(); + if (timedOut) + *timedOut = (result == 0); +} + +void QLocalServerPrivate::setError(const QString &function) +{ + if (EAGAIN == errno) + return; + + switch (errno) { + case EACCES: + errorString = QLocalServer::tr("%1: Permission denied").arg(function); + error = QAbstractSocket::SocketAccessError; + break; + case ELOOP: + case ENOENT: + case ENAMETOOLONG: + case EROFS: + case ENOTDIR: + errorString = QLocalServer::tr("%1: Name error").arg(function); + error = QAbstractSocket::HostNotFoundError; + break; + case EADDRINUSE: + errorString = QLocalServer::tr("%1: Address in use").arg(function); + error = QAbstractSocket::AddressInUseError; + break; + + default: + errorString = QLocalServer::tr("%1: Unknown error %2") + .arg(function).arg(errno); + error = QAbstractSocket::UnknownSocketError; +#if defined QLOCALSERVER_DEBUG + qWarning() << errorString << "fullServerName:" << fullServerName; +#endif + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_LOCALSERVER diff --git a/src/network/socket/qlocalserver_win.cpp b/src/network/socket/qlocalserver_win.cpp new file mode 100644 index 0000000000..fb1015792f --- /dev/null +++ b/src/network/socket/qlocalserver_win.cpp @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" + +#include <qdebug.h> + +// The buffer size need to be 0 otherwise data could be +// lost if the socket that has written data closes the connection +// before it is read. Pipewriter is used for write buffering. +#define BUFSIZE 0 + +// ###: This should be a property. Should replace the insane 50 on unix as well. +#define SYSTEM_MAX_PENDING_SOCKETS 8 + +QT_BEGIN_NAMESPACE + +bool QLocalServerPrivate::addListener() +{ + // The object must not change its address once the + // contained OVERLAPPED struct is passed to Windows. + listeners << Listener(); + Listener &listener = listeners.last(); + + listener.handle = CreateNamedPipe( + (const wchar_t *)fullServerName.utf16(), // pipe name + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, // read/write access + PIPE_TYPE_BYTE | // byte type pipe + PIPE_READMODE_BYTE | // byte-read mode + PIPE_WAIT, // blocking mode + PIPE_UNLIMITED_INSTANCES, // max. instances + BUFSIZE, // output buffer size + BUFSIZE, // input buffer size + 3000, // client time-out + NULL); + + if (listener.handle == INVALID_HANDLE_VALUE) { + setError(QLatin1String("QLocalServerPrivate::addListener")); + listeners.removeLast(); + return false; + } + + memset(&listener.overlapped, 0, sizeof(listener.overlapped)); + listener.overlapped.hEvent = eventHandle; + if (!ConnectNamedPipe(listener.handle, &listener.overlapped)) { + switch (GetLastError()) { + case ERROR_IO_PENDING: + listener.connected = false; + break; + case ERROR_PIPE_CONNECTED: + listener.connected = true; + SetEvent(eventHandle); + break; + default: + CloseHandle(listener.handle); + setError(QLatin1String("QLocalServerPrivate::addListener")); + listeners.removeLast(); + return false; + } + } else { + Q_ASSERT_X(false, "QLocalServerPrivate::addListener", "The impossible happened"); + SetEvent(eventHandle); + } + return true; +} + +void QLocalServerPrivate::setError(const QString &function) +{ + int windowsError = GetLastError(); + errorString = QString::fromLatin1("%1: %2").arg(function).arg(qt_error_string(windowsError)); + error = QAbstractSocket::UnknownSocketError; +} + +void QLocalServerPrivate::init() +{ +} + +bool QLocalServerPrivate::removeServer(const QString &name) +{ + Q_UNUSED(name); + return true; +} + +bool QLocalServerPrivate::listen(const QString &name) +{ + Q_Q(QLocalServer); + + QString pipePath = QLatin1String("\\\\.\\pipe\\"); + if (name.startsWith(pipePath)) + fullServerName = name; + else + fullServerName = pipePath + name; + + // Use only one event for all listeners of one socket. + // The idea is that listener events are rare, so polling all listeners once in a while is + // cheap compared to waiting for N additional events in each iteration of the main loop. + eventHandle = CreateEvent(NULL, TRUE, FALSE, NULL); + connectionEventNotifier = new QWinEventNotifier(eventHandle , q); + q->connect(connectionEventNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_onNewConnection())); + + for (int i = 0; i < SYSTEM_MAX_PENDING_SOCKETS; ++i) + if (!addListener()) + return false; + return true; +} + +void QLocalServerPrivate::_q_onNewConnection() +{ + Q_Q(QLocalServer); + DWORD dummy; + + // Reset first, otherwise we could reset an event which was asserted + // immediately after we checked the conn status. + ResetEvent(eventHandle); + + // Testing shows that there is indeed absolutely no guarantee which listener gets + // a client connection first, so there is no way around polling all of them. + for (int i = 0; i < listeners.size(); ) { + HANDLE handle = listeners[i].handle; + if (listeners[i].connected + || GetOverlappedResult(handle, &listeners[i].overlapped, &dummy, FALSE)) + { + listeners.removeAt(i); + + addListener(); + + if (pendingConnections.size() > maxPendingConnections) + connectionEventNotifier->setEnabled(false); + + // Make this the last thing so connected slots can wreak the least havoc + q->incomingConnection((quintptr)handle); + } else { + if (GetLastError() != ERROR_IO_INCOMPLETE) { + q->close(); + setError(QLatin1String("QLocalServerPrivate::_q_onNewConnection")); + return; + } + + ++i; + } + } +} + +void QLocalServerPrivate::closeServer() +{ + connectionEventNotifier->setEnabled(false); // Otherwise, closed handle is checked before deleter runs + connectionEventNotifier->deleteLater(); + connectionEventNotifier = 0; + CloseHandle(eventHandle); + for (int i = 0; i < listeners.size(); ++i) + CloseHandle(listeners[i].handle); + listeners.clear(); +} + +void QLocalServerPrivate::waitForNewConnection(int msecs, bool *timedOut) +{ + Q_Q(QLocalServer); + if (!pendingConnections.isEmpty() || !q->isListening()) + return; + + DWORD result = WaitForSingleObject(eventHandle, (msecs == -1) ? INFINITE : msecs); + if (result == WAIT_TIMEOUT) { + if (timedOut) + *timedOut = true; + } else { + _q_onNewConnection(); + } +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qlocalsocket.cpp b/src/network/socket/qlocalsocket.cpp new file mode 100644 index 0000000000..9a2b0ba3cd --- /dev/null +++ b/src/network/socket/qlocalsocket.cpp @@ -0,0 +1,507 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" + +#ifndef QT_NO_LOCALSOCKET + +QT_BEGIN_NAMESPACE + +/*! + \class QLocalSocket + \since 4.4 + + \brief The QLocalSocket class provides a local socket. + + On Windows this is a named pipe and on Unix this is a local domain socket. + + If an error occurs, socketError() returns the type of error, and + errorString() can be called to get a human readable description + of what happened. + + Although QLocalSocket is designed for use with an event loop, it's possible + to use it without one. In that case, you must use waitForConnected(), + waitForReadyRead(), waitForBytesWritten(), and waitForDisconnected() + which blocks until the operation is complete or the timeout expires. + + Note that this feature is not supported on versions of Windows earlier than + Windows XP. + + \sa QLocalServer +*/ + +/*! + \fn void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) + + Attempts to make a connection to \a name. + + The socket is opened in the given \a openMode and first enters ConnectingState. + It then attempts to connect to the address or addresses returned by the lookup. + Finally, if a connection is established, QLocalSocket enters ConnectedState + and emits connected(). + + At any point, the socket can emit error() to signal that an error occurred. + + See also state(), serverName(), and waitForConnected(). +*/ + +/*! + \fn void QLocalSocket::connected() + + This signal is emitted after connectToServer() has been called and + a connection has been successfully established. + + \sa connectToServer(), disconnected() +*/ + +/*! + \fn bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) + + Initializes QLocalSocket with the native socket descriptor + \a socketDescriptor. Returns true if socketDescriptor is accepted + as a valid socket descriptor; otherwise returns false. The socket is + opened in the mode specified by \a openMode, and enters the socket state + specified by \a socketState. + + \note It is not possible to initialize two local sockets with the same + native socket descriptor. + + \sa socketDescriptor(), state(), openMode() +*/ + +/*! + \fn quintptr QLocalSocket::socketDescriptor() const + + Returns the native socket descriptor of the QLocalSocket object if + this is available; otherwise returns -1. + + The socket descriptor is not available when QLocalSocket + is in UnconnectedState. + + \sa setSocketDescriptor() +*/ + +/*! + \fn qint64 QLocalSocket::readData(char *data, qint64 c) + \reimp +*/ + +/*! + \fn qint64 QLocalSocket::writeData(const char *data, qint64 c) + \reimp +*/ + +/*! + \fn void QLocalSocket::abort() + + Aborts the current connection and resets the socket. + Unlike disconnectFromServer(), this function immediately closes the socket, + clearing any pending data in the write buffer. + + \sa disconnectFromServer(), close() +*/ + +/*! + \fn qint64 QLocalSocket::bytesAvailable() const + \reimp +*/ + +/*! + \fn qint64 QLocalSocket::bytesToWrite() const + \reimp +*/ + +/*! + \fn bool QLocalSocket::canReadLine() const + \reimp +*/ + +/*! + \fn void QLocalSocket::close() + \reimp +*/ + +/*! + \fn bool QLocalSocket::waitForBytesWritten(int msecs) + \reimp +*/ + +/*! + \fn bool QLocalSocket::flush() + + This function writes as much as possible from the internal write buffer + to the socket, without blocking. If any data was written, this function + returns true; otherwise false is returned. + + Call this function if you need QLocalSocket to start sending buffered data + immediately. The number of bytes successfully written depends on the + operating system. In most cases, you do not need to call this function, + because QLocalSocket will start sending data automatically once control + goes back to the event loop. In the absence of an event loop, call + waitForBytesWritten() instead. + + \sa write(), waitForBytesWritten() +*/ + +/*! + \fn void QLocalSocket::disconnectFromServer() + + Attempts to close the socket. If there is pending data waiting to be + written, QLocalSocket will enter ClosingState and wait until all data + has been written. Eventually, it will enter UnconnectedState and emit + the disconnectedFromServer() signal. + + \sa connectToServer() +*/ + +/*! + \fn QLocalSocket::LocalSocketError QLocalSocket::error() const + + Returns the type of error that last occurred. + + \sa state(), errorString() +*/ + +/*! + \fn bool QLocalSocket::isValid() const + + Returns true if the socket is valid and ready for use; otherwise + returns false. + + \note The socket's state must be ConnectedState before reading + and writing can occur. + + \sa state(), connectToServer() +*/ + +/*! + \fn qint64 QLocalSocket::readBufferSize() const + + Returns the size of the internal read buffer. This limits the amount of + data that the client can receive before you call read() or readAll(). + A read buffer size of 0 (the default) means that the buffer has no size + limit, ensuring that no data is lost. + + \sa setReadBufferSize(), read() +*/ + +/*! + \fn void QLocalSocket::setReadBufferSize(qint64 size) + + Sets the size of QLocalSocket's internal read buffer to be \a size bytes. + + If the buffer size is limited to a certain size, QLocalSocket won't + buffer more than this size of data. Exceptionally, a buffer size of 0 + means that the read buffer is unlimited and all incoming data is buffered. + This is the default. + + This option is useful if you only read the data at certain points in + time (e.g., in a real-time streaming application) or if you want to + protect your socket against receiving too much data, which may eventually + cause your application to run out of memory. + + \sa readBufferSize(), read() +*/ + +/*! + \fn bool QLocalSocket::waitForConnected(int msecs) + + Waits until the socket is connected, up to \a msecs milliseconds. If the + connection has been established, this function returns true; otherwise + it returns false. In the case where it returns false, you can call + error() to determine the cause of the error. + + The following example waits up to one second for a connection + to be established: + + \snippet doc/src/snippets/code/src_network_socket_qlocalsocket_unix.cpp 0 + + If \a msecs is -1, this function will not time out. + + \sa connectToServer(), connected() +*/ + +/*! + \fn bool QLocalSocket::waitForDisconnected(int msecs) + + Waits until the socket has disconnected, up to \a msecs + milliseconds. If the connection has been disconnected, this + function returns true; otherwise it returns false. In the case + where it returns false, you can call error() to determine + the cause of the error. + + The following example waits up to one second for a connection + to be closed: + + \snippet doc/src/snippets/code/src_network_socket_qlocalsocket_unix.cpp 1 + + If \a msecs is -1, this function will not time out. + + \sa disconnectFromServer(), close() +*/ + +/*! + \fn bool QLocalSocket::waitForReadyRead(int msecs) + + This function blocks until data is available for reading and the + \l{QIODevice::}{readyRead()} signal has been emitted. The function + will timeout after \a msecs milliseconds; the default timeout is + 30000 milliseconds. + + The function returns true if data is available for reading; + otherwise it returns false (if an error occurred or the + operation timed out). + + \sa waitForBytesWritten() +*/ + +/*! + \fn void QLocalSocket::disconnected() + + This signal is emitted when the socket has been disconnected. + + \sa connectToServer(), disconnectFromServer(), abort(), connected() +*/ + +/*! + \fn void QLocalSocket::error(QLocalSocket::LocalSocketError socketError) + + This signal is emitted after an error occurred. The \a socketError + parameter describes the type of error that occurred. + + QLocalSocket::LocalSocketError is not a registered metatype, so for queued + connections, you will have to register it with Q_DECLARE_METATYPE() and + qRegisterMetaType(). + + \sa error(), errorString(), {Creating Custom Qt Types} +*/ + +/*! + \fn void QLocalSocket::stateChanged(QLocalSocket::LocalSocketState socketState) + + This signal is emitted whenever QLocalSocket's state changes. + The \a socketState parameter is the new state. + + QLocalSocket::SocketState is not a registered metatype, so for queued + connections, you will have to register it with Q_DECLARE_METATYPE() and + qRegisterMetaType(). + + \sa state(), {Creating Custom Qt Types} +*/ + +/*! + Creates a new local socket. The \a parent argument is passed to + QObject's constructor. + */ +QLocalSocket::QLocalSocket(QObject * parent) + : QIODevice(*new QLocalSocketPrivate, parent) +{ + Q_D(QLocalSocket); + d->init(); +} + +/*! + Destroys the socket, closing the connection if necessary. + */ +QLocalSocket::~QLocalSocket() +{ + close(); +#if !defined(Q_OS_WIN) && !defined(QT_LOCALSOCKET_TCP) + Q_D(QLocalSocket); + d->unixSocket.setParent(0); +#endif +} + +/*! + Returns the name of the peer as specified by connectToServer(), or an + empty QString if connectToServer() has not been called or it failed. + + \sa connectToServer(), fullServerName() + + */ +QString QLocalSocket::serverName() const +{ + Q_D(const QLocalSocket); + return d->serverName; +} + +/*! + Returns the server path that the socket is connected to. + + \note The return value of this function is platform specific. + + \sa connectToServer(), serverName() + */ +QString QLocalSocket::fullServerName() const +{ + Q_D(const QLocalSocket); + return d->fullServerName; +} + +/*! + Returns the state of the socket. + + \sa error() + */ +QLocalSocket::LocalSocketState QLocalSocket::state() const +{ + Q_D(const QLocalSocket); + return d->state; +} + +/*! \reimp +*/ +bool QLocalSocket::isSequential() const +{ + return true; +} + +/*! + \enum QLocalSocket::LocalSocketError + + The LocalServerError enumeration represents the errors that can occur. + The most recent error can be retrieved through a call to + \l QLocalSocket::error(). + + \value ConnectionRefusedError The connection was refused by + the peer (or timed out). + \value PeerClosedError The remote socket closed the connection. + Note that the client socket (i.e., this socket) will be closed + after the remote close notification has been sent. + \value ServerNotFoundError The local socket name was not found. + \value SocketAccessError The socket operation failed because the + application lacked the required privileges. + \value SocketResourceError The local system ran out of resources + (e.g., too many sockets). + \value SocketTimeoutError The socket operation timed out. + \value DatagramTooLargeError The datagram was larger than the operating + system's limit (which can be as low as 8192 bytes). + \value ConnectionError An error occurred with the connection. + \value UnsupportedSocketOperationError The requested socket operation + is not supported by the local operating system. + \value UnknownSocketError An unidentified error occurred. + */ + +/*! + \enum QLocalSocket::LocalSocketState + + This enum describes the different states in which a socket can be. + + \sa QLocalSocket::state() + + \value UnconnectedState The socket is not connected. + \value ConnectingState The socket has started establishing a connection. + \value ConnectedState A connection is established. + \value ClosingState The socket is about to close + (data may still be waiting to be written). + */ + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, QLocalSocket::LocalSocketError error) +{ + switch (error) { + case QLocalSocket::ConnectionRefusedError: + debug << "QLocalSocket::ConnectionRefusedError"; + break; + case QLocalSocket::PeerClosedError: + debug << "QLocalSocket::PeerClosedError"; + break; + case QLocalSocket::ServerNotFoundError: + debug << "QLocalSocket::ServerNotFoundError"; + break; + case QLocalSocket::SocketAccessError: + debug << "QLocalSocket::SocketAccessError"; + break; + case QLocalSocket::SocketResourceError: + debug << "QLocalSocket::SocketResourceError"; + break; + case QLocalSocket::SocketTimeoutError: + debug << "QLocalSocket::SocketTimeoutError"; + break; + case QLocalSocket::DatagramTooLargeError: + debug << "QLocalSocket::DatagramTooLargeError"; + break; + case QLocalSocket::ConnectionError: + debug << "QLocalSocket::ConnectionError"; + break; + case QLocalSocket::UnsupportedSocketOperationError: + debug << "QLocalSocket::UnsupportedSocketOperationError"; + break; + case QLocalSocket::UnknownSocketError: + debug << "QLocalSocket::UnknownSocketError"; + break; + default: + debug << "QLocalSocket::SocketError(" << int(error) << ')'; + break; + } + return debug; +} + +QDebug operator<<(QDebug debug, QLocalSocket::LocalSocketState state) +{ + switch (state) { + case QLocalSocket::UnconnectedState: + debug << "QLocalSocket::UnconnectedState"; + break; + case QLocalSocket::ConnectingState: + debug << "QLocalSocket::ConnectingState"; + break; + case QLocalSocket::ConnectedState: + debug << "QLocalSocket::ConnectedState"; + break; + case QLocalSocket::ClosingState: + debug << "QLocalSocket::ClosingState"; + break; + default: + debug << "QLocalSocket::SocketState(" << int(state) << ')'; + break; + } + return debug; +} +#endif + +QT_END_NAMESPACE + +#endif + +#include "moc_qlocalsocket.cpp" diff --git a/src/network/socket/qlocalsocket.h b/src/network/socket/qlocalsocket.h new file mode 100644 index 0000000000..2f99b55a70 --- /dev/null +++ b/src/network/socket/qlocalsocket.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCALSOCKET_H +#define QLOCALSOCKET_H + +#include <QtCore/qiodevice.h> +#include <QtNetwork/qabstractsocket.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_LOCALSOCKET + +class QLocalSocketPrivate; + +class Q_NETWORK_EXPORT QLocalSocket : public QIODevice +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QLocalSocket) + +public: + enum LocalSocketError + { + ConnectionRefusedError = QAbstractSocket::ConnectionRefusedError, + PeerClosedError = QAbstractSocket::RemoteHostClosedError, + ServerNotFoundError = QAbstractSocket::HostNotFoundError, + SocketAccessError = QAbstractSocket::SocketAccessError, + SocketResourceError = QAbstractSocket::SocketResourceError, + SocketTimeoutError = QAbstractSocket::SocketTimeoutError, + DatagramTooLargeError = QAbstractSocket::DatagramTooLargeError, + ConnectionError = QAbstractSocket::NetworkError, + UnsupportedSocketOperationError = QAbstractSocket::UnsupportedSocketOperationError, + UnknownSocketError = QAbstractSocket::UnknownSocketError + }; + + enum LocalSocketState + { + UnconnectedState = QAbstractSocket::UnconnectedState, + ConnectingState = QAbstractSocket::ConnectingState, + ConnectedState = QAbstractSocket::ConnectedState, + ClosingState = QAbstractSocket::ClosingState + }; + + QLocalSocket(QObject *parent = 0); + ~QLocalSocket(); + + void connectToServer(const QString &name, OpenMode openMode = ReadWrite); + void disconnectFromServer(); + + QString serverName() const; + QString fullServerName() const; + + void abort(); + virtual bool isSequential() const; + virtual qint64 bytesAvailable() const; + virtual qint64 bytesToWrite() const; + virtual bool canReadLine() const; + virtual void close(); + LocalSocketError error() const; + bool flush(); + bool isValid() const; + qint64 readBufferSize() const; + void setReadBufferSize(qint64 size); + + bool setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState = ConnectedState, + OpenMode openMode = ReadWrite); + quintptr socketDescriptor() const; + + LocalSocketState state() const; + bool waitForBytesWritten(int msecs = 30000); + bool waitForConnected(int msecs = 30000); + bool waitForDisconnected(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + +Q_SIGNALS: + void connected(); + void disconnected(); + void error(QLocalSocket::LocalSocketError socketError); + void stateChanged(QLocalSocket::LocalSocketState socketState); + +protected: + virtual qint64 readData(char*, qint64); + virtual qint64 writeData(const char*, qint64); + +private: + Q_DISABLE_COPY(QLocalSocket) +#if defined(QT_LOCALSOCKET_TCP) + Q_PRIVATE_SLOT(d_func(), void _q_stateChanged(QAbstractSocket::SocketState)) + Q_PRIVATE_SLOT(d_func(), void _q_error(QAbstractSocket::SocketError)) +#elif defined(Q_OS_WIN) + Q_PRIVATE_SLOT(d_func(), void _q_notified()) + Q_PRIVATE_SLOT(d_func(), void _q_canWrite()) + Q_PRIVATE_SLOT(d_func(), void _q_pipeClosed()) + Q_PRIVATE_SLOT(d_func(), void _q_emitReadyRead()) +#else + Q_PRIVATE_SLOT(d_func(), void _q_stateChanged(QAbstractSocket::SocketState)) + Q_PRIVATE_SLOT(d_func(), void _q_error(QAbstractSocket::SocketError)) + Q_PRIVATE_SLOT(d_func(), void _q_connectToSocket()) + Q_PRIVATE_SLOT(d_func(), void _q_abortConnectionAttempt()) +#endif +}; + +#ifndef QT_NO_DEBUG_STREAM +#include <QtCore/qdebug.h> +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QLocalSocket::LocalSocketError); +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QLocalSocket::LocalSocketState); +#endif + +#endif // QT_NO_LOCALSOCKET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLOCALSOCKET_H diff --git a/src/network/socket/qlocalsocket_p.h b/src/network/socket/qlocalsocket_p.h new file mode 100644 index 0000000000..09e50f512f --- /dev/null +++ b/src/network/socket/qlocalsocket_p.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCALSOCKET_P_H +#define QLOCALSOCKET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLocalSocket class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_LOCALSOCKET + +#include "qlocalsocket.h" +#include "private/qiodevice_p.h" + +#include <qtimer.h> + +#if defined(QT_LOCALSOCKET_TCP) +# include "qtcpsocket.h" +#elif defined(Q_OS_WIN) +# include "private/qwindowspipewriter_p.h" +# include "private/qringbuffer_p.h" +# include <private/qwineventnotifier_p.h> +#else +# include "private/qabstractsocketengine_p.h" +# include <qtcpsocket.h> +# include <qsocketnotifier.h> +# include <errno.h> +#endif + +QT_BEGIN_NAMESPACE + +#if !defined(Q_OS_WIN) || defined(QT_LOCALSOCKET_TCP) +class QLocalUnixSocket : public QTcpSocket +{ + +public: + QLocalUnixSocket() : QTcpSocket() + { + }; + + inline void setSocketState(QAbstractSocket::SocketState state) + { + QTcpSocket::setSocketState(state); + }; + + inline void setErrorString(const QString &string) + { + QTcpSocket::setErrorString(string); + } + + inline void setSocketError(QAbstractSocket::SocketError error) + { + QTcpSocket::setSocketError(error); + } + + inline qint64 readData(char *data, qint64 maxSize) + { + return QTcpSocket::readData(data, maxSize); + } + + inline qint64 writeData(const char *data, qint64 maxSize) + { + return QTcpSocket::writeData(data, maxSize); + } +}; +#endif //#if !defined(Q_OS_WIN) || defined(QT_LOCALSOCKET_TCP) + +class QLocalSocketPrivate : public QIODevicePrivate +{ + Q_DECLARE_PUBLIC(QLocalSocket) + +public: + QLocalSocketPrivate(); + void init(); + +#if defined(QT_LOCALSOCKET_TCP) + QLocalUnixSocket* tcpSocket; + bool ownsTcpSocket; + void setSocket(QLocalUnixSocket*); + QString generateErrorString(QLocalSocket::LocalSocketError, const QString &function) const; + void errorOccurred(QLocalSocket::LocalSocketError, const QString &function); + void _q_stateChanged(QAbstractSocket::SocketState newState); + void _q_error(QAbstractSocket::SocketError newError); +#elif defined(Q_OS_WIN) + ~QLocalSocketPrivate(); + void destroyPipeHandles(); + void setErrorString(const QString &function); + void _q_notified(); + void _q_canWrite(); + void _q_pipeClosed(); + void _q_emitReadyRead(); + DWORD checkPipeState(); + void startAsyncRead(); + bool completeAsyncRead(); + void checkReadyRead(); + HANDLE handle; + OVERLAPPED overlapped; + QWindowsPipeWriter *pipeWriter; + qint64 readBufferMaxSize; + QRingBuffer readBuffer; + int actualReadBufferSize; + QWinEventNotifier *dataReadNotifier; + QLocalSocket::LocalSocketError error; + bool readSequenceStarted; + bool pendingReadyRead; + bool pipeClosed; + static const qint64 initialReadBufferSize = 4096; +#else + QLocalUnixSocket unixSocket; + QString generateErrorString(QLocalSocket::LocalSocketError, const QString &function) const; + void errorOccurred(QLocalSocket::LocalSocketError, const QString &function); + void _q_stateChanged(QAbstractSocket::SocketState newState); + void _q_error(QAbstractSocket::SocketError newError); + void _q_connectToSocket(); + void _q_abortConnectionAttempt(); + void cancelDelayedConnect(); + QSocketNotifier *delayConnect; + QTimer *connectTimer; + int connectingSocket; + QString connectingName; + QIODevice::OpenMode connectingOpenMode; +#endif + + QString serverName; + QString fullServerName; + QLocalSocket::LocalSocketState state; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_LOCALSOCKET + +#endif // QLOCALSOCKET_P_H + diff --git a/src/network/socket/qlocalsocket_tcp.cpp b/src/network/socket/qlocalsocket_tcp.cpp new file mode 100644 index 0000000000..182d64aeb3 --- /dev/null +++ b/src/network/socket/qlocalsocket_tcp.cpp @@ -0,0 +1,437 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" +#include "qlocalserver.h" + +#include <qhostaddress.h> +#include <qsettings.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), + tcpSocket(0), + ownsTcpSocket(true), + state(QLocalSocket::UnconnectedState) +{ +} + +void QLocalSocketPrivate::init() +{ + setSocket(new QLocalUnixSocket); +} + +void QLocalSocketPrivate::setSocket(QLocalUnixSocket* socket) +{ + if (ownsTcpSocket) + delete tcpSocket; + ownsTcpSocket = false; + tcpSocket = socket; + + Q_Q(QLocalSocket); + // QIODevice signals + q->connect(tcpSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); + q->connect(tcpSocket, SIGNAL(bytesWritten(qint64)), + q, SIGNAL(bytesWritten(qint64))); + q->connect(tcpSocket, SIGNAL(readyRead()), q, SIGNAL(readyRead())); + // QAbstractSocket signals + q->connect(tcpSocket, SIGNAL(connected()), q, SIGNAL(connected())); + q->connect(tcpSocket, SIGNAL(disconnected()), q, SIGNAL(disconnected())); + q->connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + q, SLOT(_q_stateChanged(QAbstractSocket::SocketState))); + q->connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), + q, SLOT(_q_error(QAbstractSocket::SocketError))); + q->connect(tcpSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); + tcpSocket->setParent(q); +} + +void QLocalSocketPrivate::_q_error(QAbstractSocket::SocketError socketError) +{ + Q_Q(QLocalSocket); + QString function = QLatin1String("QLocalSocket"); + QLocalSocket::LocalSocketError error = (QLocalSocket::LocalSocketError)socketError; + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); +} + +void QLocalSocketPrivate::_q_stateChanged(QAbstractSocket::SocketState newState) +{ + Q_Q(QLocalSocket); + QLocalSocket::LocalSocketState currentState = state; + switch(newState) { + case QAbstractSocket::UnconnectedState: + state = QLocalSocket::UnconnectedState; + serverName.clear(); + fullServerName.clear(); + break; + case QAbstractSocket::ConnectingState: + state = QLocalSocket::ConnectingState; + break; + case QAbstractSocket::ConnectedState: + state = QLocalSocket::ConnectedState; + break; + case QAbstractSocket::ClosingState: + state = QLocalSocket::ClosingState; + break; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket::Unhandled socket state change:" << newState; +#endif + return; + } + if (currentState != state) + emit q->stateChanged(state); +} + +QString QLocalSocketPrivate::generateErrorString(QLocalSocket::LocalSocketError error, const QString &function) const +{ + QString errorString; + switch (error) { + case QLocalSocket::ConnectionRefusedError: + errorString = QLocalSocket::tr("%1: Connection refused").arg(function); + break; + case QLocalSocket::PeerClosedError: + errorString = QLocalSocket::tr("%1: Remote closed").arg(function); + break; + case QLocalSocket::ServerNotFoundError: + errorString = QLocalSocket::tr("%1: Invalid name").arg(function); + break; + case QLocalSocket::SocketAccessError: + errorString = QLocalSocket::tr("%1: Socket access error").arg(function); + break; + case QLocalSocket::SocketResourceError: + errorString = QLocalSocket::tr("%1: Socket resource error").arg(function); + break; + case QLocalSocket::SocketTimeoutError: + errorString = QLocalSocket::tr("%1: Socket operation timed out").arg(function); + break; + case QLocalSocket::DatagramTooLargeError: + errorString = QLocalSocket::tr("%1: Datagram too large").arg(function); + break; + case QLocalSocket::ConnectionError: + errorString = QLocalSocket::tr("%1: Connection error").arg(function); + break; + case QLocalSocket::UnsupportedSocketOperationError: + errorString = QLocalSocket::tr("%1: The socket operation is not supported").arg(function); + break; + case QLocalSocket::UnknownSocketError: + default: + errorString = QLocalSocket::tr("%1: Unknown error").arg(function); + } + return errorString; +} + +void QLocalSocketPrivate::errorOccurred(QLocalSocket::LocalSocketError error, const QString &function) +{ + Q_Q(QLocalSocket); + switch (error) { + case QLocalSocket::ConnectionRefusedError: + tcpSocket->setSocketError(QAbstractSocket::ConnectionRefusedError); + break; + case QLocalSocket::PeerClosedError: + tcpSocket->setSocketError(QAbstractSocket::RemoteHostClosedError); + break; + case QLocalSocket::ServerNotFoundError: + tcpSocket->setSocketError(QAbstractSocket::HostNotFoundError); + break; + case QLocalSocket::SocketAccessError: + tcpSocket->setSocketError(QAbstractSocket::SocketAccessError); + break; + case QLocalSocket::SocketResourceError: + tcpSocket->setSocketError(QAbstractSocket::SocketResourceError); + break; + case QLocalSocket::SocketTimeoutError: + tcpSocket->setSocketError(QAbstractSocket::SocketTimeoutError); + break; + case QLocalSocket::DatagramTooLargeError: + tcpSocket->setSocketError(QAbstractSocket::DatagramTooLargeError); + break; + case QLocalSocket::ConnectionError: + tcpSocket->setSocketError(QAbstractSocket::NetworkError); + break; + case QLocalSocket::UnsupportedSocketOperationError: + tcpSocket->setSocketError(QAbstractSocket::UnsupportedSocketOperationError); + break; + case QLocalSocket::UnknownSocketError: + default: + tcpSocket->setSocketError(QAbstractSocket::UnknownSocketError); + } + + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); + + // errors cause a disconnect + tcpSocket->setSocketState(QAbstractSocket::UnconnectedState); + bool stateChanged = (state != QLocalSocket::UnconnectedState); + state = QLocalSocket::UnconnectedState; + q->close(); + if (stateChanged) + q->emit stateChanged(state); +} + +void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) +{ + Q_D(QLocalSocket); + if (state() == ConnectedState + || state() == ConnectingState) + return; + + d->errorString.clear(); + d->state = ConnectingState; + emit stateChanged(d->state); + + if (name.isEmpty()) { + d->errorOccurred(ServerNotFoundError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } + + d->serverName = name; + const QLatin1String prefix("QLocalServer/"); + if (name.startsWith(prefix)) + d->fullServerName = name; + else + d->fullServerName = prefix + name; + + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + bool ok; + const quint16 port = settings.value(d->fullServerName).toUInt(&ok); + if (!ok) { + d->errorOccurred(ServerNotFoundError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } + d->tcpSocket->connectToHost(QHostAddress::LocalHost, port, openMode); + QIODevice::open(openMode); +} + +bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) +{ + Q_D(QLocalSocket); + QAbstractSocket::SocketState newSocketState = QAbstractSocket::UnconnectedState; + switch (socketState) { + case ConnectingState: + newSocketState = QAbstractSocket::ConnectingState; + break; + case ConnectedState: + newSocketState = QAbstractSocket::ConnectedState; + break; + case ClosingState: + newSocketState = QAbstractSocket::ClosingState; + break; + case UnconnectedState: + newSocketState = QAbstractSocket::UnconnectedState; + break; + } + QIODevice::open(openMode); + d->state = socketState; + + // Is our parent a localServer? Then it wants us to use its remote socket. + QLocalServer* localServer = qobject_cast<QLocalServer*>( parent() ); + if (localServer) { + foreach (QObject* child, localServer->children()) { + QTcpSocket* childTcpSocket = qobject_cast<QTcpSocket*>(child); + if (childTcpSocket && childTcpSocket->socketDescriptor() == socketDescriptor) { + d->setSocket( static_cast<QLocalUnixSocket*>(childTcpSocket) ); + return true; + } + } + } + + // We couldn't find the socket in the children list of our server. + // So it might be that the user wants to set a socket descriptor. + return d->tcpSocket->setSocketDescriptor(socketDescriptor, + newSocketState, openMode); +} + +quintptr QLocalSocket::socketDescriptor() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->socketDescriptor(); +} + +qint64 QLocalSocket::readData(char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->tcpSocket->readData(data, c); +} + +qint64 QLocalSocket::writeData(const char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->tcpSocket->writeData(data, c); +} + +void QLocalSocket::abort() +{ + Q_D(QLocalSocket); + d->tcpSocket->abort(); +} + +qint64 QLocalSocket::bytesAvailable() const +{ + Q_D(const QLocalSocket); + return QIODevice::bytesAvailable() + d->tcpSocket->bytesAvailable(); +} + +qint64 QLocalSocket::bytesToWrite() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->bytesToWrite(); +} + +bool QLocalSocket::canReadLine() const +{ + Q_D(const QLocalSocket); + return QIODevice::canReadLine() || d->tcpSocket->canReadLine(); +} + +void QLocalSocket::close() +{ + Q_D(QLocalSocket); + d->tcpSocket->close(); + d->serverName.clear(); + d->fullServerName.clear(); + QIODevice::close(); +} + +bool QLocalSocket::waitForBytesWritten(int msecs) +{ + Q_D(QLocalSocket); + return d->tcpSocket->waitForBytesWritten(msecs); +} + +bool QLocalSocket::flush() +{ + Q_D(QLocalSocket); + return d->tcpSocket->flush(); +} + +void QLocalSocket::disconnectFromServer() +{ + Q_D(QLocalSocket); + d->tcpSocket->disconnectFromHost(); +} + +QLocalSocket::LocalSocketError QLocalSocket::error() const +{ + Q_D(const QLocalSocket); + switch (d->tcpSocket->error()) { + case QAbstractSocket::ConnectionRefusedError: + return QLocalSocket::ConnectionRefusedError; + case QAbstractSocket::RemoteHostClosedError: + return QLocalSocket::PeerClosedError; + case QAbstractSocket::HostNotFoundError: + return QLocalSocket::ServerNotFoundError; + case QAbstractSocket::SocketAccessError: + return QLocalSocket::SocketAccessError; + case QAbstractSocket::SocketResourceError: + return QLocalSocket::SocketResourceError; + case QAbstractSocket::SocketTimeoutError: + return QLocalSocket::SocketTimeoutError; + case QAbstractSocket::DatagramTooLargeError: + return QLocalSocket::DatagramTooLargeError; + case QAbstractSocket::NetworkError: + return QLocalSocket::ConnectionError; + case QAbstractSocket::UnsupportedSocketOperationError: + return QLocalSocket::UnsupportedSocketOperationError; + case QAbstractSocket::UnknownSocketError: + return QLocalSocket::UnknownSocketError; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket error not handled:" << d->tcpSocket->error(); +#endif + break; + } + return UnknownSocketError; +} + +bool QLocalSocket::isValid() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->isValid(); +} + +qint64 QLocalSocket::readBufferSize() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->readBufferSize(); +} + +void QLocalSocket::setReadBufferSize(qint64 size) +{ + Q_D(QLocalSocket); + d->tcpSocket->setReadBufferSize(size); +} + +bool QLocalSocket::waitForConnected(int msec) +{ + Q_D(QLocalSocket); + if (state() != ConnectingState) + return (state() == ConnectedState); + + return d->tcpSocket->waitForConnected(msec); +} + +bool QLocalSocket::waitForDisconnected(int msecs) +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) { + qWarning() << "QLocalSocket::waitForDisconnected() is not allowed in UnconnectedState"; + return false; + } + return (d->tcpSocket->waitForDisconnected(msecs)); +} + +bool QLocalSocket::waitForReadyRead(int msecs) +{ + Q_D(QLocalSocket); + if (state() == QLocalSocket::UnconnectedState) + return false; + return (d->tcpSocket->waitForReadyRead(msecs)); +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qlocalsocket_unix.cpp b/src/network/socket/qlocalsocket_unix.cpp new file mode 100644 index 0000000000..da85d917e4 --- /dev/null +++ b/src/network/socket/qlocalsocket_unix.cpp @@ -0,0 +1,581 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" +#include "qnet_unix_p.h" + +#ifndef QT_NO_LOCALSOCKET + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <qdir.h> +#include <qdebug.h> +#include <qelapsedtimer.h> + +#ifdef Q_OS_VXWORKS +# include <selectLib.h> +#endif + +#define QT_CONNECT_TIMEOUT 30000 + +QT_BEGIN_NAMESPACE + +QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), + delayConnect(0), + connectTimer(0), + connectingSocket(-1), + connectingOpenMode(0), + state(QLocalSocket::UnconnectedState) +{ +} + +void QLocalSocketPrivate::init() +{ + Q_Q(QLocalSocket); + // QIODevice signals + q->connect(&unixSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); + q->connect(&unixSocket, SIGNAL(bytesWritten(qint64)), + q, SIGNAL(bytesWritten(qint64))); + q->connect(&unixSocket, SIGNAL(readyRead()), q, SIGNAL(readyRead())); + // QAbstractSocket signals + q->connect(&unixSocket, SIGNAL(connected()), q, SIGNAL(connected())); + q->connect(&unixSocket, SIGNAL(disconnected()), q, SIGNAL(disconnected())); + q->connect(&unixSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + q, SLOT(_q_stateChanged(QAbstractSocket::SocketState))); + q->connect(&unixSocket, SIGNAL(error(QAbstractSocket::SocketError)), + q, SLOT(_q_error(QAbstractSocket::SocketError))); + q->connect(&unixSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); + unixSocket.setParent(q); +} + +void QLocalSocketPrivate::_q_error(QAbstractSocket::SocketError socketError) +{ + Q_Q(QLocalSocket); + QString function = QLatin1String("QLocalSocket"); + QLocalSocket::LocalSocketError error = (QLocalSocket::LocalSocketError)socketError; + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); +} + +void QLocalSocketPrivate::_q_stateChanged(QAbstractSocket::SocketState newState) +{ + Q_Q(QLocalSocket); + QLocalSocket::LocalSocketState currentState = state; + switch(newState) { + case QAbstractSocket::UnconnectedState: + state = QLocalSocket::UnconnectedState; + serverName.clear(); + fullServerName.clear(); + break; + case QAbstractSocket::ConnectingState: + state = QLocalSocket::ConnectingState; + break; + case QAbstractSocket::ConnectedState: + state = QLocalSocket::ConnectedState; + break; + case QAbstractSocket::ClosingState: + state = QLocalSocket::ClosingState; + break; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket::Unhandled socket state change:" << newState; +#endif + return; + } + if (currentState != state) + emit q->stateChanged(state); +} + +QString QLocalSocketPrivate::generateErrorString(QLocalSocket::LocalSocketError error, const QString &function) const +{ + QString errorString; + switch (error) { + case QLocalSocket::ConnectionRefusedError: + errorString = QLocalSocket::tr("%1: Connection refused").arg(function); + break; + case QLocalSocket::PeerClosedError: + errorString = QLocalSocket::tr("%1: Remote closed").arg(function); + break; + case QLocalSocket::ServerNotFoundError: + errorString = QLocalSocket::tr("%1: Invalid name").arg(function); + break; + case QLocalSocket::SocketAccessError: + errorString = QLocalSocket::tr("%1: Socket access error").arg(function); + break; + case QLocalSocket::SocketResourceError: + errorString = QLocalSocket::tr("%1: Socket resource error").arg(function); + break; + case QLocalSocket::SocketTimeoutError: + errorString = QLocalSocket::tr("%1: Socket operation timed out").arg(function); + break; + case QLocalSocket::DatagramTooLargeError: + errorString = QLocalSocket::tr("%1: Datagram too large").arg(function); + break; + case QLocalSocket::ConnectionError: + errorString = QLocalSocket::tr("%1: Connection error").arg(function); + break; + case QLocalSocket::UnsupportedSocketOperationError: + errorString = QLocalSocket::tr("%1: The socket operation is not supported").arg(function); + break; + case QLocalSocket::UnknownSocketError: + default: + errorString = QLocalSocket::tr("%1: Unknown error %2").arg(function).arg(errno); + } + return errorString; +} + +void QLocalSocketPrivate::errorOccurred(QLocalSocket::LocalSocketError error, const QString &function) +{ + Q_Q(QLocalSocket); + switch (error) { + case QLocalSocket::ConnectionRefusedError: + unixSocket.setSocketError(QAbstractSocket::ConnectionRefusedError); + break; + case QLocalSocket::PeerClosedError: + unixSocket.setSocketError(QAbstractSocket::RemoteHostClosedError); + break; + case QLocalSocket::ServerNotFoundError: + unixSocket.setSocketError(QAbstractSocket::HostNotFoundError); + break; + case QLocalSocket::SocketAccessError: + unixSocket.setSocketError(QAbstractSocket::SocketAccessError); + break; + case QLocalSocket::SocketResourceError: + unixSocket.setSocketError(QAbstractSocket::SocketResourceError); + break; + case QLocalSocket::SocketTimeoutError: + unixSocket.setSocketError(QAbstractSocket::SocketTimeoutError); + break; + case QLocalSocket::DatagramTooLargeError: + unixSocket.setSocketError(QAbstractSocket::DatagramTooLargeError); + break; + case QLocalSocket::ConnectionError: + unixSocket.setSocketError(QAbstractSocket::NetworkError); + break; + case QLocalSocket::UnsupportedSocketOperationError: + unixSocket.setSocketError(QAbstractSocket::UnsupportedSocketOperationError); + break; + case QLocalSocket::UnknownSocketError: + default: + unixSocket.setSocketError(QAbstractSocket::UnknownSocketError); + } + + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); + + // errors cause a disconnect + unixSocket.setSocketState(QAbstractSocket::UnconnectedState); + bool stateChanged = (state != QLocalSocket::UnconnectedState); + state = QLocalSocket::UnconnectedState; + q->close(); + if (stateChanged) + q->emit stateChanged(state); +} + +void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) +{ + Q_D(QLocalSocket); + if (state() == ConnectedState + || state() == ConnectingState) + return; + + d->errorString.clear(); + d->unixSocket.setSocketState(QAbstractSocket::ConnectingState); + d->state = ConnectingState; + emit stateChanged(d->state); + + if (name.isEmpty()) { + d->errorOccurred(ServerNotFoundError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } + + // create the socket + if (-1 == (d->connectingSocket = qt_safe_socket(PF_UNIX, SOCK_STREAM, 0))) { + d->errorOccurred(UnsupportedSocketOperationError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } +#ifndef Q_OS_SYMBIAN + // set non blocking so we can try to connect and it wont wait + int flags = fcntl(d->connectingSocket, F_GETFL, 0); + if (-1 == flags + || -1 == (fcntl(d->connectingSocket, F_SETFL, flags | O_NONBLOCK))) { + d->errorOccurred(UnknownSocketError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } +#endif + + // _q_connectToSocket does the actual connecting + d->connectingName = name; + d->connectingOpenMode = openMode; + d->_q_connectToSocket(); +} + +/*! + \internal + + Tries to connect connectingName and connectingOpenMode + + \sa connectToServer() waitForConnected() + */ +void QLocalSocketPrivate::_q_connectToSocket() +{ + Q_Q(QLocalSocket); + QString connectingPathName; + + // determine the full server path + if (connectingName.startsWith(QLatin1Char('/'))) { + connectingPathName = connectingName; + } else { + connectingPathName = QDir::tempPath(); + connectingPathName += QLatin1Char('/') + connectingName; + } + + struct sockaddr_un name; + name.sun_family = PF_UNIX; + if (sizeof(name.sun_path) < (uint)connectingPathName.toLatin1().size() + 1) { + QString function = QLatin1String("QLocalSocket::connectToServer"); + errorOccurred(QLocalSocket::ServerNotFoundError, function); + return; + } + ::memcpy(name.sun_path, connectingPathName.toLatin1().data(), + connectingPathName.toLatin1().size() + 1); + if (-1 == qt_safe_connect(connectingSocket, (struct sockaddr *)&name, sizeof(name))) { + QString function = QLatin1String("QLocalSocket::connectToServer"); + switch (errno) + { + case EINVAL: + case ECONNREFUSED: + errorOccurred(QLocalSocket::ConnectionRefusedError, function); + break; + case ENOENT: + errorOccurred(QLocalSocket::ServerNotFoundError, function); + break; + case EACCES: + case EPERM: + errorOccurred(QLocalSocket::SocketAccessError, function); + break; + case ETIMEDOUT: + errorOccurred(QLocalSocket::SocketTimeoutError, function); + break; + case EAGAIN: + // Try again later, all of the sockets listening are full + if (!delayConnect) { + delayConnect = new QSocketNotifier(connectingSocket, QSocketNotifier::Write, q); + q->connect(delayConnect, SIGNAL(activated(int)), q, SLOT(_q_connectToSocket())); + } + if (!connectTimer) { + connectTimer = new QTimer(q); + q->connect(connectTimer, SIGNAL(timeout()), + q, SLOT(_q_abortConnectionAttempt()), + Qt::DirectConnection); + connectTimer->start(QT_CONNECT_TIMEOUT); + } + delayConnect->setEnabled(true); + break; + default: + errorOccurred(QLocalSocket::UnknownSocketError, function); + } + return; + } + + // connected! + cancelDelayedConnect(); + + serverName = connectingName; + fullServerName = connectingPathName; + if (unixSocket.setSocketDescriptor(connectingSocket, + QAbstractSocket::ConnectedState, connectingOpenMode)) { + q->QIODevice::open(connectingOpenMode); + q->emit connected(); + } else { + QString function = QLatin1String("QLocalSocket::connectToServer"); + errorOccurred(QLocalSocket::UnknownSocketError, function); + } + connectingSocket = -1; + connectingName.clear(); + connectingOpenMode = 0; +} + +bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) +{ + Q_D(QLocalSocket); + QAbstractSocket::SocketState newSocketState = QAbstractSocket::UnconnectedState; + switch (socketState) { + case ConnectingState: + newSocketState = QAbstractSocket::ConnectingState; + break; + case ConnectedState: + newSocketState = QAbstractSocket::ConnectedState; + break; + case ClosingState: + newSocketState = QAbstractSocket::ClosingState; + break; + case UnconnectedState: + newSocketState = QAbstractSocket::UnconnectedState; + break; + } + QIODevice::open(openMode); + d->state = socketState; + return d->unixSocket.setSocketDescriptor(socketDescriptor, + newSocketState, openMode); +} + +void QLocalSocketPrivate::_q_abortConnectionAttempt() +{ + Q_Q(QLocalSocket); + q->close(); +} + +void QLocalSocketPrivate::cancelDelayedConnect() +{ + if (delayConnect) { + delayConnect->setEnabled(false); + delete delayConnect; + delayConnect = 0; + connectTimer->stop(); + delete connectTimer; + connectTimer = 0; + } +} + +quintptr QLocalSocket::socketDescriptor() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.socketDescriptor(); +} + +qint64 QLocalSocket::readData(char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->unixSocket.readData(data, c); +} + +qint64 QLocalSocket::writeData(const char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->unixSocket.writeData(data, c); +} + +void QLocalSocket::abort() +{ + Q_D(QLocalSocket); + d->unixSocket.abort(); +} + +qint64 QLocalSocket::bytesAvailable() const +{ + Q_D(const QLocalSocket); + return QIODevice::bytesAvailable() + d->unixSocket.bytesAvailable(); +} + +qint64 QLocalSocket::bytesToWrite() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.bytesToWrite(); +} + +bool QLocalSocket::canReadLine() const +{ + Q_D(const QLocalSocket); + return QIODevice::canReadLine() || d->unixSocket.canReadLine(); +} + +void QLocalSocket::close() +{ + Q_D(QLocalSocket); + d->unixSocket.close(); + d->cancelDelayedConnect(); + if (d->connectingSocket != -1) + ::close(d->connectingSocket); + d->connectingSocket = -1; + d->connectingName.clear(); + d->connectingOpenMode = 0; + d->serverName.clear(); + d->fullServerName.clear(); + QIODevice::close(); +} + +bool QLocalSocket::waitForBytesWritten(int msecs) +{ + Q_D(QLocalSocket); + return d->unixSocket.waitForBytesWritten(msecs); +} + +bool QLocalSocket::flush() +{ + Q_D(QLocalSocket); + return d->unixSocket.flush(); +} + +void QLocalSocket::disconnectFromServer() +{ + Q_D(QLocalSocket); + d->unixSocket.disconnectFromHost(); +} + +QLocalSocket::LocalSocketError QLocalSocket::error() const +{ + Q_D(const QLocalSocket); + switch (d->unixSocket.error()) { + case QAbstractSocket::ConnectionRefusedError: + return QLocalSocket::ConnectionRefusedError; + case QAbstractSocket::RemoteHostClosedError: + return QLocalSocket::PeerClosedError; + case QAbstractSocket::HostNotFoundError: + return QLocalSocket::ServerNotFoundError; + case QAbstractSocket::SocketAccessError: + return QLocalSocket::SocketAccessError; + case QAbstractSocket::SocketResourceError: + return QLocalSocket::SocketResourceError; + case QAbstractSocket::SocketTimeoutError: + return QLocalSocket::SocketTimeoutError; + case QAbstractSocket::DatagramTooLargeError: + return QLocalSocket::DatagramTooLargeError; + case QAbstractSocket::NetworkError: + return QLocalSocket::ConnectionError; + case QAbstractSocket::UnsupportedSocketOperationError: + return QLocalSocket::UnsupportedSocketOperationError; + case QAbstractSocket::UnknownSocketError: + return QLocalSocket::UnknownSocketError; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket error not handled:" << d->unixSocket.error(); +#endif + break; + } + return UnknownSocketError; +} + +bool QLocalSocket::isValid() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.isValid(); +} + +qint64 QLocalSocket::readBufferSize() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.readBufferSize(); +} + +void QLocalSocket::setReadBufferSize(qint64 size) +{ + Q_D(QLocalSocket); + d->unixSocket.setReadBufferSize(size); +} + +bool QLocalSocket::waitForConnected(int msec) +{ + Q_D(QLocalSocket); + if (state() != ConnectingState) + return (state() == ConnectedState); + + fd_set fds; + FD_ZERO(&fds); + FD_SET(d->connectingSocket, &fds); + + timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + + // timeout can not be 0 or else select will return an error. + if (0 == msec) + timeout.tv_usec = 1000; + + int result = -1; + // on Linux timeout will be updated by select, but _not_ on other systems. + QElapsedTimer timer; + timer.start(); + while (state() == ConnectingState + && (-1 == msec || timer.elapsed() < msec)) { +#ifdef Q_OS_SYMBIAN + // On Symbian, ready-to-write is signaled when non-blocking socket + // connect is finised. Is ready-to-read really used on other + // UNIX paltforms when using non-blocking AF_UNIX socket? + result = ::select(d->connectingSocket + 1, 0, &fds, 0, &timeout); +#else + result = ::select(d->connectingSocket + 1, &fds, 0, 0, &timeout); +#endif + if (-1 == result && errno != EINTR) { + d->errorOccurred( QLocalSocket::UnknownSocketError, + QLatin1String("QLocalSocket::waitForConnected")); + break; + } + if (result > 0) + d->_q_connectToSocket(); + } + + return (state() == ConnectedState); +} + +bool QLocalSocket::waitForDisconnected(int msecs) +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) { + qWarning() << "QLocalSocket::waitForDisconnected() is not allowed in UnconnectedState"; + return false; + } + return (d->unixSocket.waitForDisconnected(msecs)); +} + +bool QLocalSocket::waitForReadyRead(int msecs) +{ + Q_D(QLocalSocket); + if (state() == QLocalSocket::UnconnectedState) + return false; + return (d->unixSocket.waitForReadyRead(msecs)); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/socket/qlocalsocket_win.cpp b/src/network/socket/qlocalsocket_win.cpp new file mode 100644 index 0000000000..185f57481a --- /dev/null +++ b/src/network/socket/qlocalsocket_win.cpp @@ -0,0 +1,633 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalsocket_p.h" + +#include <private/qthread_p.h> +#include <qcoreapplication.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +void QLocalSocketPrivate::init() +{ + Q_Q(QLocalSocket); + memset(&overlapped, 0, sizeof(overlapped)); + overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + dataReadNotifier = new QWinEventNotifier(overlapped.hEvent, q); + q->connect(dataReadNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_notified())); +} + +void QLocalSocketPrivate::setErrorString(const QString &function) +{ + Q_Q(QLocalSocket); + BOOL windowsError = GetLastError(); + QLocalSocket::LocalSocketState currentState = state; + + // If the connectToServer fails due to WaitNamedPipe() time-out, assume ConnectionError + if (state == QLocalSocket::ConnectingState && windowsError == ERROR_SEM_TIMEOUT) + windowsError = ERROR_NO_DATA; + + switch (windowsError) { + case ERROR_PIPE_NOT_CONNECTED: + case ERROR_BROKEN_PIPE: + case ERROR_NO_DATA: + error = QLocalSocket::ConnectionError; + errorString = QLocalSocket::tr("%1: Connection error").arg(function); + state = QLocalSocket::UnconnectedState; + break; + case ERROR_FILE_NOT_FOUND: + error = QLocalSocket::ServerNotFoundError; + errorString = QLocalSocket::tr("%1: Invalid name").arg(function); + state = QLocalSocket::UnconnectedState; + break; + case ERROR_ACCESS_DENIED: + error = QLocalSocket::SocketAccessError; + errorString = QLocalSocket::tr("%1: Access denied").arg(function); + state = QLocalSocket::UnconnectedState; + break; + default: + error = QLocalSocket::UnknownSocketError; + errorString = QLocalSocket::tr("%1: Unknown error %2").arg(function).arg(windowsError); +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket error not handled:" << errorString; +#endif + state = QLocalSocket::UnconnectedState; + } + + if (currentState != state) { + q->emit stateChanged(state); + if (state == QLocalSocket::UnconnectedState) + q->emit disconnected(); + } + emit q->error(error); +} + +QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), + handle(INVALID_HANDLE_VALUE), + pipeWriter(0), + readBufferMaxSize(0), + actualReadBufferSize(0), + error(QLocalSocket::UnknownSocketError), + readSequenceStarted(false), + pendingReadyRead(false), + pipeClosed(false), + state(QLocalSocket::UnconnectedState) +{ +} + +QLocalSocketPrivate::~QLocalSocketPrivate() +{ + destroyPipeHandles(); + CloseHandle(overlapped.hEvent); +} + +void QLocalSocketPrivate::destroyPipeHandles() +{ + if (handle != INVALID_HANDLE_VALUE) { + DisconnectNamedPipe(handle); + CloseHandle(handle); + } +} + +void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) +{ + Q_D(QLocalSocket); + if (state() == ConnectedState || state() == ConnectingState) + return; + + d->error = QLocalSocket::UnknownSocketError; + d->errorString = QString(); + d->state = ConnectingState; + emit stateChanged(d->state); + if (name.isEmpty()) { + d->error = QLocalSocket::ServerNotFoundError; + setErrorString(QLocalSocket::tr("%1: Invalid name").arg(QLatin1String("QLocalSocket::connectToServer"))); + d->state = UnconnectedState; + emit error(d->error); + emit stateChanged(d->state); + return; + } + + QString pipePath = QLatin1String("\\\\.\\pipe\\"); + if (name.startsWith(pipePath)) + d->fullServerName = name; + else + d->fullServerName = pipePath + name; + // Try to open a named pipe + HANDLE localSocket; + forever { + DWORD permissions = (openMode & QIODevice::ReadOnly) ? GENERIC_READ : 0; + permissions |= (openMode & QIODevice::WriteOnly) ? GENERIC_WRITE : 0; + localSocket = CreateFile((const wchar_t *)d->fullServerName.utf16(), // pipe name + permissions, + 0, // no sharing + NULL, // default security attributes + OPEN_EXISTING, // opens existing pipe + FILE_FLAG_OVERLAPPED, + NULL); // no template file + + if (localSocket != INVALID_HANDLE_VALUE) + break; + DWORD error = GetLastError(); + // It is really an error only if it is not ERROR_PIPE_BUSY + if (ERROR_PIPE_BUSY != error) { + break; + } + + // All pipe instances are busy, so wait until connected or up to 5 seconds. + if (!WaitNamedPipe((const wchar_t *)d->fullServerName.utf16(), 5000)) + break; + } + + if (localSocket == INVALID_HANDLE_VALUE) { + d->setErrorString(QLatin1String("QLocalSocket::connectToServer")); + d->fullServerName = QString(); + return; + } + + // we have a valid handle + d->serverName = name; + if (setSocketDescriptor((quintptr)localSocket, ConnectedState, openMode)) { + d->handle = localSocket; + emit connected(); + } +} + +// This is reading from the buffer +qint64 QLocalSocket::readData(char *data, qint64 maxSize) +{ + Q_D(QLocalSocket); + + if (d->pipeClosed && d->actualReadBufferSize == 0) + return -1; // signal EOF + + qint64 readSoFar; + // If startAsyncRead() read data, copy it to its destination. + if (maxSize == 1 && d->actualReadBufferSize > 0) { + *data = d->readBuffer.getChar(); + d->actualReadBufferSize--; + readSoFar = 1; + } else { + qint64 bytesToRead = qMin(qint64(d->actualReadBufferSize), maxSize); + readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = d->readBuffer.readPointer(); + int bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar, + qint64(d->readBuffer.nextDataBlockSize())); + memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + d->readBuffer.free(bytesToReadFromThisBlock); + d->actualReadBufferSize -= bytesToReadFromThisBlock; + } + } + + if (d->pipeClosed) { + if (d->actualReadBufferSize == 0) + QTimer::singleShot(0, this, SLOT(_q_pipeClosed())); + } else { + if (!d->readSequenceStarted) + d->startAsyncRead(); + d->checkReadyRead(); + } + + return readSoFar; +} + +/*! + \internal + Schedules or cancels a readyRead() emission depending on actual data availability + */ +void QLocalSocketPrivate::checkReadyRead() +{ + if (actualReadBufferSize > 0) { + if (!pendingReadyRead) { + Q_Q(QLocalSocket); + QTimer::singleShot(0, q, SLOT(_q_emitReadyRead())); + pendingReadyRead = true; + } + } else { + pendingReadyRead = false; + } +} + +/*! + \internal + Reads data from the socket into the readbuffer + */ +void QLocalSocketPrivate::startAsyncRead() +{ + do { + DWORD bytesToRead = checkPipeState(); + if (pipeClosed) + return; + + if (bytesToRead == 0) { + // There are no bytes in the pipe but we need to + // start the overlapped read with some buffer size. + bytesToRead = initialReadBufferSize; + } + + if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) { + bytesToRead = readBufferMaxSize - readBuffer.size(); + if (bytesToRead == 0) { + // Buffer is full. User must read data from the buffer + // before we can read more from the pipe. + return; + } + } + + char *ptr = readBuffer.reserve(bytesToRead); + + readSequenceStarted = true; + if (ReadFile(handle, ptr, bytesToRead, NULL, &overlapped)) { + completeAsyncRead(); + } else { + switch (GetLastError()) { + case ERROR_IO_PENDING: + // This is not an error. We're getting notified, when data arrives. + return; + case ERROR_MORE_DATA: + // This is not an error. The synchronous read succeeded. + // We're connected to a message mode pipe and the message + // didn't fit into the pipe's system buffer. + completeAsyncRead(); + break; + case ERROR_PIPE_NOT_CONNECTED: + { + // It may happen, that the other side closes the connection directly + // after writing data. Then we must set the appropriate socket state. + pipeClosed = true; + Q_Q(QLocalSocket); + emit q->readChannelFinished(); + return; + } + default: + setErrorString(QLatin1String("QLocalSocketPrivate::startAsyncRead")); + return; + } + } + } while (!readSequenceStarted); +} + +/*! + \internal + Sets the correct size of the read buffer after a read operation. + Returns false, if an error occurred or the connection dropped. + */ +bool QLocalSocketPrivate::completeAsyncRead() +{ + ResetEvent(overlapped.hEvent); + readSequenceStarted = false; + + DWORD bytesRead; + if (!GetOverlappedResult(handle, &overlapped, &bytesRead, TRUE)) { + switch (GetLastError()) { + case ERROR_MORE_DATA: + // This is not an error. We're connected to a message mode + // pipe and the message didn't fit into the pipe's system + // buffer. We will read the remaining data in the next call. + break; + case ERROR_PIPE_NOT_CONNECTED: + return false; + default: + setErrorString(QLatin1String("QLocalSocketPrivate::completeAsyncRead")); + return false; + } + } + + actualReadBufferSize += bytesRead; + readBuffer.truncate(actualReadBufferSize); + return true; +} + +qint64 QLocalSocket::writeData(const char *data, qint64 maxSize) +{ + Q_D(QLocalSocket); + if (!d->pipeWriter) { + d->pipeWriter = new QWindowsPipeWriter(d->handle, this); + connect(d->pipeWriter, SIGNAL(canWrite()), this, SLOT(_q_canWrite())); + connect(d->pipeWriter, SIGNAL(bytesWritten(qint64)), this, SIGNAL(bytesWritten(qint64))); + d->pipeWriter->start(); + } + return d->pipeWriter->write(data, maxSize); +} + +void QLocalSocket::abort() +{ + Q_D(QLocalSocket); + if (d->pipeWriter) { + delete d->pipeWriter; + d->pipeWriter = 0; + } + close(); +} + +/*! + \internal + Returns the number of available bytes in the pipe. + Sets QLocalSocketPrivate::pipeClosed to true if the connection is broken. + */ +DWORD QLocalSocketPrivate::checkPipeState() +{ + Q_Q(QLocalSocket); + DWORD bytes; + if (PeekNamedPipe(handle, NULL, 0, NULL, &bytes, NULL)) { + return bytes; + } else { + if (!pipeClosed) { + pipeClosed = true; + emit q->readChannelFinished(); + if (actualReadBufferSize == 0) + QTimer::singleShot(0, q, SLOT(_q_pipeClosed())); + } + } + return 0; +} + +void QLocalSocketPrivate::_q_pipeClosed() +{ + Q_Q(QLocalSocket); + q->close(); +} + +qint64 QLocalSocket::bytesAvailable() const +{ + Q_D(const QLocalSocket); + qint64 available = QIODevice::bytesAvailable(); + available += (qint64) d->actualReadBufferSize; + return available; +} + +qint64 QLocalSocket::bytesToWrite() const +{ + Q_D(const QLocalSocket); + return (d->pipeWriter) ? d->pipeWriter->bytesToWrite() : 0; +} + +bool QLocalSocket::canReadLine() const +{ + Q_D(const QLocalSocket); + if (state() != ConnectedState) + return false; + return (QIODevice::canReadLine() + || d->readBuffer.indexOf('\n', d->actualReadBufferSize) != -1); +} + +void QLocalSocket::close() +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) + return; + + QIODevice::close(); + d->state = ClosingState; + emit stateChanged(d->state); + if (!d->pipeClosed) + emit readChannelFinished(); + d->serverName = QString(); + d->fullServerName = QString(); + + if (state() != UnconnectedState && bytesToWrite() > 0) { + disconnectFromServer(); + return; + } + d->readSequenceStarted = false; + d->pendingReadyRead = false; + d->pipeClosed = false; + d->destroyPipeHandles(); + d->handle = INVALID_HANDLE_VALUE; + ResetEvent(d->overlapped.hEvent); + d->state = UnconnectedState; + emit stateChanged(d->state); + emit disconnected(); + if (d->pipeWriter) { + delete d->pipeWriter; + d->pipeWriter = 0; + } +} + +bool QLocalSocket::flush() +{ + Q_D(QLocalSocket); + if (d->pipeWriter) + return d->pipeWriter->waitForWrite(0); + return false; +} + +void QLocalSocket::disconnectFromServer() +{ + Q_D(QLocalSocket); + + // Are we still connected? + if (!isValid()) { + // If we have unwritten data, the pipeWriter is still present. + // It must be destroyed before close() to prevent an infinite loop. + delete d->pipeWriter; + d->pipeWriter = 0; + } + + flush(); + if (d->pipeWriter && d->pipeWriter->bytesToWrite() != 0) { + d->state = QLocalSocket::ClosingState; + emit stateChanged(d->state); + } else { + close(); + } +} + +QLocalSocket::LocalSocketError QLocalSocket::error() const +{ + Q_D(const QLocalSocket); + return d->error; +} + +bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) +{ + Q_D(QLocalSocket); + d->readBuffer.clear(); + d->actualReadBufferSize = 0; + QIODevice::open(openMode); + d->handle = (int*)socketDescriptor; + d->state = socketState; + emit stateChanged(d->state); + if (d->state == ConnectedState && openMode.testFlag(QIODevice::ReadOnly)) { + d->startAsyncRead(); + d->checkReadyRead(); + } + return true; +} + +void QLocalSocketPrivate::_q_canWrite() +{ + Q_Q(QLocalSocket); + if (state == QLocalSocket::ClosingState) + q->close(); +} + +void QLocalSocketPrivate::_q_notified() +{ + Q_Q(QLocalSocket); + if (!completeAsyncRead()) { + pipeClosed = true; + emit q->readChannelFinished(); + if (actualReadBufferSize == 0) + QTimer::singleShot(0, q, SLOT(_q_pipeClosed())); + return; + } + startAsyncRead(); + pendingReadyRead = false; + emit q->readyRead(); +} + +void QLocalSocketPrivate::_q_emitReadyRead() +{ + if (pendingReadyRead) { + Q_Q(QLocalSocket); + pendingReadyRead = false; + emit q->readyRead(); + } +} + +quintptr QLocalSocket::socketDescriptor() const +{ + Q_D(const QLocalSocket); + return (quintptr)d->handle; +} + +qint64 QLocalSocket::readBufferSize() const +{ + Q_D(const QLocalSocket); + return d->readBufferMaxSize; +} + +void QLocalSocket::setReadBufferSize(qint64 size) +{ + Q_D(QLocalSocket); + d->readBufferMaxSize = size; +} + +bool QLocalSocket::waitForConnected(int msecs) +{ + Q_UNUSED(msecs); + return (state() == ConnectedState); +} + +bool QLocalSocket::waitForDisconnected(int msecs) +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) + return false; + if (!openMode().testFlag(QIODevice::ReadOnly)) { + qWarning("QLocalSocket::waitForDisconnected isn't supported for write only pipes."); + return false; + } + QIncrementalSleepTimer timer(msecs); + forever { + d->checkPipeState(); + if (d->pipeClosed) + close(); + if (state() == UnconnectedState) + return true; + Sleep(timer.nextSleepTime()); + if (timer.hasTimedOut()) + break; + } + + return false; +} + +bool QLocalSocket::isValid() const +{ + Q_D(const QLocalSocket); + return d->handle != INVALID_HANDLE_VALUE; +} + +bool QLocalSocket::waitForReadyRead(int msecs) +{ + Q_D(QLocalSocket); + + if (bytesAvailable() > 0) + return true; + + if (d->state != QLocalSocket::ConnectedState) + return false; + + // We already know that the pipe is gone, but did not enter the event loop yet. + if (d->pipeClosed) { + close(); + return false; + } + + Q_ASSERT(d->readSequenceStarted); + DWORD result = WaitForSingleObject(d->overlapped.hEvent, msecs == -1 ? INFINITE : msecs); + switch (result) { + case WAIT_OBJECT_0: + d->_q_notified(); + // We just noticed that the pipe is gone. + if (d->pipeClosed) { + close(); + return false; + } + return true; + case WAIT_TIMEOUT: + return false; + } + + qWarning("QLocalSocket::waitForReadyRead WaitForSingleObject failed with error code %d.", int(GetLastError())); + return false; +} + +bool QLocalSocket::waitForBytesWritten(int msecs) +{ + Q_D(const QLocalSocket); + if (!d->pipeWriter) + return false; + + // Wait for the pipe writer to acknowledge that it has + // written. This will succeed if either the pipe writer has + // already written the data, or if it manages to write data + // within the given timeout. + return d->pipeWriter->waitForWrite(msecs); +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnativesocketengine.cpp b/src/network/socket/qnativesocketengine.cpp new file mode 100644 index 0000000000..f5a88e2cc7 --- /dev/null +++ b/src/network/socket/qnativesocketengine.cpp @@ -0,0 +1,1258 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QNATIVESOCKETENGINE_DEBUG + +/*! \class QNativeSocketEngine + \internal + + \brief The QNativeSocketEngine class provides low level access to a socket. + + \reentrant + \ingroup network + \inmodule QtNetwork + + QtSocketLayer provides basic socket functionality provided by the + operating system. It also keeps track of what state the socket is + in, and which errors that occur. + + The classes QTcpSocket, QUdpSocket and QTcpServer provide a + higher level API, and are in general more useful for the common + application. + + There are two main ways of initializing the a QNativeSocketEngine; either + create a new socket by passing the socket type (TcpSocket or + UdpSocket) and network layer protocol (IPv4Protocol or + IPv6Protocol) to initialize(), or pass an existing socket + descriptor and have QNativeSocketEngine determine the type and protocol + itself. The native socket descriptor can later be fetched by + calling socketDescriptor(). The socket is made non-blocking, but + blocking behavior can still be achieved by calling waitForRead() + and waitForWrite(). isValid() can be called to check if the socket + has been successfully initialized and is ready to use. + + To connect to a host, determine its address and pass this and the + port number to connectToHost(). The socket can then be used as a + TCP or UDP client. Otherwise; bind(), listen() and accept() are + used to have the socket function as a TCP or UDP server. Call + close() to close the socket. + + bytesAvailable() is called to determine how much data is available + for reading. read() and write() are used by both TCP and UDP + clients to exchange data with the connected peer. UDP clients can + also call hasMoreDatagrams(), nextDatagramSize(), + readDatagram(), and writeDatagram(). + + Call state() to determine the state of the socket, for + example, ListeningState or ConnectedState. socketType() tells + whether the socket is a TCP socket or a UDP socket, or if the + socket type is unknown. protocol() is used to determine the + socket's network layer protocol. + + localAddress(), localPort() are called to find the address and + port that are currently bound to the socket. If the socket is + connected, peerAddress() and peerPort() determine the address and + port of the connected peer. + + Finally, if any function should fail, error() and + errorString() can be called to determine the cause of the error. +*/ + +#include <qabstracteventdispatcher.h> +#include <qsocketnotifier.h> +#include <qnetworkinterface.h> + +#include "qnativesocketengine_p.h" +#include <private/qthread_p.h> +#include <private/qobject_p.h> + +#if !defined(QT_NO_NETWORKPROXY) +# include "qnetworkproxy.h" +# include "qabstractsocket.h" +# include "qtcpserver.h" +#endif + +QT_BEGIN_NAMESPACE + +//#define QNATIVESOCKETENGINE_DEBUG + +#define Q_VOID + +// Common constructs +#define Q_CHECK_VALID_SOCKETLAYER(function, returnValue) do { \ + if (!isValid()) { \ + qWarning(""#function" was called on an uninitialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_INVALID_SOCKETLAYER(function, returnValue) do { \ + if (isValid()) { \ + qWarning(""#function" was called on an already initialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_STATE(function, checkState, returnValue) do { \ + if (d->socketState != (checkState)) { \ + qWarning(""#function" was not called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_NOT_STATE(function, checkState, returnValue) do { \ + if (d->socketState == (checkState)) { \ + qWarning(""#function" was called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_STATES(function, state1, state2, returnValue) do { \ + if (d->socketState != (state1) && d->socketState != (state2)) { \ + qWarning(""#function" was called" \ + " not in "#state1" or "#state2); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_TYPE(function, type, returnValue) do { \ + if (d->socketType != (type)) { \ + qWarning(#function" was called by a" \ + " socket other than "#type""); \ + return (returnValue); \ + } } while (0) +#define Q_TR(a) QT_TRANSLATE_NOOP(QNativeSocketEngine, a) + +/*! \internal + Constructs the private class and initializes all data members. + + On Windows, WSAStartup is called "recursively" for every + concurrent QNativeSocketEngine. This is safe, because WSAStartup and + WSACleanup are reference counted. +*/ +QNativeSocketEnginePrivate::QNativeSocketEnginePrivate() : + socketDescriptor(-1), + readNotifier(0), + writeNotifier(0), + exceptNotifier(0) +{ +} + +/*! \internal + Destructs the private class. +*/ +QNativeSocketEnginePrivate::~QNativeSocketEnginePrivate() +{ +} + +/*! \internal + + Sets the error and error string if not set already. The only + interesting error is the first one that occurred, and not the last + one. +*/ +void QNativeSocketEnginePrivate::setError(QAbstractSocket::SocketError error, ErrorString errorString) const +{ + if (hasSetSocketError) { + // Only set socket errors once for one engine; expect the + // socket to recreate its engine after an error. Note: There's + // one exception: SocketError(11) bypasses this as it's purely + // a temporary internal error condition. + // Another exception is the way the waitFor*() functions set + // an error when a timeout occurs. After the call to setError() + // they reset the hasSetSocketError to false + return; + } + if (error != QAbstractSocket::SocketError(11)) + hasSetSocketError = true; + + socketError = error; + + switch (errorString) { + case NonBlockingInitFailedErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to initialize non-blocking socket"); + break; + case BroadcastingInitFailedErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to initialize broadcast socket"); + break; + case NoIpV6ErrorString: + socketErrorString = QNativeSocketEngine::tr("Attempt to use IPv6 socket on a platform with no IPv6 support"); + break; + case RemoteHostClosedErrorString: + socketErrorString = QNativeSocketEngine::tr("The remote host closed the connection"); + break; + case TimeOutErrorString: + socketErrorString = QNativeSocketEngine::tr("Network operation timed out"); + break; + case ResourceErrorString: + socketErrorString = QNativeSocketEngine::tr("Out of resources"); + break; + case OperationUnsupportedErrorString: + socketErrorString = QNativeSocketEngine::tr("Unsupported socket operation"); + break; + case ProtocolUnsupportedErrorString: + socketErrorString = QNativeSocketEngine::tr("Protocol type not supported"); + break; + case InvalidSocketErrorString: + socketErrorString = QNativeSocketEngine::tr("Invalid socket descriptor"); + break; + case HostUnreachableErrorString: + socketErrorString = QNativeSocketEngine::tr("Host unreachable"); + break; + case NetworkUnreachableErrorString: + socketErrorString = QNativeSocketEngine::tr("Network unreachable"); + break; + case AccessErrorString: + socketErrorString = QNativeSocketEngine::tr("Permission denied"); + break; + case ConnectionTimeOutErrorString: + socketErrorString = QNativeSocketEngine::tr("Connection timed out"); + break; + case ConnectionRefusedErrorString: + socketErrorString = QNativeSocketEngine::tr("Connection refused"); + break; + case AddressInuseErrorString: + socketErrorString = QNativeSocketEngine::tr("The bound address is already in use"); + break; + case AddressNotAvailableErrorString: + socketErrorString = QNativeSocketEngine::tr("The address is not available"); + break; + case AddressProtectedErrorString: + socketErrorString = QNativeSocketEngine::tr("The address is protected"); + break; + case DatagramTooLargeErrorString: + socketErrorString = QNativeSocketEngine::tr("Datagram was too large to send"); + break; + case SendDatagramErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to send a message"); + break; + case ReceiveDatagramErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to receive a message"); + break; + case WriteErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to write"); + break; + case ReadErrorString: + socketErrorString = QNativeSocketEngine::tr("Network error"); + break; + case PortInuseErrorString: + socketErrorString = QNativeSocketEngine::tr("Another socket is already listening on the same port"); + break; + case NotSocketErrorString: + socketErrorString = QNativeSocketEngine::tr("Operation on non-socket"); + break; + case InvalidProxyTypeString: + socketErrorString = QNativeSocketEngine::tr("The proxy type is invalid for this operation"); + break; + case UnknownSocketErrorString: + socketErrorString = QNativeSocketEngine::tr("Unknown error"); + break; + } +} + +bool QNativeSocketEnginePrivate::checkProxy(const QHostAddress &address) +{ + if (address == QHostAddress::LocalHost || address == QHostAddress::LocalHostIPv6) + return true; + +#if !defined(QT_NO_NETWORKPROXY) + QObject *parent = q_func()->parent(); + QNetworkProxy proxy; + if (QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(parent)) { + proxy = socket->proxy(); + } else if (QTcpServer *server = qobject_cast<QTcpServer *>(parent)) { + proxy = server->proxy(); + } else { + // no parent -> no proxy + return true; + } + + if (proxy.type() == QNetworkProxy::DefaultProxy) + proxy = QNetworkProxy::applicationProxy(); + + if (proxy.type() != QNetworkProxy::DefaultProxy && + proxy.type() != QNetworkProxy::NoProxy) { + // QNativeSocketEngine doesn't do proxies + setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::InvalidProxyTypeString); + return false; + } +#endif + + return true; +} + +/*! + Constructs a QNativeSocketEngine. + + \sa initialize() +*/ +QNativeSocketEngine::QNativeSocketEngine(QObject *parent) + : QAbstractSocketEngine(*new QNativeSocketEnginePrivate(), parent) +{ +} + +/*! + Destructs a QNativeSocketEngine. +*/ +QNativeSocketEngine::~QNativeSocketEngine() +{ + close(); +} + +/*! + Initializes a QNativeSocketEngine by creating a new socket of type \a + socketType and network layer protocol \a protocol. Returns true on + success; otherwise returns false. + + If the socket was already initialized, this function closes the + socket before reeinitializing it. + + The new socket is non-blocking, and for UDP sockets it's also + broadcast enabled. +*/ +bool QNativeSocketEngine::initialize(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QNativeSocketEngine); + if (isValid()) + close(); + +#if defined(QT_NO_IPV6) + if (protocol == QAbstractSocket::IPv6Protocol) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NoIpV6ErrorString); + return false; + } +#endif + + // Create the socket + if (!d->createNewSocket(socketType, protocol)) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString typeStr = QLatin1String("UnknownSocketType"); + if (socketType == QAbstractSocket::TcpSocket) typeStr = QLatin1String("TcpSocket"); + else if (socketType == QAbstractSocket::UdpSocket) typeStr = QLatin1String("UdpSocket"); + QString protocolStr = QLatin1String("UnknownProtocol"); + if (protocol == QAbstractSocket::IPv4Protocol) protocolStr = QLatin1String("IPv4Protocol"); + else if (protocol == QAbstractSocket::IPv6Protocol) protocolStr = QLatin1String("IPv6Protocol"); + qDebug("QNativeSocketEngine::initialize(type == %s, protocol == %s) failed: %s", + typeStr.toLatin1().constData(), protocolStr.toLatin1().constData(), d->socketErrorString.toLatin1().constData()); +#endif + return false; + } + + // Make the socket nonblocking. + if (!setOption(NonBlockingSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NonBlockingInitFailedErrorString); + close(); + return false; + } + + // Set the broadcasting flag if it's a UDP socket. + if (socketType == QAbstractSocket::UdpSocket + && !setOption(BroadcastSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString); + close(); + return false; + } + + + // Make sure we receive out-of-band data + if (socketType == QAbstractSocket::TcpSocket + && !setOption(ReceiveOutOfBandData, 1)) { + qWarning("QNativeSocketEngine::initialize unable to inline out-of-band data"); + } + + // Before Qt 4.6, we always set the send and receive buffer size to 49152 as + // this was found to be an optimal value. However, modern OS + // all have some kind of auto tuning for this and we therefore don't set + // this explictly anymore. + // If it introduces any performance regressions for Qt 4.6.x (x > 0) then + // it will be put back in. + // + // You can use tests/manual/qhttpnetworkconnection to test HTTP download speed + // with this. + // + // pre-4.6: + // setReceiveBufferSize(49152); + // setSendBufferSize(49152); + + d->socketType = socketType; + d->socketProtocol = protocol; + return true; +} + +/*! \overload + + Initializes the socket using \a socketDescriptor instead of + creating a new one. The socket type and network layer protocol are + determined automatically. The socket's state is set to \a + socketState. + + If the socket type is either TCP or UDP, it is made non-blocking. + UDP sockets are also broadcast enabled. + */ +bool QNativeSocketEngine::initialize(int socketDescriptor, QAbstractSocket::SocketState socketState) +{ + Q_D(QNativeSocketEngine); + + if (isValid()) + close(); + + d->socketDescriptor = socketDescriptor; + + // determine socket type and protocol + if (!d->fetchConnectionParameters()) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEngine::initialize(socketDescriptor == %i) failed: %s", + socketDescriptor, d->socketErrorString.toLatin1().constData()); +#endif + d->socketDescriptor = -1; + return false; + } + + if (d->socketType != QAbstractSocket::UnknownSocketType) { + // Make the socket nonblocking. + if (!setOption(NonBlockingSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NonBlockingInitFailedErrorString); + close(); + return false; + } + + // Set the broadcasting flag if it's a UDP socket. + if (d->socketType == QAbstractSocket::UdpSocket + && !setOption(BroadcastSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString); + close(); + return false; + } + } + + d->socketState = socketState; + return true; +} + +/*! + Returns true if the socket is valid; otherwise returns false. A + socket is valid if it has not been successfully initialized, or if + it has been closed. +*/ +bool QNativeSocketEngine::isValid() const +{ + Q_D(const QNativeSocketEngine); + return d->socketDescriptor != -1; +} + +/*! + Returns the native socket descriptor. Any use of this descriptor + stands the risk of being non-portable. +*/ +int QNativeSocketEngine::socketDescriptor() const +{ + Q_D(const QNativeSocketEngine); + return d->socketDescriptor; +} + +/*! + Connects to the IP address and port specified by \a address and \a + port. If the connection is established, this function returns true + and the socket enters ConnectedState. Otherwise, false is + returned. + + If false is returned, state() should be called to see if the + socket is in ConnectingState. If so, a delayed TCP connection is + taking place, and connectToHost() must be called again later to + determine if the connection was established successfully or + not. The second connection attempt must be made when the socket is + ready for writing. This state can be determined either by + connecting a QSocketNotifier to the socket descriptor returned by + socketDescriptor(), or by calling the blocking function + waitForWrite(). + + Example: + \snippet doc/src/snippets/code/src_network_socket_qnativesocketengine.cpp 0 + + Otherwise, error() should be called to determine the cause of the + error. +*/ +bool QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::connectToHost(), false); + +#if defined (QT_NO_IPV6) + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NoIpV6ErrorString); + return false; + } +#endif + if (!d->checkProxy(address)) + return false; + + Q_CHECK_STATES(QNativeSocketEngine::connectToHost(), + QAbstractSocket::UnconnectedState, QAbstractSocket::ConnectingState, false); + + d->peerAddress = address; + d->peerPort = port; + bool connected = d->nativeConnect(address, port); + if (connected) + d->fetchConnectionParameters(); + + return connected; +} + +/*! + If there's a connection activity on the socket, process it. Then + notify our parent if there really was activity. +*/ +void QNativeSocketEngine::connectionNotification() +{ + Q_D(QNativeSocketEngine); + Q_ASSERT(state() == QAbstractSocket::ConnectingState); + + connectToHost(d->peerAddress, d->peerPort); + if (state() != QAbstractSocket::ConnectingState) { + // we changed states + QAbstractSocketEngine::connectionNotification(); + } +} + +/*! + Connects to the remote host name given by \a name on port \a + port. When this function is called, the upper-level will not + perform a hostname lookup. + + The native socket engine does not support this operation, + but some other socket engines (notably proxy-based ones) do. +*/ +bool QNativeSocketEngine::connectToHostByName(const QString &name, quint16 port) +{ + Q_UNUSED(name); + Q_UNUSED(port); + Q_D(QNativeSocketEngine); + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::OperationUnsupportedErrorString); + return false; +} + +/*! + Binds the socket to the address \a address and port \a + port. Returns true on success; otherwise false is returned. The + port may be 0, in which case an arbitrary unused port is assigned + automatically by the operating system. + + Servers call this function to set up the server's address and + port. TCP servers must in addition call listen() after bind(). +*/ +bool QNativeSocketEngine::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::bind(), false); + +#if defined (QT_NO_IPV6) + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NoIpV6ErrorString); + return false; + } +#endif + if (!d->checkProxy(address)) + return false; + + Q_CHECK_STATE(QNativeSocketEngine::bind(), QAbstractSocket::UnconnectedState, false); + + if (!d->nativeBind(address, port)) + return false; + + d->fetchConnectionParameters(); + return true; +} + +/*! + Prepares a TCP server for accepting incoming connections. This + function must be called after bind(), and only by TCP sockets. + + After this function has been called, pending client connections + are detected by checking if the socket is ready for reading. This + can be done by either creating a QSocketNotifier, passing the + socket descriptor returned by socketDescriptor(), or by calling + the blocking function waitForRead(). + + Example: + \snippet doc/src/snippets/code/src_network_socket_qnativesocketengine.cpp 1 + + \sa bind(), accept() +*/ +bool QNativeSocketEngine::listen() +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::listen(), false); + Q_CHECK_STATE(QNativeSocketEngine::listen(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QNativeSocketEngine::listen(), QAbstractSocket::TcpSocket, false); + + // We're using a backlog of 50. Most modern kernels support TCP + // syncookies by default, and if they do, the backlog is ignored. + // When there is no support for TCP syncookies, this value is + // fine. + return d->nativeListen(50); +} + +/*! + Accepts a pending connection from the socket, which must be in + ListeningState, and returns its socket descriptor. If no pending + connections are available, -1 is returned. + + \sa bind(), listen() +*/ +int QNativeSocketEngine::accept() +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::accept(), -1); + Q_CHECK_STATE(QNativeSocketEngine::accept(), QAbstractSocket::ListeningState, false); + Q_CHECK_TYPE(QNativeSocketEngine::accept(), QAbstractSocket::TcpSocket, false); + + return d->nativeAccept(); +} + +#ifndef QT_NO_NETWORKINTERFACE + +/*! + \since 4.8 +*/ +bool QNativeSocketEngine::joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::joinMulticastGroup(), false); + Q_CHECK_STATE(QNativeSocketEngine::joinMulticastGroup(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QNativeSocketEngine::joinMulticastGroup(), QAbstractSocket::UdpSocket, false); + return d->nativeJoinMulticastGroup(groupAddress, iface); +} + +/*! + \since 4.8 +*/ +bool QNativeSocketEngine::leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::leaveMulticastGroup(), false); + Q_CHECK_STATE(QNativeSocketEngine::leaveMulticastGroup(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QNativeSocketEngine::leaveMulticastGroup(), QAbstractSocket::UdpSocket, false); + return d->nativeLeaveMulticastGroup(groupAddress, iface); +} + +/*! \since 4.8 */ +QNetworkInterface QNativeSocketEngine::multicastInterface() const +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::multicastInterface(), QNetworkInterface()); + Q_CHECK_TYPE(QNativeSocketEngine::multicastInterface(), QAbstractSocket::UdpSocket, QNetworkInterface()); + return d->nativeMulticastInterface(); +} + +/*! \since 4.8 */ +bool QNativeSocketEngine::setMulticastInterface(const QNetworkInterface &iface) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setMulticastInterface(), false); + Q_CHECK_TYPE(QNativeSocketEngine::setMulticastInterface(), QAbstractSocket::UdpSocket, false); + return d->nativeSetMulticastInterface(iface); +} + +#endif // QT_NO_NETWORKINTERFACE + +/*! + Returns the number of bytes that are currently available for + reading. On error, -1 is returned. + + For UDP sockets, this function returns the accumulated size of all + pending datagrams, and it is therefore more useful for UDP sockets + to call hasPendingDatagrams() and pendingDatagramSize(). +*/ +qint64 QNativeSocketEngine::bytesAvailable() const +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::bytesAvailable(), -1); + Q_CHECK_NOT_STATE(QNativeSocketEngine::bytesAvailable(), QAbstractSocket::UnconnectedState, false); + + return d->nativeBytesAvailable(); +} + +/*! + Returns true if there is at least one datagram pending. This + function is only called by UDP sockets, where a datagram can have + a size of 0. TCP sockets call bytesAvailable(). +*/ +bool QNativeSocketEngine::hasPendingDatagrams() const +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::hasPendingDatagrams(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::hasPendingDatagrams(), QAbstractSocket::UnconnectedState, false); + Q_CHECK_TYPE(QNativeSocketEngine::hasPendingDatagrams(), QAbstractSocket::UdpSocket, false); + + return d->nativeHasPendingDatagrams(); +} + +/*! + Returns the size of the pending datagram, or -1 if no datagram is + pending. A datagram size of 0 is perfectly valid. This function is + called by UDP sockets before receiveMessage(). For TCP sockets, + call bytesAvailable(). +*/ +qint64 QNativeSocketEngine::pendingDatagramSize() const +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::pendingDatagramSize(), -1); + Q_CHECK_TYPE(QNativeSocketEngine::pendingDatagramSize(), QAbstractSocket::UdpSocket, false); + + return d->nativePendingDatagramSize(); +} + +/*! + Reads up to \a maxSize bytes of a datagram from the socket, + stores it in \a data and returns the number of bytes read. The + address and port of the sender are stored in \a address and \a + port. If either of these pointers is 0, the corresponding value is + discarded. + + To avoid unnecessarily loss of data, call pendingDatagramSize() to + determine the size of the pending message before reading it. If \a + maxSize is too small, the rest of the datagram will be lost. + + Returns -1 if an error occurred. + + \sa hasPendingDatagrams() +*/ +qint64 QNativeSocketEngine::readDatagram(char *data, qint64 maxSize, QHostAddress *address, + quint16 *port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::readDatagram(), -1); + Q_CHECK_TYPE(QNativeSocketEngine::readDatagram(), QAbstractSocket::UdpSocket, false); + + return d->nativeReceiveDatagram(data, maxSize, address, port); +} + +/*! + Writes a UDP datagram of size \a size bytes to the socket from + \a data to the address \a host on port \a port, and returns the + number of bytes written, or -1 if an error occurred. + + Only one datagram is sent, and if there is too much data to fit + into a single datagram, the operation will fail and error() + will return QAbstractSocket::DatagramTooLargeError. Operating systems impose an + upper limit to the size of a datagram, but this size is different + on almost all platforms. Sending large datagrams is in general + disadvised, as even if they are sent successfully, they are likely + to be fragmented before arriving at their destination. + + Experience has shown that it is in general safe to send datagrams + no larger than 512 bytes. + + \sa readDatagram() +*/ +qint64 QNativeSocketEngine::writeDatagram(const char *data, qint64 size, + const QHostAddress &host, quint16 port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::writeDatagram(), -1); + Q_CHECK_TYPE(QNativeSocketEngine::writeDatagram(), QAbstractSocket::UdpSocket, -1); + return d->nativeSendDatagram(data, size, host, port); +} + +/*! + Writes a block of \a size bytes from \a data to the socket. + Returns the number of bytes written, or -1 if an error occurred. +*/ +qint64 QNativeSocketEngine::write(const char *data, qint64 size) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::write(), -1); + Q_CHECK_STATE(QNativeSocketEngine::write(), QAbstractSocket::ConnectedState, -1); + return d->nativeWrite(data, size); +} + + +qint64 QNativeSocketEngine::bytesToWrite() const +{ + return 0; +} + +/*! + Reads up to \a maxSize bytes into \a data from the socket. + Returns the number of bytes read, or -1 if an error occurred. +*/ +qint64 QNativeSocketEngine::read(char *data, qint64 maxSize) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::read(), -1); + Q_CHECK_STATES(QNativeSocketEngine::read(), QAbstractSocket::ConnectedState, QAbstractSocket::BoundState, -1); + + qint64 readBytes = d->nativeRead(data, maxSize); + + // Handle remote close + if (readBytes == 0 && d->socketType == QAbstractSocket::TcpSocket) { + d->setError(QAbstractSocket::RemoteHostClosedError, + QNativeSocketEnginePrivate::RemoteHostClosedErrorString); + close(); + return -1; + } else if (readBytes == -1) { + if (!d->hasSetSocketError) { + d->hasSetSocketError = true; + d->socketError = QAbstractSocket::NetworkError; + d->socketErrorString = qt_error_string(); + } + close(); + return -1; + } + return readBytes; +} + +/*! + Closes the socket. In order to use the socket again, initialize() + must be called. +*/ +void QNativeSocketEngine::close() +{ + Q_D(QNativeSocketEngine); + if (d->readNotifier) + d->readNotifier->setEnabled(false); + if (d->writeNotifier) + d->writeNotifier->setEnabled(false); + if (d->exceptNotifier) + d->exceptNotifier->setEnabled(false); + + if(d->socketDescriptor != -1) { + d->nativeClose(); + d->socketDescriptor = -1; + } + d->socketState = QAbstractSocket::UnconnectedState; + d->hasSetSocketError = false; + d->localPort = 0; + d->localAddress.clear(); + d->peerPort = 0; + d->peerAddress.clear(); + if (d->readNotifier) { + qDeleteInEventHandler(d->readNotifier); + d->readNotifier = 0; + } + if (d->writeNotifier) { + qDeleteInEventHandler(d->writeNotifier); + d->writeNotifier = 0; + } + if (d->exceptNotifier) { + qDeleteInEventHandler(d->exceptNotifier); + d->exceptNotifier = 0; + } +} + +/*! + Waits for \a msecs milliseconds or until the socket is ready for + reading. If \a timedOut is not 0 and \a msecs milliseconds have + passed, the value of \a timedOut is set to true. + + Returns true if data is available for reading; otherwise returns + false. + + This is a blocking function call; its use is disadvised in a + single threaded application, as the whole thread will stop + responding until the function returns. waitForRead() is most + useful when there is no event loop available. The general approach + is to create a QSocketNotifier, passing the socket descriptor + returned by socketDescriptor() to its constructor. +*/ +bool QNativeSocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForRead(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForRead(), + QAbstractSocket::UnconnectedState, false); + + if (timedOut) + *timedOut = false; + + int ret = d->nativeSelect(msecs, true); + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + QNativeSocketEnginePrivate::TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +/*! + Waits for \a msecs milliseconds or until the socket is ready for + writing. If \a timedOut is not 0 and \a msecs milliseconds have + passed, the value of \a timedOut is set to true. + + Returns true if data is available for writing; otherwise returns + false. + + This is a blocking function call; its use is disadvised in a + single threaded application, as the whole thread will stop + responding until the function returns. waitForWrite() is most + useful when there is no event loop available. The general approach + is to create a QSocketNotifier, passing the socket descriptor + returned by socketDescriptor() to its constructor. +*/ +bool QNativeSocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForWrite(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForWrite(), + QAbstractSocket::UnconnectedState, false); + + if (timedOut) + *timedOut = false; + + int ret = d->nativeSelect(msecs, false); + // On Windows, the socket is in connected state if a call to + // select(writable) is successful. In this case we should not + // issue a second call to WSAConnect() +#if defined (Q_WS_WIN) + if (ret > 0) { + setState(QAbstractSocket::ConnectedState); + d_func()->fetchConnectionParameters(); + return true; + } else { + int value = 0; + int valueSize = sizeof(value); + if (::getsockopt(d->socketDescriptor, SOL_SOCKET, SO_ERROR, (char *) &value, &valueSize) == 0) { + if (value == WSAECONNREFUSED) { + d->setError(QAbstractSocket::ConnectionRefusedError, QNativeSocketEnginePrivate::ConnectionRefusedErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } else if (value == WSAETIMEDOUT) { + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::ConnectionTimeOutErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } else if (value == WSAEHOSTUNREACH) { + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::HostUnreachableErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } + } + } +#endif + + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + QNativeSocketEnginePrivate::TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +bool QNativeSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForWrite(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForReadOrWrite(), + QAbstractSocket::UnconnectedState, false); + + int ret = d->nativeSelect(msecs, checkRead, checkWrite, readyToRead, readyToWrite); + // On Windows, the socket is in connected state if a call to + // select(writable) is successful. In this case we should not + // issue a second call to WSAConnect() +#if defined (Q_WS_WIN) + if (checkWrite && ((readyToWrite && *readyToWrite) || !readyToWrite) && ret > 0) { + setState(QAbstractSocket::ConnectedState); + d_func()->fetchConnectionParameters(); + return true; + } else { + int value = 0; + int valueSize = sizeof(value); + if (::getsockopt(d->socketDescriptor, SOL_SOCKET, SO_ERROR, (char *) &value, &valueSize) == 0) { + if (value == WSAECONNREFUSED) { + d->setError(QAbstractSocket::ConnectionRefusedError, QNativeSocketEnginePrivate::ConnectionRefusedErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } else if (value == WSAETIMEDOUT) { + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::ConnectionTimeOutErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } else if (value == WSAEHOSTUNREACH) { + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::HostUnreachableErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } + } + } +#endif + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + QNativeSocketEnginePrivate::TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +/*! + Returns the size of the operating system's socket receive + buffer. Depending on the operating system, this size may be + different from what has been set earlier with + setReceiveBufferSize(). +*/ +qint64 QNativeSocketEngine::receiveBufferSize() const +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::receiveBufferSize(), -1); + return option(ReceiveBufferSocketOption); +} + +/*! + Sets the size of the operating system receive buffer to \a size. + + For clients, this should be set before connectToHost() is called; + otherwise it will have no effect. For servers, it should be called + before listen(). + + The operating system receive buffer size effectively limits two + things: how much data can be in transit at any one moment, and how + much data can be received in one iteration of the main event loop. + Setting the size of the receive buffer may have an impact on the + socket's performance. + + The default value is operating system-dependent. +*/ +void QNativeSocketEngine::setReceiveBufferSize(qint64 size) +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setReceiveBufferSize(), Q_VOID); + setOption(ReceiveBufferSocketOption, size); +} + +/*! + Returns the size of the operating system send buffer. Depending on + the operating system, this size may be different from what has + been set earlier with setSendBufferSize(). +*/ +qint64 QNativeSocketEngine::sendBufferSize() const +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setSendBufferSize(), -1); + return option(SendBufferSocketOption); +} + +/*! + Sets the size of the operating system send buffer to \a size. + + The operating system send buffer size effectively limits how much + data can be in transit at any one moment. Setting the size of the + send buffer may have an impact on the socket's performance. + + The default value is operating system-dependent. +*/ +void QNativeSocketEngine::setSendBufferSize(qint64 size) +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setSendBufferSize(), Q_VOID); + setOption(SendBufferSocketOption, size); +} + + +/*! + Sets the option \a option to the value \a value. +*/ +bool QNativeSocketEngine::setOption(SocketOption option, int value) +{ + Q_D(QNativeSocketEngine); + return d->setOption(option, value); +} + +/*! + Returns the value of the option \a socketOption. +*/ +int QNativeSocketEngine::option(SocketOption socketOption) const +{ + Q_D(const QNativeSocketEngine); + return d->option(socketOption); +} + +bool QNativeSocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->readNotifier && d->readNotifier->isEnabled(); +} + +/* + \internal + \class QReadNotifier + \brief The QReadNotifer class is used to improve performance. + + QReadNotifier is a private class used for performance reasons vs + connecting to the QSocketNotifier activated() signal. + */ +class QReadNotifier : public QSocketNotifier +{ +public: + QReadNotifier(int fd, QNativeSocketEngine *parent) + : QSocketNotifier(fd, QSocketNotifier::Read, parent) + { engine = parent; } + +protected: + bool event(QEvent *); + + QNativeSocketEngine *engine; +}; + +bool QReadNotifier::event(QEvent *e) +{ + if (e->type() == QEvent::SockAct) { + engine->readNotification(); + return true; + } + return QSocketNotifier::event(e); +} + +/* + \internal + \class QWriteNotifier + \brief The QWriteNotifer class is used to improve performance. + + QWriteNotifier is a private class used for performance reasons vs + connecting to the QSocketNotifier activated() signal. + */ +class QWriteNotifier : public QSocketNotifier +{ +public: + QWriteNotifier(int fd, QNativeSocketEngine *parent) + : QSocketNotifier(fd, QSocketNotifier::Write, parent) { engine = parent; } + +protected: + bool event(QEvent *); + + QNativeSocketEngine *engine; +}; + +bool QWriteNotifier::event(QEvent *e) +{ + if (e->type() == QEvent::SockAct) { + if (engine->state() == QAbstractSocket::ConnectingState) + engine->connectionNotification(); + else + engine->writeNotification(); + return true; + } + return QSocketNotifier::event(e); +} + +class QExceptionNotifier : public QSocketNotifier +{ +public: + QExceptionNotifier(int fd, QNativeSocketEngine *parent) + : QSocketNotifier(fd, QSocketNotifier::Exception, parent) { engine = parent; } + +protected: + bool event(QEvent *); + + QNativeSocketEngine *engine; +}; + +bool QExceptionNotifier::event(QEvent *e) +{ + if (e->type() == QEvent::SockAct) { + if (engine->state() == QAbstractSocket::ConnectingState) + engine->connectionNotification(); + else + engine->exceptionNotification(); + return true; + } + return QSocketNotifier::event(e); +} + +void QNativeSocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + if (d->readNotifier) { + d->readNotifier->setEnabled(enable); + } else if (enable && d->threadData->eventDispatcher) { + d->readNotifier = new QReadNotifier(d->socketDescriptor, this); + d->readNotifier->setEnabled(true); + } +} + +bool QNativeSocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->writeNotifier && d->writeNotifier->isEnabled(); +} + +void QNativeSocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + if (d->writeNotifier) { + d->writeNotifier->setEnabled(enable); + } else if (enable && d->threadData->eventDispatcher) { + d->writeNotifier = new QWriteNotifier(d->socketDescriptor, this); + d->writeNotifier->setEnabled(true); + } +} + +bool QNativeSocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->exceptNotifier && d->exceptNotifier->isEnabled(); +} + +void QNativeSocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + if (d->exceptNotifier) { + d->exceptNotifier->setEnabled(enable); + } else if (enable && d->threadData->eventDispatcher) { + d->exceptNotifier = new QExceptionNotifier(d->socketDescriptor, this); + d->exceptNotifier->setEnabled(true); + } +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnativesocketengine_p.h b/src/network/socket/qnativesocketengine_p.h new file mode 100644 index 0000000000..35054fb414 --- /dev/null +++ b/src/network/socket/qnativesocketengine_p.h @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNATIVESOCKETENGINE_P_H +#define QNATIVESOCKETENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// +#include "QtNetwork/qhostaddress.h" +#include "private/qabstractsocketengine_p.h" +#ifndef Q_OS_WIN +# include "qplatformdefs.h" +#else +# include <winsock2.h> +#endif + +QT_BEGIN_NAMESPACE + +// Use our own defines and structs which we know are correct +# define QT_SS_MAXSIZE 128 +# define QT_SS_ALIGNSIZE (sizeof(qint64)) +# define QT_SS_PAD1SIZE (QT_SS_ALIGNSIZE - sizeof (short)) +# define QT_SS_PAD2SIZE (QT_SS_MAXSIZE - (sizeof (short) + QT_SS_PAD1SIZE + QT_SS_ALIGNSIZE)) +struct qt_sockaddr_storage { + short ss_family; + char __ss_pad1[QT_SS_PAD1SIZE]; + qint64 __ss_align; + char __ss_pad2[QT_SS_PAD2SIZE]; +}; + +// sockaddr_in6 size changed between old and new SDK +// Only the new version is the correct one, so always +// use this structure. +struct qt_in6_addr { + quint8 qt_s6_addr[16]; +}; +struct qt_sockaddr_in6 { + short sin6_family; /* AF_INET6 */ + quint16 sin6_port; /* Transport level port number */ + quint32 sin6_flowinfo; /* IPv6 flow information */ + struct qt_in6_addr sin6_addr; /* IPv6 address */ + quint32 sin6_scope_id; /* set of interfaces for a scope */ +}; + +union qt_sockaddr { + sockaddr a; + sockaddr_in a4; + qt_sockaddr_in6 a6; + qt_sockaddr_storage storage; +}; + +class QNativeSocketEnginePrivate; +#ifndef QT_NO_NETWORKINTERFACE +class QNetworkInterface; +#endif + +class Q_AUTOTEST_EXPORT QNativeSocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT +public: + QNativeSocketEngine(QObject *parent = 0); + ~QNativeSocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + int socketDescriptor() const; + + bool isValid() const; + + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + +#ifndef QT_NO_NETWORKINTERFACE + bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + QNetworkInterface multicastInterface() const; + bool setMulticastInterface(const QNetworkInterface &iface); +#endif + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; + + qint64 bytesToWrite() const; + + qint64 receiveBufferSize() const; + void setReceiveBufferSize(qint64 bufferSize); + + qint64 sendBufferSize() const; + void setSendBufferSize(qint64 bufferSize); + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + +public Q_SLOTS: + // non-virtual override; + void connectionNotification(); + +private: + Q_DECLARE_PRIVATE(QNativeSocketEngine) + Q_DISABLE_COPY(QNativeSocketEngine) +}; + +#ifdef Q_OS_WIN +class QWindowsSockInit +{ +public: + QWindowsSockInit(); + ~QWindowsSockInit(); + int version; +}; +#endif + +class QSocketNotifier; + +class QNativeSocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QNativeSocketEngine) +public: + QNativeSocketEnginePrivate(); + ~QNativeSocketEnginePrivate(); + + int socketDescriptor; + + QSocketNotifier *readNotifier, *writeNotifier, *exceptNotifier; + +#ifdef Q_OS_WIN + QWindowsSockInit winSock; +#endif + + enum ErrorString { + NonBlockingInitFailedErrorString, + BroadcastingInitFailedErrorString, + NoIpV6ErrorString, + RemoteHostClosedErrorString, + TimeOutErrorString, + ResourceErrorString, + OperationUnsupportedErrorString, + ProtocolUnsupportedErrorString, + InvalidSocketErrorString, + HostUnreachableErrorString, + NetworkUnreachableErrorString, + AccessErrorString, + ConnectionTimeOutErrorString, + ConnectionRefusedErrorString, + AddressInuseErrorString, + AddressNotAvailableErrorString, + AddressProtectedErrorString, + DatagramTooLargeErrorString, + SendDatagramErrorString, + ReceiveDatagramErrorString, + WriteErrorString, + ReadErrorString, + PortInuseErrorString, + NotSocketErrorString, + InvalidProxyTypeString, + + UnknownSocketErrorString = -1 + }; + + void setError(QAbstractSocket::SocketError error, ErrorString errorString) const; + + // native functions + int option(QNativeSocketEngine::SocketOption option) const; + bool setOption(QNativeSocketEngine::SocketOption option, int value); + + bool createNewSocket(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol); + + bool nativeConnect(const QHostAddress &address, quint16 port); + bool nativeBind(const QHostAddress &address, quint16 port); + bool nativeListen(int backlog); + int nativeAccept(); +#ifndef QT_NO_NETWORKINTERFACE + bool nativeJoinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + bool nativeLeaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + QNetworkInterface nativeMulticastInterface() const; + bool nativeSetMulticastInterface(const QNetworkInterface &iface); +#endif + qint64 nativeBytesAvailable() const; + + bool nativeHasPendingDatagrams() const; + qint64 nativePendingDatagramSize() const; + qint64 nativeReceiveDatagram(char *data, qint64 maxLength, + QHostAddress *address, quint16 *port); + qint64 nativeSendDatagram(const char *data, qint64 length, + const QHostAddress &host, quint16 port); + qint64 nativeRead(char *data, qint64 maxLength); + qint64 nativeWrite(const char *data, qint64 length); + int nativeSelect(int timeout, bool selectForRead) const; + int nativeSelect(int timeout, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const; + + void nativeClose(); + + bool checkProxy(const QHostAddress &address); + bool fetchConnectionParameters(); +}; + +QT_END_NAMESPACE + +#endif // QNATIVESOCKETENGINE_P_H diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp new file mode 100644 index 0000000000..43184271e5 --- /dev/null +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -0,0 +1,1125 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QNATIVESOCKETENGINE_DEBUG +#include "qnativesocketengine_p.h" +#include "private/qnet_unix_p.h" +#include "qiodevice.h" +#include "qhostaddress.h" +#include "qelapsedtimer.h" +#include "qvarlengtharray.h" +#include "qnetworkinterface.h" +#include <time.h> +#include <errno.h> +#include <fcntl.h> +#ifndef QT_NO_IPV6IFNAME +#include <net/if.h> +#endif +#ifndef QT_NO_IPV6IFNAME +#include <net/if.h> +#endif +#ifdef QT_LINUXBASE +#include <arpa/inet.h> +#endif + +#if defined QNATIVESOCKETENGINE_DEBUG +#include <qstring.h> +#include <ctype.h> +#endif + +#include <netinet/tcp.h> + +QT_BEGIN_NAMESPACE + +#if defined QNATIVESOCKETENGINE_DEBUG + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(c)) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1(); + } + } + + if (len < maxSize) + out += "..."; + + return out; +} +#endif + +static void qt_ignore_sigpipe() +{ +#ifndef Q_NO_POSIX_SIGNALS + // Set to ignore SIGPIPE once only. + static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0); + if (atom.testAndSetRelaxed(0, 1)) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + ::sigaction(SIGPIPE, &noaction, 0); + } +#else + // Posix signals are not supported by the underlying platform + // so we don't need to ignore sigpipe signal explicitly +#endif +} + +/* + 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 !defined(QT_NO_IPV6) + 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; +#ifndef QT_NO_IPV6IFNAME + char scopeid[IFNAMSIZ]; + if (::if_indextoname(s->a6.sin6_scope_id, scopeid)) { + addr->setScopeId(QLatin1String(scopeid)); + } else +#endif + addr->setScopeId(QString::number(s->a6.sin6_scope_id)); + } + if (port) + *port = ntohs(s->a6.sin6_port); + return; + } +#endif + if (port) + *port = ntohs(s->a4.sin_port); + if (addr) { + QHostAddress tmpAddress; + tmpAddress.setAddress(ntohl(s->a4.sin_addr.s_addr)); + *addr = tmpAddress; + } +} + +/*! \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_IPV6 + int protocol = (socketProtocol == QAbstractSocket::IPv6Protocol) ? AF_INET6 : AF_INET; +#else + Q_UNUSED(socketProtocol); + int protocol = AF_INET; +#endif + int type = (socketType == QAbstractSocket::UdpSocket) ? SOCK_DGRAM : SOCK_STREAM; + + int socket = qt_safe_socket(protocol, type, 0); + + if (socket <= 0) { + switch (errno) { + 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; + } + + return false; + } + + socketDescriptor = socket; + 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; + + int n = -1; + int level = SOL_SOCKET; // default + + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::NonBlockingSocketOption: + break; + case QNativeSocketEngine::BroadcastSocketOption: + break; + case QNativeSocketEngine::AddressReusable: + n = SO_REUSEADDR; + break; + case QNativeSocketEngine::BindExclusively: + return true; + 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: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_HOPS; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_TTL; + } + break; + case QNativeSocketEngine::MulticastLoopbackOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_LOOP; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_LOOP; + } + break; + } + + int v = -1; + QT_SOCKOPTLEN_T len = sizeof(v); + if (::getsockopt(socketDescriptor, level, n, (char *) &v, &len) != -1) + return 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; + + int n = 0; + int level = SOL_SOCKET; // default + + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::BroadcastSocketOption: + n = SO_BROADCAST; + break; + 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::AddressReusable: +#if defined(SO_REUSEPORT) + n = SO_REUSEPORT; +#else + n = SO_REUSEADDR; +#endif + break; + case QNativeSocketEngine::BindExclusively: + return true; + 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: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_HOPS; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_TTL; + } + break; + case QNativeSocketEngine::MulticastLoopbackOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_LOOP; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_LOOP; + } + break; + } + + return ::setsockopt(socketDescriptor, level, n, (char *) &v, sizeof(v)) == 0; +} + +bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &addr, quint16 port) +{ +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug("QNativeSocketEnginePrivate::nativeConnect() : %d ", socketDescriptor); +#endif + + struct sockaddr_in sockAddrIPv4; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 sockAddrIPv6; + + if (addr.protocol() == QAbstractSocket::IPv6Protocol) { + memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6)); + sockAddrIPv6.sin6_family = AF_INET6; + sockAddrIPv6.sin6_port = htons(port); + + QString scopeid = addr.scopeId(); + bool ok; + sockAddrIPv6.sin6_scope_id = scopeid.toInt(&ok); +#ifndef QT_NO_IPV6IFNAME + if (!ok) + sockAddrIPv6.sin6_scope_id = ::if_nametoindex(scopeid.toLatin1()); +#endif + Q_IPV6ADDR ip6 = addr.toIPv6Address(); + memcpy(&sockAddrIPv6.sin6_addr.s6_addr, &ip6, sizeof(ip6)); + + sockAddrSize = sizeof(sockAddrIPv6); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv6; + } else +#if 0 + {} +#endif +#endif + if (addr.protocol() == QAbstractSocket::IPv4Protocol) { + memset(&sockAddrIPv4, 0, sizeof(sockAddrIPv4)); + sockAddrIPv4.sin_family = AF_INET; + sockAddrIPv4.sin_port = htons(port); + sockAddrIPv4.sin_addr.s_addr = htonl(addr.toIPv4Address()); + + sockAddrSize = sizeof(sockAddrIPv4); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv4; + } else { + // unreachable + } + + int connectResult = qt_safe_connect(socketDescriptor, sockAddrPtr, sockAddrSize); + 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); + setError(QAbstractSocket::SocketResourceError, ResourceErrorString); + 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" : socketErrorString.toLatin1().constData()); +#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) +{ + struct sockaddr_in sockAddrIPv4; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 sockAddrIPv6; + + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6)); + sockAddrIPv6.sin6_family = AF_INET6; + sockAddrIPv6.sin6_port = htons(port); +#ifndef QT_NO_IPV6IFNAME + sockAddrIPv6.sin6_scope_id = ::if_nametoindex(address.scopeId().toLatin1().data()); +#else + sockAddrIPv6.sin6_scope_id = address.scopeId().toInt(); +#endif + Q_IPV6ADDR tmp = address.toIPv6Address(); + memcpy(&sockAddrIPv6.sin6_addr.s6_addr, &tmp, sizeof(tmp)); + sockAddrSize = sizeof(sockAddrIPv6); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv6; + } else +#endif + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + memset(&sockAddrIPv4, 0, sizeof(sockAddrIPv4)); + sockAddrIPv4.sin_family = AF_INET; + sockAddrIPv4.sin_port = htons(port); + sockAddrIPv4.sin_addr.s_addr = htonl(address.toIPv4Address()); + sockAddrSize = sizeof(sockAddrIPv4); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv4; + } else { + // unreachable + } + + int bindResult = QT_SOCKET_BIND(socketDescriptor, sockAddrPtr, sockAddrSize); + + if (bindResult < 0) { + 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, socketErrorString.toLatin1().constData()); +#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) { + switch (errno) { + case EADDRINUSE: + setError(QAbstractSocket::AddressInUseError, + PortInuseErrorString); + break; + default: + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == false (%s)", + backlog, socketErrorString.toLatin1().constData()); +#endif + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == true", backlog); +#endif + + socketState = QAbstractSocket::ListeningState; + return true; +} + +int QNativeSocketEnginePrivate::nativeAccept() +{ + int acceptedDescriptor = qt_safe_accept(socketDescriptor, 0, 0); + + return 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; +#ifndef QT_NO_IPV6 + 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 +#endif + 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()) { + QList<QNetworkAddressEntry> addressEntries = interface.addressEntries(); + if (!addressEntries.isEmpty()) { + QHostAddress firstIP = addressEntries.first().ip(); + mreq4.imr_interface.s_addr = htonl(firstIP.toIPv4Address()); + } else { + 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, +#ifndef QT_NO_IPV6 + IPV6_JOIN_GROUP, +#else + 0, +#endif + IP_ADD_MEMBERSHIP, + groupAddress, + interface); +} + +bool QNativeSocketEnginePrivate::nativeLeaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &interface) +{ + return multicastMembershipHelper(this, +#ifndef QT_NO_IPV6 + IPV6_LEAVE_GROUP, +#else + 0, +#endif + IP_DROP_MEMBERSHIP, + groupAddress, + interface); +} + +QNetworkInterface QNativeSocketEnginePrivate::nativeMulticastInterface() const +{ +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + 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); + } +#endif + + struct in_addr v = { 0 }; + 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 >= sizeof(v)) { + QHostAddress ipv4(ntohl(v.s_addr)); + QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces(); + for (int i = 0; i < ifaces.count(); ++i) { + const QNetworkInterface &iface = ifaces.at(i); + QList<QNetworkAddressEntry> entries = iface.addressEntries(); + for (int j = 0; j < entries.count(); ++j) { + const QNetworkAddressEntry &entry = entries.at(j); + if (entry.ip() == ipv4) + return iface; + } + } + } + return QNetworkInterface(); +} + +bool QNativeSocketEnginePrivate::nativeSetMulticastInterface(const QNetworkInterface &iface) +{ +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + uint v = iface.index(); + return (::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF, &v, sizeof(v)) != -1); + } +#endif + + struct in_addr v; + if (iface.isValid()) { + QList<QNetworkAddressEntry> entries = iface.addressEntries(); + for (int i = 0; i < entries.count(); ++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 = 0; + if (qt_safe_ioctl(socketDescriptor, FIONREAD, (char *) &nbytes) >= 0) + available = (qint64) nbytes; + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBytesAvailable() == %lli", available); +#endif + return available; +} + +bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const +{ + // Create a sockaddr struct and reset its port number. + qt_sockaddr storage; + QT_SOCKLEN_T storageSize = sizeof(storage); + memset(&storage, 0, storageSize); + + // Peek 0 bytes into the next message. The size of the message may + // well be 0, so we can't check recvfrom's return value. + ssize_t readBytes; + do { + char c; + readBytes = ::recvfrom(socketDescriptor, &c, 1, MSG_PEEK, &storage.a, &storageSize); + } while (readBytes == -1 && errno == EINTR); + + // 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 +{ + QVarLengthArray<char, 8192> udpMessagePeekBuffer(8192); + ssize_t recvResult = -1; + + for (;;) { + // the data written to udpMessagePeekBuffer is discarded, so + // this function is still reentrant although it might not look + // so. + recvResult = ::recv(socketDescriptor, udpMessagePeekBuffer.data(), + udpMessagePeekBuffer.size(), MSG_PEEK); + if (recvResult == -1 && errno == EINTR) + continue; + + if (recvResult != (ssize_t) udpMessagePeekBuffer.size()) + break; + + udpMessagePeekBuffer.resize(udpMessagePeekBuffer.size() * 2); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativePendingDatagramSize() == %i", recvResult); +#endif + + return qint64(recvResult); +} + +qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxSize, + QHostAddress *address, quint16 *port) +{ + qt_sockaddr aa; + memset(&aa, 0, sizeof(aa)); + QT_SOCKLEN_T sz; + sz = sizeof(aa); + + ssize_t recvFromResult = 0; + do { + char c; + recvFromResult = ::recvfrom(socketDescriptor, maxSize ? data : &c, maxSize ? maxSize : 1, + 0, &aa.a, &sz); + } while (recvFromResult == -1 && errno == EINTR); + + if (recvFromResult == -1) { + setError(QAbstractSocket::NetworkError, ReceiveDatagramErrorString); + } else if (port || address) { + qt_socket_getPortAndAddress(&aa, port, address); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeReceiveDatagram(%p \"%s\", %lli, %s, %i) == %lli", + data, qt_prettyDebug(data, qMin(recvFromResult, ssize_t(16)), recvFromResult).data(), maxSize, + address ? address->toString().toLatin1().constData() : "(nil)", + port ? *port : 0, (qint64) recvFromResult); +#endif + + return qint64(maxSize ? recvFromResult : recvFromResult == -1 ? -1 : 0); +} + +qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 len, + const QHostAddress &host, quint16 port) +{ + struct sockaddr_in sockAddrIPv4; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 sockAddrIPv6; + if (host.protocol() == QAbstractSocket::IPv6Protocol) { + memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6)); + sockAddrIPv6.sin6_family = AF_INET6; + sockAddrIPv6.sin6_port = htons(port); + + Q_IPV6ADDR tmp = host.toIPv6Address(); + memcpy(&sockAddrIPv6.sin6_addr.s6_addr, &tmp, sizeof(tmp)); + sockAddrSize = sizeof(sockAddrIPv6); + sockAddrPtr = (struct sockaddr *)&sockAddrIPv6; + } else +#endif + if (host.protocol() == QAbstractSocket::IPv4Protocol) { + memset(&sockAddrIPv4, 0, sizeof(sockAddrIPv4)); + sockAddrIPv4.sin_family = AF_INET; + sockAddrIPv4.sin_port = htons(port); + sockAddrIPv4.sin_addr.s_addr = htonl(host.toIPv4Address()); + sockAddrSize = sizeof(sockAddrIPv4); + sockAddrPtr = (struct sockaddr *)&sockAddrIPv4; + } + + // ignore the SIGPIPE signal + qt_ignore_sigpipe(); + ssize_t sentBytes = qt_safe_sendto(socketDescriptor, data, len, + 0, sockAddrPtr, sockAddrSize); + + if (sentBytes < 0) { + switch (errno) { + case EMSGSIZE: + setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString); + break; + default: + setError(QAbstractSocket::NetworkError, SendDatagramErrorString); + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEngine::sendDatagram(%p \"%s\", %lli, \"%s\", %i) == %lli", data, + qt_prettyDebug(data, qMin<int>(len, 16), len).data(), len, host.toString().toLatin1().constData(), + port, (qint64) sentBytes); +#endif + + return qint64(sentBytes); +} + +bool QNativeSocketEnginePrivate::fetchConnectionParameters() +{ + localPort = 0; + localAddress.clear(); + peerPort = 0; + peerAddress.clear(); + + 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; +#if !defined (QT_NO_IPV6) + case AF_INET6: + socketProtocol = QAbstractSocket::IPv6Protocol; + break; +#endif + default: + socketProtocol = QAbstractSocket::UnknownNetworkLayerProtocol; + break; + } + + } else if (errno == EBADF) { + setError(QAbstractSocket::UnsupportedSocketOperationError, InvalidSocketErrorString); + return false; + } + + // Determine the remote address + if (!::getpeername(socketDescriptor, &sa.a, &sockAddrSize)) + qt_socket_getPortAndAddress(&sa, &peerPort, &peerAddress); + + // Determine the socket type (UDP/TCP) + int value = 0; + QT_SOCKOPTLEN_T valueSize = sizeof(int); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_TYPE, &value, &valueSize) == 0) { + if (value == SOCK_STREAM) + socketType = QAbstractSocket::TcpSocket; + else if (value == SOCK_DGRAM) + socketType = QAbstractSocket::UdpSocket; + else + socketType = QAbstractSocket::UnknownSocketType; + } +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString socketProtocolStr = "UnknownProtocol"; + if (socketProtocol == QAbstractSocket::IPv4Protocol) socketProtocolStr = "IPv4Protocol"; + else if (socketProtocol == QAbstractSocket::IPv6Protocol) socketProtocolStr = "IPv6Protocol"; + + QString socketTypeStr = "UnknownSocketType"; + if (socketType == QAbstractSocket::TcpSocket) socketTypeStr = "TcpSocket"; + else if (socketType == QAbstractSocket::UdpSocket) socketTypeStr = "UdpSocket"; + + qDebug("QNativeSocketEnginePrivate::fetchConnectionParameters() local == %s:%i," + " peer == %s:%i, socket == %s - %s", + localAddress.toString().toLatin1().constData(), localPort, + peerAddress.toString().toLatin1().constData(), peerPort,socketTypeStr.toLatin1().constData(), + socketProtocolStr.toLatin1().constData()); +#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); + + // ignore the SIGPIPE signal + qt_ignore_sigpipe(); + + ssize_t writtenBytes; + writtenBytes = qt_safe_write(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, qt_prettyDebug(data, qMin((int) len, 16), + (int) len).data(), 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 EBADF: + case EINVAL: + case EIO: + //error string is now set in read(), not here in nativeRead() + break; + case ECONNRESET: +#if defined(Q_OS_VXWORKS) + case ESHUTDOWN: +#endif + r = 0; + break; + default: + break; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeRead(%p \"%s\", %llu) == %i", + data, qt_prettyDebug(data, qMin(r, ssize_t(16)), r).data(), + maxSize, r); +#endif + + return qint64(r); +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) const +{ + fd_set fds; + FD_ZERO(&fds); + FD_SET(socketDescriptor, &fds); + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + int retval; + if (selectForRead) + retval = qt_safe_select(socketDescriptor + 1, &fds, 0, 0, timeout < 0 ? 0 : &tv); + else + retval = qt_safe_select(socketDescriptor + 1, 0, &fds, 0, timeout < 0 ? 0 : &tv); + + return retval; +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const +{ + fd_set fdread; + FD_ZERO(&fdread); + if (checkRead) + FD_SET(socketDescriptor, &fdread); + + fd_set fdwrite; + FD_ZERO(&fdwrite); + if (checkWrite) + FD_SET(socketDescriptor, &fdwrite); + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + int ret; + ret = qt_safe_select(socketDescriptor + 1, &fdread, &fdwrite, 0, timeout < 0 ? 0 : &tv); + + if (ret <= 0) + return ret; + *selectForRead = FD_ISSET(socketDescriptor, &fdread); + *selectForWrite = FD_ISSET(socketDescriptor, &fdwrite); + + return ret; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp new file mode 100644 index 0000000000..940569aa7b --- /dev/null +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -0,0 +1,1439 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <winsock2.h> +#include <ws2tcpip.h> + +#include "qnativesocketengine_p.h" + +#include <qabstracteventdispatcher.h> +#include <qsocketnotifier.h> +#include <qdebug.h> +#include <qdatetime.h> +#include <qnetworkinterface.h> + +//#define QNATIVESOCKETENGINE_DEBUG +#if defined(QNATIVESOCKETENGINE_DEBUG) +# include <qstring.h> +# include <qbytearray.h> +#endif + +QT_BEGIN_NAMESPACE + +#if defined(QNATIVESOCKETENGINE_DEBUG) + +void verboseWSErrorDebug(int r) +{ + switch (r) { + case WSANOTINITIALISED : qDebug("WSA error : WSANOTINITIALISED"); break; + case WSAEINTR: qDebug("WSA error : WSAEINTR"); break; + case WSAEBADF: qDebug("WSA error : WSAEBADF"); break; + case WSAEACCES: qDebug("WSA error : WSAEACCES"); break; + case WSAEFAULT: qDebug("WSA error : WSAEFAULT"); break; + case WSAEINVAL: qDebug("WSA error : WSAEINVAL"); break; + case WSAEMFILE: qDebug("WSA error : WSAEMFILE"); break; + case WSAEWOULDBLOCK: qDebug("WSA error : WSAEWOULDBLOCK"); break; + case WSAEINPROGRESS: qDebug("WSA error : WSAEINPROGRESS"); break; + case WSAEALREADY: qDebug("WSA error : WSAEALREADY"); break; + case WSAENOTSOCK: qDebug("WSA error : WSAENOTSOCK"); break; + case WSAEDESTADDRREQ: qDebug("WSA error : WSAEDESTADDRREQ"); break; + case WSAEMSGSIZE: qDebug("WSA error : WSAEMSGSIZE"); break; + case WSAEPROTOTYPE: qDebug("WSA error : WSAEPROTOTYPE"); break; + case WSAENOPROTOOPT: qDebug("WSA error : WSAENOPROTOOPT"); break; + case WSAEPROTONOSUPPORT: qDebug("WSA error : WSAEPROTONOSUPPORT"); break; + case WSAESOCKTNOSUPPORT: qDebug("WSA error : WSAESOCKTNOSUPPORT"); break; + case WSAEOPNOTSUPP: qDebug("WSA error : WSAEOPNOTSUPP"); break; + case WSAEPFNOSUPPORT: qDebug("WSA error : WSAEPFNOSUPPORT"); break; + case WSAEAFNOSUPPORT: qDebug("WSA error : WSAEAFNOSUPPORT"); break; + case WSAEADDRINUSE: qDebug("WSA error : WSAEADDRINUSE"); break; + case WSAEADDRNOTAVAIL: qDebug("WSA error : WSAEADDRNOTAVAIL"); break; + case WSAENETDOWN: qDebug("WSA error : WSAENETDOWN"); break; + case WSAENETUNREACH: qDebug("WSA error : WSAENETUNREACH"); break; + case WSAENETRESET: qDebug("WSA error : WSAENETRESET"); break; + case WSAECONNABORTED: qDebug("WSA error : WSAECONNABORTED"); break; + case WSAECONNRESET: qDebug("WSA error : WSAECONNRESET"); break; + case WSAENOBUFS: qDebug("WSA error : WSAENOBUFS"); break; + case WSAEISCONN: qDebug("WSA error : WSAEISCONN"); break; + case WSAENOTCONN: qDebug("WSA error : WSAENOTCONN"); break; + case WSAESHUTDOWN: qDebug("WSA error : WSAESHUTDOWN"); break; + case WSAETOOMANYREFS: qDebug("WSA error : WSAETOOMANYREFS"); break; + case WSAETIMEDOUT: qDebug("WSA error : WSAETIMEDOUT"); break; + case WSAECONNREFUSED: qDebug("WSA error : WSAECONNREFUSED"); break; + case WSAELOOP: qDebug("WSA error : WSAELOOP"); break; + case WSAENAMETOOLONG: qDebug("WSA error : WSAENAMETOOLONG"); break; + case WSAEHOSTDOWN: qDebug("WSA error : WSAEHOSTDOWN"); break; + case WSAEHOSTUNREACH: qDebug("WSA error : WSAEHOSTUNREACH"); break; + case WSAENOTEMPTY: qDebug("WSA error : WSAENOTEMPTY"); break; + case WSAEPROCLIM: qDebug("WSA error : WSAEPROCLIM"); break; + case WSAEUSERS: qDebug("WSA error : WSAEUSERS"); break; + case WSAEDQUOT: qDebug("WSA error : WSAEDQUOT"); break; + case WSAESTALE: qDebug("WSA error : WSAESTALE"); break; + case WSAEREMOTE: qDebug("WSA error : WSAEREMOTE"); break; + case WSAEDISCON: qDebug("WSA error : WSAEDISCON"); break; + default: qDebug("WSA error : Unknown"); break; + } + qErrnoWarning(r, "more details"); +} + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxLength) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(int(uchar(c)))) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1().constData(); + } + } + + if (len < maxLength) + out += "..."; + + return out; +} + + +#define WS_ERROR_DEBUG(x) verboseWSErrorDebug(x); + +#else + +#define WS_ERROR_DEBUG(x) Q_UNUSED(x) + +#endif + +#ifndef AF_INET6 +#define AF_INET6 23 /* Internetwork Version 6 */ +#endif + +#ifndef SO_EXCLUSIVEADDRUSE +#define SO_EXCLUSIVEADDRUSE ((int)(~SO_REUSEADDR)) /* disallow local address reuse */ +#endif + +//### +#define QT_SOCKLEN_T int +#define QT_SOCKOPTLEN_T int + + +/* + 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(SOCKET socketDescriptor, const qt_sockaddr *sa, quint16 *port, QHostAddress *address) +{ +#if !defined (QT_NO_IPV6) + if (sa->a.sa_family == AF_INET6) { + const qt_sockaddr_in6 *sa6 = &sa->a6; + Q_IPV6ADDR tmp; + for (int i = 0; i < 16; ++i) + tmp.c[i] = sa6->sin6_addr.qt_s6_addr[i]; + QHostAddress a; + a.setAddress(tmp); + if (address) + *address = a; + if (port) + WSANtohs(socketDescriptor, sa6->sin6_port, port); + } else +#endif + if (sa->a.sa_family == AF_INET) { + const sockaddr_in *sa4 = &sa->a4; + unsigned long addr; + WSANtohl(socketDescriptor, sa4->sin_addr.s_addr, &addr); + QHostAddress a; + a.setAddress(addr); + if (address) + *address = a; + if (port) + WSANtohs(socketDescriptor, sa4->sin_port, port); + } +} + + +/*! \internal + + Sets the port and address to a sockaddr. Requires that sa point to the IPv6 struct if the address is IPv6. +*/ +static inline void qt_socket_setPortAndAddress(SOCKET socketDescriptor, sockaddr_in * sockAddrIPv4, qt_sockaddr_in6 * sockAddrIPv6, + quint16 port, const QHostAddress & address, sockaddr ** sockAddrPtr, QT_SOCKLEN_T *sockAddrSize) +{ +#if !defined(QT_NO_IPV6) + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + memset(sockAddrIPv6, 0, sizeof(qt_sockaddr_in6)); + sockAddrIPv6->sin6_family = AF_INET6; + sockAddrIPv6->sin6_scope_id = address.scopeId().toInt(); + WSAHtons(socketDescriptor, port, &(sockAddrIPv6->sin6_port)); + Q_IPV6ADDR tmp = address.toIPv6Address(); + memcpy(&(sockAddrIPv6->sin6_addr.qt_s6_addr), &tmp, sizeof(tmp)); + *sockAddrSize = sizeof(qt_sockaddr_in6); + *sockAddrPtr = (struct sockaddr *) sockAddrIPv6; + } else +#endif + if (address.protocol() == QAbstractSocket::IPv4Protocol + || address.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol) { + memset(sockAddrIPv4, 0, sizeof(sockaddr_in)); + sockAddrIPv4->sin_family = AF_INET; + WSAHtons(socketDescriptor, port, &(sockAddrIPv4->sin_port)); + WSAHtonl(socketDescriptor, address.toIPv4Address(), &(sockAddrIPv4->sin_addr.s_addr)); + *sockAddrSize = sizeof(sockaddr_in); + *sockAddrPtr = (struct sockaddr *) sockAddrIPv4; + } else { + // unreachable + } +} + +/*! \internal + +*/ +static inline QAbstractSocket::SocketType qt_socket_getType(int socketDescriptor) +{ + int value = 0; + QT_SOCKLEN_T valueSize = sizeof(value); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_TYPE, (char *) &value, &valueSize) != 0) { + WS_ERROR_DEBUG(WSAGetLastError()); + } else { + if (value == SOCK_STREAM) + return QAbstractSocket::TcpSocket; + else if (value == SOCK_DGRAM) + return QAbstractSocket::UdpSocket; + } + return QAbstractSocket::UnknownSocketType; +} + +/*! \internal + +*/ +static inline int qt_socket_getMaxMsgSize(int socketDescriptor) +{ + int value = 0; + QT_SOCKLEN_T valueSize = sizeof(value); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_MAX_MSG_SIZE, (char *) &value, &valueSize) != 0) { + WS_ERROR_DEBUG(WSAGetLastError()); + } + return value; +} + +QWindowsSockInit::QWindowsSockInit() +: version(0) +{ + //### should we try for 2.2 on all platforms ?? + WSAData wsadata; + + // IPv6 requires Winsock v2.0 or better. + if (WSAStartup(MAKEWORD(2,0), &wsadata) != 0) { + qWarning("QTcpSocketAPI: WinSock v2.0 initialization failed."); + } else { + version = 0x20; + } +} + +QWindowsSockInit::~QWindowsSockInit() +{ + WSACleanup(); +} + +// MS Transport Provider IOCTL to control +// reporting PORT_UNREACHABLE messages +// on UDP sockets via recv/WSARecv/etc. +// Path TRUE in input buffer to enable (default if supported), +// FALSE to disable. +#ifndef SIO_UDP_CONNRESET +# ifndef IOC_VENDOR +# define IOC_VENDOR 0x18000000 +# endif +# ifndef _WSAIOW +# define _WSAIOW(x,y) (IOC_IN|(x)|(y)) +# endif +# define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) +#endif + +bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol socketProtocol) +{ + + //### no ip6 support on winsocket 1.1 but we will try not to use this !!!!!!!!!!!!1 + /* + if (winsockVersion < 0x20 && socketProtocol == QAbstractSocket::IPv6Protocol) { + //### no ip6 support + return -1; + } + */ + + int protocol = (socketProtocol == QAbstractSocket::IPv6Protocol) ? AF_INET6 : AF_INET; + int type = (socketType == QAbstractSocket::UdpSocket) ? SOCK_DGRAM : SOCK_STREAM; + // MSDN KB179942 states that on winnt 4 WSA_FLAG_OVERLAPPED is needed if socket is to be non blocking + // and recomends alwasy doing it for cross windows version comapablity. + SOCKET socket = ::WSASocket(protocol, type, 0, NULL, 0, WSA_FLAG_OVERLAPPED); + + if (socket == INVALID_SOCKET) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEAFNOSUPPORT: + case WSAESOCKTNOSUPPORT: + case WSAEPROTOTYPE: + case WSAEINVAL: + setError(QAbstractSocket::UnsupportedSocketOperationError, ProtocolUnsupportedErrorString); + break; + case WSAEMFILE: + case WSAENOBUFS: + setError(QAbstractSocket::SocketResourceError, ResourceErrorString); + break; + default: + break; + } + + return false; + } + +#if !defined(Q_OS_WINCE) + if (socketType == QAbstractSocket::UdpSocket) { + // enable new behavior using + // SIO_UDP_CONNRESET + DWORD dwBytesReturned = 0; + int bNewBehavior = 1; + if (::WSAIoctl(socket, SIO_UDP_CONNRESET, &bNewBehavior, sizeof(bNewBehavior), + NULL, 0, &dwBytesReturned, NULL, NULL) == SOCKET_ERROR) { + // not to worry isBogusUdpReadNotification() should handle this otherwise + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + } + } +#endif + + socketDescriptor = socket; + return true; + +} + +/*! \internal + + 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; + + int n = -1; + int level = SOL_SOCKET; // default + + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::BroadcastSocketOption: + n = SO_BROADCAST; + break; + case QNativeSocketEngine::NonBlockingSocketOption: { + unsigned long buf = 0; + if (WSAIoctl(socketDescriptor, FIONBIO, 0,0, &buf, sizeof(buf), 0,0,0) == 0) + return buf; + else + return -1; + break; + } + case QNativeSocketEngine::AddressReusable: + n = SO_REUSEADDR; + break; + case QNativeSocketEngine::BindExclusively: + n = SO_EXCLUSIVEADDRUSE; + 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: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_HOPS; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_TTL; + } + break; + case QNativeSocketEngine::MulticastLoopbackOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_LOOP; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_LOOP; + } + break; + } + + int v = -1; + QT_SOCKOPTLEN_T len = sizeof(v); + if (getsockopt(socketDescriptor, level, n, (char *) &v, &len) != -1) + return v; + return -1; +} + + +/*! \internal + Sets the socket option \a opt to \a v. +*/ +bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt, int v) +{ + Q_Q(const QNativeSocketEngine); + if (!q->isValid()) + return false; + + int n = 0; + int level = SOL_SOCKET; // default + + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::BroadcastSocketOption: + n = SO_BROADCAST; + break; + case QNativeSocketEngine::NonBlockingSocketOption: + { + unsigned long buf = v; + unsigned long outBuf; + DWORD sizeWritten = 0; + if (::WSAIoctl(socketDescriptor, FIONBIO, &buf, sizeof(unsigned long), &outBuf, sizeof(unsigned long), &sizeWritten, 0,0) == SOCKET_ERROR) { + WS_ERROR_DEBUG(WSAGetLastError()); + return false; + } + return true; + break; + } + case QNativeSocketEngine::AddressReusable: + n = SO_REUSEADDR; + break; + case QNativeSocketEngine::BindExclusively: + n = SO_EXCLUSIVEADDRUSE; + 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: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_HOPS; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_TTL; + } + break; + case QNativeSocketEngine::MulticastLoopbackOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_LOOP; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_LOOP; + } + break; + } + + if (::setsockopt(socketDescriptor, level, n, (char*)&v, sizeof(v)) != 0) { + WS_ERROR_DEBUG(WSAGetLastError()); + return false; + } + return true; +} + +/*! + Fetches information about both ends of the connection: whatever is + available. +*/ +bool QNativeSocketEnginePrivate::fetchConnectionParameters() +{ + localPort = 0; + localAddress.clear(); + peerPort = 0; + peerAddress.clear(); + + 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(socketDescriptor, &sa, &localPort, &localAddress); + // Determine protocol family + switch (sa.a.sa_family) { + case AF_INET: + socketProtocol = QAbstractSocket::IPv4Protocol; + break; +#if !defined (QT_NO_IPV6) + case AF_INET6: + socketProtocol = QAbstractSocket::IPv6Protocol; + break; +#endif + default: + socketProtocol = QAbstractSocket::UnknownNetworkLayerProtocol; + break; + } + } else { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + if (err == WSAENOTSOCK) { + setError(QAbstractSocket::UnsupportedSocketOperationError, + InvalidSocketErrorString); + return false; + } + } + + memset(&sa, 0, sizeof(sa)); + if (::getpeername(socketDescriptor, &sa.a, &sockAddrSize) == 0) { + qt_socket_getPortAndAddress(socketDescriptor, &sa, &peerPort, &peerAddress); + } else { + WS_ERROR_DEBUG(WSAGetLastError()); + } + + socketType = qt_socket_getType(socketDescriptor); + +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString socketProtocolStr = "UnknownProtocol"; + if (socketProtocol == QAbstractSocket::IPv4Protocol) socketProtocolStr = "IPv4Protocol"; + else if (socketProtocol == QAbstractSocket::IPv6Protocol) socketProtocolStr = "IPv6Protocol"; + + QString socketTypeStr = "UnknownSocketType"; + if (socketType == QAbstractSocket::TcpSocket) socketTypeStr = "TcpSocket"; + else if (socketType == QAbstractSocket::UdpSocket) socketTypeStr = "UdpSocket"; + + qDebug("QNativeSocketEnginePrivate::fetchConnectionParameters() localAddress == %s, localPort = %i, peerAddress == %s, peerPort = %i, socketProtocol == %s, socketType == %s", localAddress.toString().toLatin1().constData(), localPort, peerAddress.toString().toLatin1().constData(), peerPort, socketProtocolStr.toLatin1().constData(), socketTypeStr.toLatin1().constData()); +#endif + + return true; +} + + +bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &address, quint16 port) +{ + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect() to %s :: %i", address.toString().toLatin1().constData(), port); +#endif + + struct sockaddr_in sockAddrIPv4; + qt_sockaddr_in6 sockAddrIPv6; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + + qt_socket_setPortAndAddress(socketDescriptor, &sockAddrIPv4, &sockAddrIPv6, port, address, &sockAddrPtr, &sockAddrSize); + + forever { + int connectResult = ::WSAConnect(socketDescriptor, sockAddrPtr, sockAddrSize, 0,0,0,0); + if (connectResult == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEISCONN: + socketState = QAbstractSocket::ConnectedState; + break; + case WSAEWOULDBLOCK: { + // If WSAConnect returns WSAEWOULDBLOCK on the second + // connection attempt, we have to check SO_ERROR's + // value to detect ECONNREFUSED. If we don't get + // ECONNREFUSED, we'll have to treat it as an + // unfinished operation. + int value = 0; + QT_SOCKLEN_T valueSize = sizeof(value); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_ERROR, (char *) &value, &valueSize) == 0) { + if (value == WSAECONNREFUSED) { + setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + } + if (value == WSAETIMEDOUT) { + setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + } + if (value == WSAEHOSTUNREACH) { + setError(QAbstractSocket::NetworkError, HostUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + } + if (value == WSAEADDRNOTAVAIL) { + setError(QAbstractSocket::NetworkError, AddressNotAvailableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + } + } + // fall through + } + case WSAEINPROGRESS: + setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); + socketState = QAbstractSocket::ConnectingState; + break; + case WSAEADDRINUSE: + setError(QAbstractSocket::NetworkError, AddressInuseErrorString); + break; + case WSAECONNREFUSED: + setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAETIMEDOUT: + setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); + break; + case WSAEACCES: + setError(QAbstractSocket::SocketAccessError, AccessErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAEHOSTUNREACH: + setError(QAbstractSocket::NetworkError, HostUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAENETUNREACH: + setError(QAbstractSocket::NetworkError, NetworkUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAEINVAL: + case WSAEALREADY: + setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); + break; + default: + break; + } + if (socketState != QAbstractSocket::ConnectedState) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == false (%s)", + address.toString().toLatin1().constData(), port, + socketState == QAbstractSocket::ConnectingState + ? "Connection in progress" : socketErrorString.toLatin1().constData()); +#endif + return false; + } + } + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == true", + address.toString().toLatin1().constData(), port); +#endif + + socketState = QAbstractSocket::ConnectedState; + return true; +} + + +bool QNativeSocketEnginePrivate::nativeBind(const QHostAddress &a, quint16 port) +{ + QHostAddress address = a; + switch (address.protocol()) { + case QAbstractSocket::IPv6Protocol: + if (address.toIPv6Address()[0] == 0xff) { + // binding to a multicast address + address = QHostAddress(QHostAddress::AnyIPv6); + } + break; + case QAbstractSocket::IPv4Protocol: + if ((address.toIPv4Address() & 0xffff0000) == 0xefff0000) { + // binding to a multicast address + address = QHostAddress(QHostAddress::Any); + } + break; + default: + break; + } + + struct sockaddr_in sockAddrIPv4; + qt_sockaddr_in6 sockAddrIPv6; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + + qt_socket_setPortAndAddress(socketDescriptor, &sockAddrIPv4, &sockAddrIPv6, port, address, &sockAddrPtr, &sockAddrSize); + + + int bindResult = ::bind(socketDescriptor, sockAddrPtr, sockAddrSize); + if (bindResult == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEADDRINUSE: + case WSAEINVAL: + setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString); + break; + case WSAEACCES: + setError(QAbstractSocket::SocketAccessError, AddressProtectedErrorString); + break; + case WSAEADDRNOTAVAIL: + setError(QAbstractSocket::SocketAddressNotAvailableError, AddressNotAvailableErrorString); + break; + default: + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBind(%s, %i) == false (%s)", + address.toString().toLatin1().constData(), port, socketErrorString.toLatin1().constData()); +#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 (::listen(socketDescriptor, backlog) == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEADDRINUSE: + setError(QAbstractSocket::AddressInUseError, + PortInuseErrorString); + break; + default: + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == false (%s)", + backlog, socketErrorString.toLatin1().constData()); +#endif + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == true", backlog); +#endif + + socketState = QAbstractSocket::ListeningState; + return true; +} + +int QNativeSocketEnginePrivate::nativeAccept() +{ + int acceptedDescriptor = WSAAccept(socketDescriptor, 0,0,0,0); + if (acceptedDescriptor != -1 && QAbstractEventDispatcher::instance()) { + // Because of WSAAsyncSelect() WSAAccept returns a non blocking socket + // with the same attributes as the listening socket including the current + // WSAAsyncSelect(). To be able to change the socket to blocking mode the + // WSAAsyncSelect() call must be cancled. + QSocketNotifier n(acceptedDescriptor, QSocketNotifier::Read); + n.setEnabled(true); + n.setEnabled(false); + } +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeAccept() == %i", acceptedDescriptor); +#endif + return acceptedDescriptor; +} + +static bool multicastMembershipHelper(QNativeSocketEnginePrivate *d, + int how6, + int how4, + const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + int level = 0; + int sockOpt = 0; + char *sockArg; + int sockArgSize; + + struct ip_mreq mreq4; +#ifndef QT_NO_IPV6 + struct ipv6_mreq mreq6; + + if (groupAddress.protocol() == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + sockOpt = how6; + sockArg = reinterpret_cast<char *>(&mreq6); + sockArgSize = sizeof(mreq6); + memset(&mreq6, 0, sizeof(mreq6)); + Q_IPV6ADDR ip6 = groupAddress.toIPv6Address(); + memcpy(&mreq6.ipv6mr_multiaddr, &ip6, sizeof(ip6)); + mreq6.ipv6mr_interface = iface.index(); + } else +#endif + if (groupAddress.protocol() == QAbstractSocket::IPv4Protocol) { + level = IPPROTO_IP; + sockOpt = how4; + sockArg = reinterpret_cast<char *>(&mreq4); + sockArgSize = sizeof(mreq4); + memset(&mreq4, 0, sizeof(mreq4)); + mreq4.imr_multiaddr.s_addr = htonl(groupAddress.toIPv4Address()); + + if (iface.isValid()) { + QList<QNetworkAddressEntry> addressEntries = iface.addressEntries(); + if (!addressEntries.isEmpty()) { + QHostAddress firstIP = addressEntries.first().ip(); + mreq4.imr_interface.s_addr = htonl(firstIP.toIPv4Address()); + } else { + 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) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::OperationUnsupportedErrorString); + return false; + } + return true; +} + +bool QNativeSocketEnginePrivate::nativeJoinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + return multicastMembershipHelper(this, +#ifndef QT_NO_IPV6 + IPV6_JOIN_GROUP, +#else + 0, +#endif + IP_ADD_MEMBERSHIP, + groupAddress, + iface); +} + +bool QNativeSocketEnginePrivate::nativeLeaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + return multicastMembershipHelper(this, +#ifndef QT_NO_IPV6 + IPV6_LEAVE_GROUP, +#else + 0, +#endif + IP_DROP_MEMBERSHIP, + groupAddress, + iface); +} + +QNetworkInterface QNativeSocketEnginePrivate::nativeMulticastInterface() const +{ +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + uint v; + QT_SOCKOPTLEN_T sizeofv = sizeof(v); + if (::getsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *) &v, &sizeofv) == -1) + return QNetworkInterface(); + return QNetworkInterface::interfaceFromIndex(v); + } +#endif + + struct in_addr v; + v.s_addr = 0; + QT_SOCKOPTLEN_T sizeofv = sizeof(v); + if (::getsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, (char *) &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.count(); ++i) { + const QNetworkInterface &iface = ifaces.at(i); + if (!(iface.flags() & QNetworkInterface::CanMulticast)) + continue; + QList<QNetworkAddressEntry> entries = iface.addressEntries(); + for (int j = 0; j < entries.count(); ++j) { + const QNetworkAddressEntry &entry = entries.at(j); + if (entry.ip() == ipv4) + return iface; + } + } + } + return QNetworkInterface(); +} + +bool QNativeSocketEnginePrivate::nativeSetMulticastInterface(const QNetworkInterface &iface) +{ +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + uint v = iface.isValid() ? iface.index() : 0; + return (::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *) &v, sizeof(v)) != -1); + } +#endif + + struct in_addr v; + if (iface.isValid()) { + QList<QNetworkAddressEntry> entries = iface.addressEntries(); + for (int i = 0; i < entries.count(); ++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, (char *) &v, sizeof(v)); + if (r != -1) + return true; + } + } + return false; + } + + v.s_addr = INADDR_ANY; + return (::setsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, (char *) &v, sizeof(v)) != -1); +} + +qint64 QNativeSocketEnginePrivate::nativeBytesAvailable() const +{ + unsigned long nbytes = 0; + unsigned long dummy = 0; + DWORD sizeWritten = 0; + if (::WSAIoctl(socketDescriptor, FIONREAD, &dummy, sizeof(dummy), &nbytes, sizeof(nbytes), &sizeWritten, 0,0) == SOCKET_ERROR) { + WS_ERROR_DEBUG(WSAGetLastError()); + return -1; + } + + // ioctlsocket sometimes reports 1 byte available for datagrams + // while the following recvfrom returns -1 and claims connection + // was reset (udp is connectionless). so we peek one byte to + // catch this case and return 0 bytes available if recvfrom + // fails. + if (nbytes == 1 && socketType == QAbstractSocket::UdpSocket) { + char c; + WSABUF buf; + buf.buf = &c; + buf.len = sizeof(c); + DWORD flags = MSG_PEEK; + if (::WSARecvFrom(socketDescriptor, &buf, 1, 0, &flags, 0,0,0,0) == SOCKET_ERROR) + return 0; + } + return nbytes; +} + + +bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const +{ +#if !defined(Q_OS_WINCE) + // Create a sockaddr struct and reset its port number. + qt_sockaddr storage; + QT_SOCKLEN_T storageSize = sizeof(storage); + memset(&storage, 0, storageSize); + + bool result = false; + + // Peek 0 bytes into the next message. The size of the message may + // well be 0, so we check if there was a sender. + char c; + WSABUF buf; + buf.buf = &c; + buf.len = sizeof(c); + DWORD available = 0; + DWORD flags = MSG_PEEK; + int ret = ::WSARecvFrom(socketDescriptor, &buf, 1, &available, &flags, &storage.a, &storageSize,0,0); + int err = WSAGetLastError(); + if (ret == SOCKET_ERROR && err != WSAEMSGSIZE) { + WS_ERROR_DEBUG(err); + if (err == WSAECONNRESET) { + // Discard error message to prevent QAbstractSocket from + // getting this message repeatedly after reenabling the + // notifiers. + flags = 0; + ::WSARecvFrom(socketDescriptor, &buf, 1, &available, &flags, + &storage.a, &storageSize, 0, 0); + } + } else { + // If there's no error, or if our buffer was too small, there must be + // a pending datagram. + result = true; + } + +#else // Q_OS_WINCE + bool result = false; + fd_set readS; + FD_ZERO(&readS); + FD_SET((SOCKET)socketDescriptor, &readS); + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 5000; + int available = ::select(1, &readS, 0, 0, &timeout); + result = available > 0 ? true : false; +#endif + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeHasPendingDatagrams() == %s", + result ? "true" : "false"); +#endif + return result; +} + + +qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const +{ + qint64 ret = -1; +#if !defined(Q_OS_WINCE) + int recvResult = 0; + DWORD flags; + DWORD bufferCount = 5; + WSABUF * buf = 0; + for (;;) { + // the data written to udpMessagePeekBuffer is discarded, so + // this function is still reentrant although it might not look + // so. + static char udpMessagePeekBuffer[8192]; + + buf = new WSABUF[bufferCount]; + for (DWORD i=0; i<bufferCount; i++) { + buf[i].buf = udpMessagePeekBuffer; + buf[i].len = sizeof(udpMessagePeekBuffer); + } + flags = MSG_PEEK; + DWORD bytesRead = 0; + recvResult = ::WSARecv(socketDescriptor, buf, bufferCount, &bytesRead, &flags, 0,0); + int err = WSAGetLastError(); + if (recvResult != SOCKET_ERROR) { + ret = qint64(bytesRead); + break; + } else if (recvResult == SOCKET_ERROR && err == WSAEMSGSIZE) { + bufferCount += 5; + delete[] buf; + } else if (recvResult == SOCKET_ERROR) { + WS_ERROR_DEBUG(err); + ret = -1; + break; + } + } + + if (buf) + delete[] buf; + +#else // Q_OS_WINCE + DWORD size = -1; + DWORD bytesReturned; + int ioResult = WSAIoctl(socketDescriptor, FIONREAD, 0,0, &size, sizeof(size), &bytesReturned, 0, 0); + if (ioResult == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + } else { + ret = qint64(size); + } +#endif + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativePendingDatagramSize() == %li", ret); +#endif + + return ret; +} + + +qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxLength, + QHostAddress *address, quint16 *port) +{ + qint64 ret = 0; + + qt_sockaddr aa; + memset(&aa, 0, sizeof(aa)); + QT_SOCKLEN_T sz; + sz = sizeof(aa); + + WSABUF buf; + buf.buf = data; + buf.len = maxLength; +#if !defined(Q_OS_WINCE) + buf.buf = data; + buf.len = maxLength; +#else + char tmpChar; + buf.buf = data ? data : &tmpChar; + buf.len = maxLength; +#endif + + DWORD flags = 0; + DWORD bytesRead = 0; + int wsaRet = ::WSARecvFrom(socketDescriptor, &buf, 1, &bytesRead, &flags, &aa.a, &sz,0,0); + if (wsaRet == SOCKET_ERROR) { + int err = WSAGetLastError(); + if (err == WSAEMSGSIZE) { + // it is ok the buffer was to small if bytesRead is larger than + // maxLength then assume bytes read is really maxLenth + ret = qint64(bytesRead) > maxLength ? maxLength : qint64(bytesRead); + } else { + WS_ERROR_DEBUG(err); + setError(QAbstractSocket::NetworkError, ReceiveDatagramErrorString); + ret = -1; + } + } else { + ret = qint64(bytesRead); + } + + qt_socket_getPortAndAddress(socketDescriptor, &aa, port, address); + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeReceiveDatagram(%p \"%s\", %li, %s, %i) == %li", + data, qt_prettyDebug(data, qMin<qint64>(ret, 16), ret).data(), maxLength, + address ? address->toString().toLatin1().constData() : "(nil)", + port ? *port : 0, ret); +#endif + + return ret; +} + + +qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 len, + const QHostAddress &address, quint16 port) +{ + qint64 ret = -1; + struct sockaddr_in sockAddrIPv4; + qt_sockaddr_in6 sockAddrIPv6; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + + qt_socket_setPortAndAddress(socketDescriptor, &sockAddrIPv4, &sockAddrIPv6, port, address, &sockAddrPtr, &sockAddrSize); + + WSABUF buf; +#if !defined(Q_OS_WINCE) + buf.buf = len ? (char*)data : 0; +#else + char tmp; + buf.buf = len ? (char*)data : &tmp; +#endif + buf.len = len; + DWORD flags = 0; + DWORD bytesSent = 0; + if (::WSASendTo(socketDescriptor, &buf, 1, &bytesSent, flags, sockAddrPtr, sockAddrSize, 0,0) == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSAEMSGSIZE: + setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString); + break; + default: + setError(QAbstractSocket::NetworkError, SendDatagramErrorString); + break; + } + ret = -1; + } else { + ret = qint64(bytesSent); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeSendDatagram(%p \"%s\", %li, \"%s\", %i) == %li", data, + qt_prettyDebug(data, qMin<qint64>(len, 16), len).data(), 0, address.toString().toLatin1().constData(), + port, ret); +#endif + + return ret; +} + + +qint64 QNativeSocketEnginePrivate::nativeWrite(const char *data, qint64 len) +{ + Q_Q(QNativeSocketEngine); + qint64 ret = 0; + qint64 bytesToSend = len; + + for (;;) { + WSABUF buf; + buf.buf = (char*)data + ret; + buf.len = bytesToSend; + DWORD flags = 0; + DWORD bytesWritten = 0; + + int socketRet = ::WSASend(socketDescriptor, &buf, 1, &bytesWritten, flags, 0,0); + + ret += qint64(bytesWritten); + + int err; + if (socketRet != SOCKET_ERROR) { + if (ret == len) + break; + else + continue; + } else if ((err = WSAGetLastError()) == WSAEWOULDBLOCK) { + break; + } else if (err == WSAENOBUFS) { + // this function used to not send more than 49152 per call to WSASendTo + // to avoid getting a WSAENOBUFS. However this is a performance regression + // and we think it only appears with old windows versions. We now handle the + // WSAENOBUFS and hope it never appears anyway. + // just go on, the next loop run we will try a smaller number + } else { + WS_ERROR_DEBUG(err); + switch (err) { + case WSAECONNRESET: + case WSAECONNABORTED: + ret = -1; + setError(QAbstractSocket::NetworkError, WriteErrorString); + q->close(); + break; + default: + break; + } + break; + } + + // for next send: + bytesToSend = qMin<qint64>(49152, len - ret); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeWrite(%p \"%s\", %li) == %li", + data, qt_prettyDebug(data, qMin((int)ret, 16), (int)ret).data(), (int)len, (int)ret); +#endif + + return ret; +} + +qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxLength) +{ + qint64 ret = -1; + WSABUF buf; + buf.buf = data; + buf.len = maxLength; + DWORD flags = 0; + DWORD bytesRead = 0; +#if defined(Q_OS_WINCE) + WSASetLastError(0); +#endif + if (::WSARecv(socketDescriptor, &buf, 1, &bytesRead, &flags, 0,0) == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSAEWOULDBLOCK: + ret = -2; + break; + case WSAEBADF: + case WSAEINVAL: + //error string is now set in read(), not here in nativeRead() + break; + case WSAECONNRESET: + case WSAECONNABORTED: + // for tcp sockets this will be handled in QNativeSocketEngine::read + ret = 0; + break; + default: + break; + } + } else { + if (WSAGetLastError() == WSAEWOULDBLOCK) + ret = -2; + else + ret = qint64(bytesRead); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + if (ret != -2) { + qDebug("QNativeSocketEnginePrivate::nativeRead(%p \"%s\", %l) == %li", + data, qt_prettyDebug(data, qMin((int)bytesRead, 16), (int)bytesRead).data(), (int)maxLength, (int)ret); + } else { + qDebug("QNativeSocketEnginePrivate::nativeRead(%p, %l) == -2 (WOULD BLOCK)", + data, int(maxLength)); + } +#endif + + return ret; +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) const +{ + bool readEnabled = selectForRead && readNotifier && readNotifier->isEnabled(); + if (readEnabled) + readNotifier->setEnabled(false); + + fd_set fds; + + int ret = 0; + + memset(&fds, 0, sizeof(fd_set)); + fds.fd_count = 1; + fds.fd_array[0] = (SOCKET)socketDescriptor; + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + if (selectForRead) { + ret = select(0, &fds, 0, 0, timeout < 0 ? 0 : &tv); + } else { + // select for write + + // Windows needs this to report errors when connecting a socket ... + fd_set fdexception; + FD_ZERO(&fdexception); + FD_SET((SOCKET)socketDescriptor, &fdexception); + + ret = select(0, 0, &fds, &fdexception, timeout < 0 ? 0 : &tv); + + // ... but if it is actually set, pretend it did not happen + if (ret > 0 && FD_ISSET((SOCKET)socketDescriptor, &fdexception)) + ret--; + } + + if (readEnabled) + readNotifier->setEnabled(true); + + return ret; +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, + bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const +{ + bool readEnabled = checkRead && readNotifier && readNotifier->isEnabled(); + if (readEnabled) + readNotifier->setEnabled(false); + + fd_set fdread; + fd_set fdwrite; + fd_set fdexception; + + int ret = 0; + + memset(&fdread, 0, sizeof(fd_set)); + if (checkRead) { + fdread.fd_count = 1; + fdread.fd_array[0] = (SOCKET)socketDescriptor; + } + memset(&fdwrite, 0, sizeof(fd_set)); + FD_ZERO(&fdexception); + if (checkWrite) { + fdwrite.fd_count = 1; + fdwrite.fd_array[0] = (SOCKET)socketDescriptor; + + // Windows needs this to report errors when connecting a socket + FD_SET((SOCKET)socketDescriptor, &fdexception); + } + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + +#if !defined(Q_OS_WINCE) + ret = select(socketDescriptor + 1, &fdread, &fdwrite, &fdexception, timeout < 0 ? 0 : &tv); +#else + ret = select(1, &fdread, &fdwrite, &fdexception, timeout < 0 ? 0 : &tv); +#endif + + //... but if it is actually set, pretend it did not happen + if (ret > 0 && FD_ISSET((SOCKET)socketDescriptor, &fdexception)) + ret--; + + if (readEnabled) + readNotifier->setEnabled(true); + + if (ret <= 0) + return ret; + + *selectForRead = FD_ISSET((SOCKET)socketDescriptor, &fdread); + *selectForWrite = FD_ISSET((SOCKET)socketDescriptor, &fdwrite); + + return ret; +} + +void QNativeSocketEnginePrivate::nativeClose() +{ +#if defined (QTCPSOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeClose()"); +#endif + // We were doing a setsockopt here before with SO_DONTLINGER. (However with kind of wrong + // usage of parameters, it wants a BOOL but we used a struct and pretended it to be bool). + // We don't think setting this option should be done here, if a user wants it she/he can + // do it manually with socketDescriptor()/setSocketDescriptor(); + ::closesocket(socketDescriptor); +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnet_unix_p.h b/src/network/socket/qnet_unix_p.h new file mode 100644 index 0000000000..c406ed948c --- /dev/null +++ b/src/network/socket/qnet_unix_p.h @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNET_UNIX_P_H +#define QNET_UNIX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt code on Unix. This header file may change from version to +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qcore_unix_p.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#if defined(Q_OS_VXWORKS) +# include <sockLib.h> +#endif + +// for inet_addr +#include <netdb.h> +#include <arpa/inet.h> +#if defined(Q_OS_VXWORKS) +# include <hostLib.h> +#else +# include <resolv.h> +#endif + +QT_BEGIN_NAMESPACE + +// Almost always the same. If not, specify in qplatformdefs.h. +#if !defined(QT_SOCKOPTLEN_T) +# define QT_SOCKOPTLEN_T QT_SOCKLEN_T +#endif + +// UnixWare 7 redefines socket -> _socket +static inline int qt_safe_socket(int domain, int type, int protocol, int flags = 0) +{ + Q_ASSERT((flags & ~O_NONBLOCK) == 0); + + register int fd; +#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + int newtype = type | SOCK_CLOEXEC; + if (flags & O_NONBLOCK) + newtype |= SOCK_NONBLOCK; + fd = ::socket(domain, newtype, protocol); + if (fd != -1 || errno != EINVAL) + return fd; +#endif + + fd = ::socket(domain, type, protocol); + if (fd == -1) + return -1; + + ::fcntl(fd, F_SETFD, FD_CLOEXEC); + + // set non-block too? + if (flags & O_NONBLOCK) + ::fcntl(fd, F_SETFL, ::fcntl(fd, F_GETFL) | O_NONBLOCK); + + return fd; +} + +// Tru64 redefines accept -> _accept with _XOPEN_SOURCE_EXTENDED +static inline int qt_safe_accept(int s, struct sockaddr *addr, QT_SOCKLEN_T *addrlen, int flags = 0) +{ + Q_ASSERT((flags & ~O_NONBLOCK) == 0); + + register int fd; +#if QT_UNIX_SUPPORTS_THREADSAFE_CLOEXEC && defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + // use accept4 + int sockflags = SOCK_CLOEXEC; + if (flags & O_NONBLOCK) + sockflags |= SOCK_NONBLOCK; + fd = ::accept4(s, addr, static_cast<QT_SOCKLEN_T *>(addrlen), sockflags); + if (fd != -1 || !(errno == ENOSYS || errno == EINVAL)) + return fd; +#endif + + fd = ::accept(s, addr, static_cast<QT_SOCKLEN_T *>(addrlen)); + if (fd == -1) + return -1; + + ::fcntl(fd, F_SETFD, FD_CLOEXEC); + + // set non-block too? + if (flags & O_NONBLOCK) + ::fcntl(fd, F_SETFL, ::fcntl(fd, F_GETFL) | O_NONBLOCK); + + return fd; +} + +// UnixWare 7 redefines listen -> _listen +static inline int qt_safe_listen(int s, int backlog) +{ + return ::listen(s, backlog); +} + +static inline int qt_safe_connect(int sockfd, const struct sockaddr *addr, QT_SOCKLEN_T addrlen) +{ + register int ret; + // Solaris e.g. expects a non-const 2nd parameter + EINTR_LOOP(ret, QT_SOCKET_CONNECT(sockfd, const_cast<struct sockaddr *>(addr), addrlen)); + return ret; +} +#undef QT_SOCKET_CONNECT +#define QT_SOCKET_CONNECT qt_safe_connect + +#if defined(socket) +# undef socket +#endif +#if defined(accept) +# undef accept +#endif +#if defined(listen) +# undef listen +#endif + +// VxWorks' headers specify 'int' instead of '...' for the 3rd ioctl() parameter. +template <typename T> +static inline int qt_safe_ioctl(int sockfd, int request, T arg) +{ +#ifdef Q_OS_VXWORKS + return ::ioctl(sockfd, request, (int) arg); +#else + return ::ioctl(sockfd, request, arg); +#endif +} + +// VxWorks' headers do not specify any const modifiers +static inline in_addr_t qt_safe_inet_addr(const char *cp) +{ +#ifdef Q_OS_VXWORKS + return ::inet_addr((char *) cp); +#else + return ::inet_addr(cp); +#endif +} + +// VxWorks' headers do not specify any const modifiers +static inline int qt_safe_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *to, QT_SOCKLEN_T tolen) +{ +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + register int ret; +#ifdef Q_OS_VXWORKS + EINTR_LOOP(ret, ::sendto(sockfd, (char *) buf, len, flags, (struct sockaddr *) to, tolen)); +#else + EINTR_LOOP(ret, ::sendto(sockfd, buf, len, flags, to, tolen)); +#endif + return ret; +} + +QT_END_NAMESPACE + +#endif // QNET_UNIX_P_H diff --git a/src/network/socket/qsocks5socketengine.cpp b/src/network/socket/qsocks5socketengine.cpp new file mode 100644 index 0000000000..c365635990 --- /dev/null +++ b/src/network/socket/qsocks5socketengine.cpp @@ -0,0 +1,1923 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsocks5socketengine_p.h" + +#ifndef QT_NO_SOCKS5 + +#include "qtcpsocket.h" +#include "qudpsocket.h" +#include "qtcpserver.h" +#include "qdebug.h" +#include "qhash.h" +#include "qqueue.h" +#include "qelapsedtimer.h" +#include "qmutex.h" +#include "qthread.h" +#include "qcoreapplication.h" +#include "qurl.h" +#include "qauthenticator.h" +#include <qendian.h> +#include <qnetworkinterface.h> + +QT_BEGIN_NAMESPACE + +#ifdef Q_OS_SYMBIAN +static const int MaxWriteBufferSize = 4*1024; +#else +static const int MaxWriteBufferSize = 128*1024; +#endif + +//#define QSOCKS5SOCKETLAYER_DEBUG + +#define MAX_DATA_DUMP 256 +#if !defined(Q_OS_WINCE) +#define SOCKS5_BLOCKING_BIND_TIMEOUT 5000 +#else +#define SOCKS5_BLOCKING_BIND_TIMEOUT 10000 +#endif + +#define Q_INIT_CHECK(returnValue) do { \ + if (!d->data) { \ + return returnValue; \ + } } while (0) + +#define S5_VERSION_5 0x05 +#define S5_CONNECT 0x01 +#define S5_BIND 0x02 +#define S5_UDP_ASSOCIATE 0x03 +#define S5_IP_V4 0x01 +#define S5_DOMAINNAME 0x03 +#define S5_IP_V6 0x04 +#define S5_SUCCESS 0x00 +#define S5_R_ERROR_SOCKS_FAILURE 0x01 +#define S5_R_ERROR_CON_NOT_ALLOWED 0x02 +#define S5_R_ERROR_NET_UNREACH 0x03 +#define S5_R_ERROR_HOST_UNREACH 0x04 +#define S5_R_ERROR_CONN_REFUSED 0x05 +#define S5_R_ERROR_TTL 0x06 +#define S5_R_ERROR_CMD_NOT_SUPPORTED 0x07 +#define S5_R_ERROR_ADD_TYPE_NOT_SUPORTED 0x08 + +#define S5_AUTHMETHOD_NONE 0x00 +#define S5_AUTHMETHOD_PASSWORD 0x02 +#define S5_AUTHMETHOD_NOTACCEPTABLE 0xFF + +#define S5_PASSWORDAUTH_VERSION 0x01 + +#ifdef QSOCKS5SOCKETLAYER_DEBUG +# define QSOCKS5_Q_DEBUG qDebug() << this +# define QSOCKS5_D_DEBUG qDebug() << q_ptr +# define QSOCKS5_DEBUG qDebug() << "[QSocks5]" +static QString s5StateToString(QSocks5SocketEnginePrivate::Socks5State s) +{ + switch (s) { + case QSocks5SocketEnginePrivate::Uninitialized: return QLatin1String("Uninitialized"); + case QSocks5SocketEnginePrivate::ConnectError: return QLatin1String("ConnectError"); + case QSocks5SocketEnginePrivate::AuthenticationMethodsSent: return QLatin1String("AuthenticationMethodsSent"); + case QSocks5SocketEnginePrivate::Authenticating: return QLatin1String("Authenticating"); + case QSocks5SocketEnginePrivate::AuthenticatingError: return QLatin1String("AuthenticatingError"); + case QSocks5SocketEnginePrivate::RequestMethodSent: return QLatin1String("RequestMethodSent"); + case QSocks5SocketEnginePrivate::RequestError: return QLatin1String("RequestError"); + case QSocks5SocketEnginePrivate::Connected: return QLatin1String("Connected"); + case QSocks5SocketEnginePrivate::UdpAssociateSuccess: return QLatin1String("UdpAssociateSuccess"); + case QSocks5SocketEnginePrivate::BindSuccess: return QLatin1String("BindSuccess"); + case QSocks5SocketEnginePrivate::ControlSocketError: return QLatin1String("ControlSocketError"); + case QSocks5SocketEnginePrivate::SocksError: return QLatin1String("SocksError"); + case QSocks5SocketEnginePrivate::HostNameLookupError: return QLatin1String("HostNameLookupError"); + default: break; + } + return QLatin1String("unknown state"); +} + +static QString dump(const QByteArray &buf) +{ + QString data; + for (int i = 0; i < qMin<int>(MAX_DATA_DUMP, buf.size()); ++i) { + if (i) data += QLatin1Char(' '); + uint val = (unsigned char)buf.at(i); + // data += QString("0x%1").arg(val, 3, 16, QLatin1Char('0')); + data += QString::number(val); + } + if (buf.size() > MAX_DATA_DUMP) + data += QLatin1String(" ..."); + + return QString::fromLatin1("size: %1 data: { %2 }").arg(buf.size()).arg(data); +} + +#else +# define QSOCKS5_DEBUG if (0) qDebug() +# define QSOCKS5_Q_DEBUG if (0) qDebug() +# define QSOCKS5_D_DEBUG if (0) qDebug() + +static inline QString s5StateToString(QSocks5SocketEnginePrivate::Socks5State) { return QString(); } +static inline QString dump(const QByteArray &) { return QString(); } +#endif + +/* + inserts the host address in buf at pos and updates pos. + if the func fails the data in buf and the vallue of pos is undefined +*/ +static bool qt_socks5_set_host_address_and_port(const QHostAddress &address, quint16 port, QByteArray *pBuf) +{ + QSOCKS5_DEBUG << "setting [" << address << ':' << port << ']'; + + union { + quint16 port; + quint32 ipv4; + QIPv6Address ipv6; + char ptr; + } data; + + // add address + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + data.ipv4 = qToBigEndian<quint32>(address.toIPv4Address()); + pBuf->append(S5_IP_V4); + pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.ipv4)); + } else if (address.protocol() == QAbstractSocket::IPv6Protocol) { + data.ipv6 = address.toIPv6Address(); + pBuf->append(S5_IP_V6); + pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.ipv6)); + } else { + return false; + } + + // add port + data.port = qToBigEndian<quint16>(port); + pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.port)); + return true; +} + +/* + like above, but for a hostname +*/ +static bool qt_socks5_set_host_name_and_port(const QString &hostname, quint16 port, QByteArray *pBuf) +{ + QSOCKS5_DEBUG << "setting [" << hostname << ':' << port << ']'; + + QByteArray encodedHostName = QUrl::toAce(hostname); + QByteArray &buf = *pBuf; + + if (encodedHostName.length() > 255) + return false; + + buf.append(S5_DOMAINNAME); + buf.append(uchar(encodedHostName.length())); + buf.append(encodedHostName); + + // add port + union { + quint16 port; + char ptr; + } data; + data.port = qToBigEndian<quint16>(port); + buf.append(QByteArray::fromRawData(&data.ptr, sizeof data.port)); + + return true; +} + + +/* + retrives the host address in buf at pos and updates pos. + if the func fails the value of the address and the pos is undefined +*/ +static bool qt_socks5_get_host_address_and_port(const QByteArray &buf, QHostAddress *pAddress, quint16 *pPort, int *pPos) +{ + bool ret = false; + int pos = *pPos; + const unsigned char *pBuf = reinterpret_cast<const unsigned char*>(buf.constData()); + QHostAddress address; + quint16 port = 0; + + if (buf.size() - pos < 1) { + QSOCKS5_DEBUG << "need more data address/port"; + return false; + } + if (pBuf[pos] == S5_IP_V4) { + pos++; + if (buf.size() - pos < 4) { + QSOCKS5_DEBUG << "need more data for ip4 address"; + return false; + } + address.setAddress(qFromBigEndian<quint32>(&pBuf[pos])); + pos += 4; + ret = true; + } else if (pBuf[pos] == S5_IP_V6) { + pos++; + if (buf.size() - pos < 16) { + QSOCKS5_DEBUG << "need more data for ip6 address"; + return false; + } + QIPv6Address add; + for (int i = 0; i < 16; ++i) + add[i] = buf[pos++]; + ret = true; + } else if (pBuf[pos] == S5_DOMAINNAME){ + // just skip it + pos++; + qDebug() << "skipping hostname of len" << uint(pBuf[pos]); + pos += uchar(pBuf[pos]); + } else { + QSOCKS5_DEBUG << "invalid address type" << (int)pBuf[pos]; + ret = false; + } + + if (ret) { + if (buf.size() - pos < 2) { + QSOCKS5_DEBUG << "need more data for port"; + return false; + } + port = qFromBigEndian<quint16>(&pBuf[pos]); + pos += 2; + } + + if (ret) { + QSOCKS5_DEBUG << "got [" << address << ':' << port << ']'; + *pAddress = address; + *pPort = port; + *pPos = pos; + } + + return ret; +} + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +struct QSocks5Data +{ + QTcpSocket *controlSocket; + QSocks5Authenticator *authenticator; +}; + +struct QSocks5ConnectData : public QSocks5Data +{ + QByteArray readBuffer; +}; + +struct QSocks5BindData : public QSocks5Data +{ + QHostAddress localAddress; + quint16 localPort; + QHostAddress peerAddress; + quint16 peerPort; + QElapsedTimer timeStamp; +}; + +struct QSocks5RevivedDatagram +{ + QByteArray data; + QHostAddress address; + quint16 port; +}; + +#ifndef QT_NO_UDPSOCKET +struct QSocks5UdpAssociateData : public QSocks5Data +{ + QUdpSocket *udpSocket; + QHostAddress associateAddress; + quint16 associatePort; + QQueue<QSocks5RevivedDatagram> pendingDatagrams; +}; +#endif + +// needs to be thread safe +class QSocks5BindStore : public QObject +{ +public: + QSocks5BindStore(); + ~QSocks5BindStore(); + + void add(int socketDescriptor, QSocks5BindData *bindData); + bool contains(int socketDescriptor); + QSocks5BindData *retrieve(int socketDescriptor); + +protected: + void timerEvent(QTimerEvent * event); + + QMutex mutex; + int sweepTimerId; + //socket descriptor, data, timestamp + QHash<int, QSocks5BindData *> store; +}; + +Q_GLOBAL_STATIC(QSocks5BindStore, socks5BindStore) + +QSocks5BindStore::QSocks5BindStore() + : mutex(QMutex::Recursive) + , sweepTimerId(-1) +{ + QCoreApplication *app = QCoreApplication::instance(); + if (app && app->thread() != thread()) + moveToThread(app->thread()); +} + +QSocks5BindStore::~QSocks5BindStore() +{ +} + +void QSocks5BindStore::add(int socketDescriptor, QSocks5BindData *bindData) +{ + QMutexLocker lock(&mutex); + if (store.contains(socketDescriptor)) { + // qDebug() << "delete it"; + } + bindData->timeStamp.start(); + store.insert(socketDescriptor, bindData); + // start sweep timer if not started + if (sweepTimerId == -1) + sweepTimerId = startTimer(60000); +} + +bool QSocks5BindStore::contains(int socketDescriptor) +{ + QMutexLocker lock(&mutex); + return store.contains(socketDescriptor); +} + +QSocks5BindData *QSocks5BindStore::retrieve(int socketDescriptor) +{ + QMutexLocker lock(&mutex); + if (!store.contains(socketDescriptor)) + return 0; + QSocks5BindData *bindData = store.take(socketDescriptor); + if (bindData) { + if (bindData->controlSocket->thread() != QThread::currentThread()) { + qWarning("Can not access socks5 bind data from different thread"); + return 0; + } + } else { + QSOCKS5_DEBUG << "__ERROR__ binddata == 0"; + } + // stop the sweep timer if not needed + if (store.isEmpty()) { + killTimer(sweepTimerId); + sweepTimerId = -1; + } + return bindData; +} + +void QSocks5BindStore::timerEvent(QTimerEvent * event) +{ + QMutexLocker lock(&mutex); + if (event->timerId() == sweepTimerId) { + QSOCKS5_DEBUG << "QSocks5BindStore performing sweep"; + QMutableHashIterator<int, QSocks5BindData *> it(store); + while (it.hasNext()) { + it.next(); + if (it.value()->timeStamp.hasExpired(350000)) { + QSOCKS5_DEBUG << "QSocks5BindStore removing JJJJ"; + it.remove(); + } + } + } +} + +QSocks5Authenticator::QSocks5Authenticator() +{ +} + +QSocks5Authenticator::~QSocks5Authenticator() +{ +} + +char QSocks5Authenticator::methodId() +{ + return 0x00; +} + +bool QSocks5Authenticator::beginAuthenticate(QTcpSocket *socket, bool *completed) +{ + Q_UNUSED(socket); + *completed = true; + return true; +} + +bool QSocks5Authenticator::continueAuthenticate(QTcpSocket *socket, bool *completed) +{ + Q_UNUSED(socket); + *completed = true; + return true; +} + +bool QSocks5Authenticator::seal(const QByteArray buf, QByteArray *sealedBuf) +{ + *sealedBuf = buf; + return true; +} + +bool QSocks5Authenticator::unSeal(const QByteArray sealedBuf, QByteArray *buf) +{ + *buf = sealedBuf; + return true; +} + +bool QSocks5Authenticator::unSeal(QTcpSocket *sealedSocket, QByteArray *buf) +{ + return unSeal(sealedSocket->readAll(), buf); +} + +QSocks5PasswordAuthenticator::QSocks5PasswordAuthenticator(const QString &userName, const QString &password) +{ + this->userName = userName; + this->password = password; +} + +char QSocks5PasswordAuthenticator::methodId() +{ + return 0x02; +} + +bool QSocks5PasswordAuthenticator::beginAuthenticate(QTcpSocket *socket, bool *completed) +{ + *completed = false; + QByteArray uname = userName.toLatin1(); + QByteArray passwd = password.toLatin1(); + QByteArray dataBuf(3 + uname.size() + passwd.size(), 0); + char *buf = dataBuf.data(); + int pos = 0; + buf[pos++] = S5_PASSWORDAUTH_VERSION; + buf[pos++] = uname.size(); + memcpy(&buf[pos], uname.data(), uname.size()); + pos += uname.size(); + buf[pos++] = passwd.size(); + memcpy(&buf[pos], passwd.data(), passwd.size()); + return socket->write(dataBuf) == dataBuf.size(); +} + +bool QSocks5PasswordAuthenticator::continueAuthenticate(QTcpSocket *socket, bool *completed) +{ + *completed = false; + + if (socket->bytesAvailable() < 2) + return true; + + QByteArray buf = socket->read(2); + if (buf.at(0) == S5_PASSWORDAUTH_VERSION && buf.at(1) == 0x00) { + *completed = true; + return true; + } + + // must disconnect + socket->close(); + return false; +} + +QString QSocks5PasswordAuthenticator::errorString() +{ + return QLatin1String("Socks5 user name or password incorrect"); +} + + + +QSocks5SocketEnginePrivate::QSocks5SocketEnginePrivate() + : socks5State(Uninitialized) + , readNotificationEnabled(false) + , writeNotificationEnabled(false) + , exceptNotificationEnabled(false) + , socketDescriptor(-1) + , data(0) + , connectData(0) +#ifndef QT_NO_UDPSOCKET + , udpData(0) +#endif + , bindData(0) + , readNotificationActivated(false) + , writeNotificationActivated(false) + , readNotificationPending(false) + , writeNotificationPending(false) + , connectionNotificationPending(false) +{ + mode = NoMode; +} + +QSocks5SocketEnginePrivate::~QSocks5SocketEnginePrivate() +{ +} + +void QSocks5SocketEnginePrivate::initialize(Socks5Mode socks5Mode) +{ + Q_Q(QSocks5SocketEngine); + + mode = socks5Mode; + if (mode == ConnectMode) { + connectData = new QSocks5ConnectData; + data = connectData; +#ifndef QT_NO_UDPSOCKET + } else if (mode == UdpAssociateMode) { + udpData = new QSocks5UdpAssociateData; + data = udpData; + udpData->udpSocket = new QUdpSocket(q); +#ifndef QT_NO_BEARERMANAGEMENT + udpData->udpSocket->setProperty("_q_networksession", q->property("_q_networksession")); +#endif + udpData->udpSocket->setProxy(QNetworkProxy::NoProxy); + QObject::connect(udpData->udpSocket, SIGNAL(readyRead()), + q, SLOT(_q_udpSocketReadNotification()), + Qt::DirectConnection); +#endif // QT_NO_UDPSOCKET + } else if (mode == BindMode) { + bindData = new QSocks5BindData; + data = bindData; + } + + data->controlSocket = new QTcpSocket(q); +#ifndef QT_NO_BEARERMANAGEMENT + data->controlSocket->setProperty("_q_networksession", q->property("_q_networksession")); +#endif + data->controlSocket->setProxy(QNetworkProxy::NoProxy); + QObject::connect(data->controlSocket, SIGNAL(connected()), q, SLOT(_q_controlSocketConnected()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(readyRead()), q, SLOT(_q_controlSocketReadNotification()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(bytesWritten(qint64)), q, SLOT(_q_controlSocketBytesWritten()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(error(QAbstractSocket::SocketError)), + q, SLOT(_q_controlSocketError(QAbstractSocket::SocketError)), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(disconnected()), q, SLOT(_q_controlSocketDisconnected()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + q, SLOT(_q_controlSocketStateChanged(QAbstractSocket::SocketState)), + Qt::DirectConnection); + + if (!proxyInfo.user().isEmpty() || !proxyInfo.password().isEmpty()) { + QSOCKS5_D_DEBUG << "using username/password authentication; user =" << proxyInfo.user(); + data->authenticator = new QSocks5PasswordAuthenticator(proxyInfo.user(), proxyInfo.password()); + } else { + QSOCKS5_D_DEBUG << "not using authentication"; + data->authenticator = new QSocks5Authenticator(); + } +} + +void QSocks5SocketEnginePrivate::setErrorState(Socks5State state, const QString &extraMessage) +{ + Q_Q(QSocks5SocketEngine); + + switch (state) { + case Uninitialized: + case Authenticating: + case AuthenticationMethodsSent: + case RequestMethodSent: + case Connected: + case UdpAssociateSuccess: + case BindSuccess: + // these aren't error states + return; + + case ConnectError: + case ControlSocketError: { + QAbstractSocket::SocketError controlSocketError = data->controlSocket->error(); + if (socks5State != Connected) { + switch (controlSocketError) { + case QAbstractSocket::ConnectionRefusedError: + q->setError(QAbstractSocket::ProxyConnectionRefusedError, + QSocks5SocketEngine::tr("Connection to proxy refused")); + break; + case QAbstractSocket::RemoteHostClosedError: + q->setError(QAbstractSocket::ProxyConnectionClosedError, + QSocks5SocketEngine::tr("Connection to proxy closed prematurely")); + break; + case QAbstractSocket::HostNotFoundError: + q->setError(QAbstractSocket::ProxyNotFoundError, + QSocks5SocketEngine::tr("Proxy host not found")); + break; + case QAbstractSocket::SocketTimeoutError: + if (state == ConnectError) { + q->setError(QAbstractSocket::ProxyConnectionTimeoutError, + QSocks5SocketEngine::tr("Connection to proxy timed out")); + break; + } + /* fall through */ + default: + q->setError(controlSocketError, data->controlSocket->errorString()); + break; + } + } else { + q->setError(controlSocketError, data->controlSocket->errorString()); + } + break; + } + + case AuthenticatingError: + q->setError(QAbstractSocket::ProxyAuthenticationRequiredError, + extraMessage.isEmpty() ? + QSocks5SocketEngine::tr("Proxy authentication failed") : + QSocks5SocketEngine::tr("Proxy authentication failed: %1").arg(extraMessage)); + break; + + case RequestError: + // error code set by caller (overload) + break; + + case SocksError: + q->setError(QAbstractSocket::ProxyProtocolError, + QSocks5SocketEngine::tr("SOCKS version 5 protocol error")); + break; + + case HostNameLookupError: + q->setError(QAbstractSocket::HostNotFoundError, + QAbstractSocket::tr("Host not found")); + break; + } + + q->setState(QAbstractSocket::UnconnectedState); + socks5State = state; +} + +void QSocks5SocketEnginePrivate::setErrorState(Socks5State state, Socks5Error socks5error) +{ + Q_Q(QSocks5SocketEngine); + switch (socks5error) { + case SocksFailure: + q->setError(QAbstractSocket::NetworkError, + QSocks5SocketEngine::tr("General SOCKSv5 server failure")); + break; + case ConnectionNotAllowed: + q->setError(QAbstractSocket::SocketAccessError, + QSocks5SocketEngine::tr("Connection not allowed by SOCKSv5 server")); + break; + case NetworkUnreachable: + q->setError(QAbstractSocket::NetworkError, + QAbstractSocket::tr("Network unreachable")); + break; + case HostUnreachable: + q->setError(QAbstractSocket::HostNotFoundError, + QAbstractSocket::tr("Host not found")); + break; + case ConnectionRefused: + q->setError(QAbstractSocket::ConnectionRefusedError, + QAbstractSocket::tr("Connection refused")); + break; + case TTLExpired: + q->setError(QAbstractSocket::NetworkError, + QSocks5SocketEngine::tr("TTL expired")); + break; + case CommandNotSupported: + q->setError(QAbstractSocket::UnsupportedSocketOperationError, + QSocks5SocketEngine::tr("SOCKSv5 command not supported")); + break; + case AddressTypeNotSupported: + q->setError(QAbstractSocket::UnsupportedSocketOperationError, + QSocks5SocketEngine::tr("Address type not supported")); + break; + + default: + q->setError(QAbstractSocket::UnknownSocketError, + QSocks5SocketEngine::tr("Unknown SOCKSv5 proxy error code 0x%1").arg(int(socks5error), 16)); + break; + } + + setErrorState(state, QString()); +} + +void QSocks5SocketEnginePrivate::reauthenticate() +{ + Q_Q(QSocks5SocketEngine); + + // we require authentication + QAuthenticator auth; + emit q->proxyAuthenticationRequired(proxyInfo, &auth); + + if (!auth.user().isEmpty() || !auth.password().isEmpty()) { + // we have new credentials, let's try again + QSOCKS5_DEBUG << "authentication failure: retrying connection"; + socks5State = QSocks5SocketEnginePrivate::Uninitialized; + + delete data->authenticator; + proxyInfo.setUser(auth.user()); + proxyInfo.setPassword(auth.password()); + data->authenticator = new QSocks5PasswordAuthenticator(proxyInfo.user(), proxyInfo.password()); + + data->controlSocket->blockSignals(true); + data->controlSocket->abort(); + data->controlSocket->blockSignals(false); + data->controlSocket->connectToHost(proxyInfo.hostName(), proxyInfo.port()); + } else { + // authentication failure + + setErrorState(AuthenticatingError); + data->controlSocket->close(); + emitConnectionNotification(); + } +} + +void QSocks5SocketEnginePrivate::parseAuthenticationMethodReply() +{ + // not enough data to begin + if (data->controlSocket->bytesAvailable() < 2) + return; + + QByteArray buf = data->controlSocket->read(2); + if (buf.at(0) != S5_VERSION_5) { + QSOCKS5_D_DEBUG << "Socks5 version incorrect"; + setErrorState(SocksError); + data->controlSocket->close(); + emitConnectionNotification(); + return; + } + + bool authComplete = false; + if (uchar(buf.at(1)) == S5_AUTHMETHOD_NONE) { + authComplete = true; + } else if (uchar(buf.at(1)) == S5_AUTHMETHOD_NOTACCEPTABLE) { + reauthenticate(); + return; + } else if (buf.at(1) != data->authenticator->methodId() + || !data->authenticator->beginAuthenticate(data->controlSocket, &authComplete)) { + setErrorState(AuthenticatingError, QLatin1String("Socks5 host did not support authentication method.")); + socketError = QAbstractSocket::SocketAccessError; // change the socket error + emitConnectionNotification(); + return; + } + + if (authComplete) + sendRequestMethod(); + else + socks5State = Authenticating; +} + +void QSocks5SocketEnginePrivate::parseAuthenticatingReply() +{ + bool authComplete = false; + if (!data->authenticator->continueAuthenticate(data->controlSocket, &authComplete)) { + reauthenticate(); + return; + } + if (authComplete) + sendRequestMethod(); +} + +void QSocks5SocketEnginePrivate::sendRequestMethod() +{ + QHostAddress address; + quint16 port = 0; + char command = 0; + if (mode == ConnectMode) { + command = S5_CONNECT; + address = peerAddress; + port = peerPort; + } else if (mode == BindMode) { + command = S5_BIND; + address = localAddress; + port = localPort; + } else { +#ifndef QT_NO_UDPSOCKET + command = S5_UDP_ASSOCIATE; + address = localAddress; //data->controlSocket->localAddress(); + port = localPort; +#endif + } + + QByteArray buf; + buf.reserve(270); // big enough for domain name; + buf[0] = S5_VERSION_5; + buf[1] = command; + buf[2] = 0x00; + if (peerName.isEmpty() && !qt_socks5_set_host_address_and_port(address, port, &buf)) { + QSOCKS5_DEBUG << "error setting address" << address << " : " << port; + //### set error code .... + return; + } else if (!peerName.isEmpty() && !qt_socks5_set_host_name_and_port(peerName, port, &buf)) { + QSOCKS5_DEBUG << "error setting address" << address << " : " << port; + //### set error code .... + return; + } + QSOCKS5_DEBUG << "sending" << dump(buf); + QByteArray sealedBuf; + if (!data->authenticator->seal(buf, &sealedBuf)) { + // ### Handle this error. + } + data->controlSocket->write(sealedBuf); + data->controlSocket->flush(); + socks5State = RequestMethodSent; +} + +void QSocks5SocketEnginePrivate::parseRequestMethodReply() +{ + Q_Q(QSocks5SocketEngine); + QSOCKS5_DEBUG << "parseRequestMethodReply()"; + + QByteArray inBuf; + if (!data->authenticator->unSeal(data->controlSocket, &inBuf)) { + // ### check error and not just not enough data + QSOCKS5_DEBUG << "unSeal failed, needs more data"; + return; + } + QSOCKS5_DEBUG << dump(inBuf); + if (inBuf.size() < 2) { + QSOCKS5_DEBUG << "need more data for request reply header .. put this data somewhere"; + return; + } + + QHostAddress address; + quint16 port = 0; + + if (inBuf.at(0) != S5_VERSION_5 || inBuf.length() < 3 || inBuf.at(2) != 0x00) { + QSOCKS5_DEBUG << "socks protocol error"; + setErrorState(SocksError); + } else if (inBuf.at(1) != S5_SUCCESS) { + Socks5Error socks5Error = Socks5Error(inBuf.at(1)); + QSOCKS5_DEBUG << "Request error :" << socks5Error; + if ((socks5Error == SocksFailure || socks5Error == ConnectionNotAllowed) + && !peerName.isEmpty()) { + // Dante seems to use this error code to indicate hostname resolution failure + setErrorState(HostNameLookupError); + } else { + setErrorState(RequestError, socks5Error); + } + } else { + // connection success, retrieve the remote addresses + int pos = 3; + if (!qt_socks5_get_host_address_and_port(inBuf, &address, &port, &pos)) { + QSOCKS5_DEBUG << "error getting address"; + setErrorState(SocksError); + } else { + inBuf.remove(0, pos); + for (int i = inBuf.size() - 1; i >= 0 ; --i) + data->controlSocket->ungetChar(inBuf.at(i)); + } + } + + if (socks5State == RequestMethodSent) { + // no error + localAddress = address; + localPort = port; + + if (mode == ConnectMode) { + socks5State = Connected; + // notify the upper layer that we're done + q->setState(QAbstractSocket::ConnectedState); + emitConnectionNotification(); + } else if (mode == BindMode) { + socks5State = BindSuccess; + q->setState(QAbstractSocket::ListeningState); + } else { + socks5State = UdpAssociateSuccess; + } + } else if (socks5State == BindSuccess) { + // no error and we got a connection + bindData->peerAddress = address; + bindData->peerPort = port; + + emitReadNotification(); + } else { + // got an error + data->controlSocket->close(); + emitConnectionNotification(); + } +} + +void QSocks5SocketEnginePrivate::_q_emitPendingReadNotification() +{ + Q_Q(QSocks5SocketEngine); + readNotificationPending = false; + if (readNotificationEnabled) { + QSOCKS5_D_DEBUG << "emitting readNotification"; + QPointer<QSocks5SocketEngine> qq = q; + emit q->readNotification(); + if (!qq) + return; + // check if there needs to be a new zero read notification + if (data && data->controlSocket->state() == QAbstractSocket::UnconnectedState + && data->controlSocket->error() == QAbstractSocket::RemoteHostClosedError) { + connectData->readBuffer.clear(); + emitReadNotification(); + } + } +} + +void QSocks5SocketEnginePrivate::emitReadNotification() +{ + Q_Q(QSocks5SocketEngine); + readNotificationActivated = true; + if (readNotificationEnabled && !readNotificationPending) { + QSOCKS5_D_DEBUG << "queueing readNotification"; + readNotificationPending = true; + QMetaObject::invokeMethod(q, "_q_emitPendingReadNotification", Qt::QueuedConnection); + } +} + +void QSocks5SocketEnginePrivate::_q_emitPendingWriteNotification() +{ + writeNotificationPending = false; + Q_Q(QSocks5SocketEngine); + if (writeNotificationEnabled) { + QSOCKS5_D_DEBUG << "emitting writeNotification"; + emit q->writeNotification(); + } +} + +void QSocks5SocketEnginePrivate::emitWriteNotification() +{ + Q_Q(QSocks5SocketEngine); + writeNotificationActivated = true; + if (writeNotificationEnabled && !writeNotificationPending) { + QSOCKS5_D_DEBUG << "queueing writeNotification"; + writeNotificationPending = true; + QMetaObject::invokeMethod(q, "_q_emitPendingWriteNotification", Qt::QueuedConnection); + } +} + +void QSocks5SocketEnginePrivate::_q_emitPendingConnectionNotification() +{ + connectionNotificationPending = false; + Q_Q(QSocks5SocketEngine); + QSOCKS5_D_DEBUG << "emitting connectionNotification"; + emit q->connectionNotification(); +} + +void QSocks5SocketEnginePrivate::emitConnectionNotification() +{ + Q_Q(QSocks5SocketEngine); + QSOCKS5_D_DEBUG << "queueing connectionNotification"; + connectionNotificationPending = true; + QMetaObject::invokeMethod(q, "_q_emitPendingConnectionNotification", Qt::QueuedConnection); +} + +QSocks5SocketEngine::QSocks5SocketEngine(QObject *parent) +:QAbstractSocketEngine(*new QSocks5SocketEnginePrivate(), parent) +{ +} + +QSocks5SocketEngine::~QSocks5SocketEngine() +{ + Q_D(QSocks5SocketEngine); + + if (d->data) { + delete d->data->authenticator; + delete d->data->controlSocket; + } + if (d->connectData) + delete d->connectData; +#ifndef QT_NO_UDPSOCKET + if (d->udpData) { + delete d->udpData->udpSocket; + delete d->udpData; + } +#endif + if (d->bindData) + delete d->bindData; +} + +static QBasicAtomicInt descriptorCounter = Q_BASIC_ATOMIC_INITIALIZER(1); + +bool QSocks5SocketEngine::initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QSocks5SocketEngine); + + d->socketDescriptor = descriptorCounter.fetchAndAddRelaxed(1); + + d->socketType = type; + d->socketProtocol = protocol; + + return true; +} + +bool QSocks5SocketEngine::initialize(int socketDescriptor, QAbstractSocket::SocketState socketState) +{ + Q_D(QSocks5SocketEngine); + + QSOCKS5_Q_DEBUG << "initialize" << socketDescriptor; + + // this is only valid for the other side of a bind, nothing else is supported + + if (socketState != QAbstractSocket::ConnectedState) { + //### must be connected state ??? + return false; + } + + QSocks5BindData *bindData = socks5BindStore()->retrieve(socketDescriptor); + if (bindData) { + + d->socketState = QAbstractSocket::ConnectedState; + d->socketType = QAbstractSocket::TcpSocket; + d->connectData = new QSocks5ConnectData; + d->data = d->connectData; + d->mode = QSocks5SocketEnginePrivate::ConnectMode; + d->data->controlSocket = bindData->controlSocket; + bindData->controlSocket = 0; + d->data->controlSocket->setParent(this); + d->socketProtocol = d->data->controlSocket->localAddress().protocol(); + d->data->authenticator = bindData->authenticator; + bindData->authenticator = 0; + d->localPort = bindData->localPort; + d->localAddress = bindData->localAddress; + d->peerPort = bindData->peerPort; + d->peerAddress = bindData->peerAddress; + delete bindData; + + QObject::connect(d->data->controlSocket, SIGNAL(connected()), this, SLOT(_q_controlSocketConnected()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(readyRead()), this, SLOT(_q_controlSocketReadNotification()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(_q_controlSocketBytesWritten()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(_q_controlSocketError(QAbstractSocket::SocketError)), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(disconnected()), this, SLOT(_q_controlSocketDisconnected()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + this, SLOT(_q_controlSocketStateChanged(QAbstractSocket::SocketState)), + Qt::DirectConnection); + + d->socks5State = QSocks5SocketEnginePrivate::Connected; + + if (d->data->controlSocket->bytesAvailable() != 0) + d->_q_controlSocketReadNotification(); + return true; + } + return false; +} + +void QSocks5SocketEngine::setProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QSocks5SocketEngine); + d->proxyInfo = networkProxy; +} + +int QSocks5SocketEngine::socketDescriptor() const +{ + Q_D(const QSocks5SocketEngine); + return d->socketDescriptor; +} + +bool QSocks5SocketEngine::isValid() const +{ + Q_D(const QSocks5SocketEngine); + return d->socketType != QAbstractSocket::UnknownSocketType + && d->socks5State != QSocks5SocketEnginePrivate::SocksError + && (d->socketError == QAbstractSocket::UnknownSocketError + || d->socketError == QAbstractSocket::SocketTimeoutError + || d->socketError == QAbstractSocket::UnfinishedSocketOperationError); +} + +bool QSocks5SocketEngine::connectInternal() +{ + Q_D(QSocks5SocketEngine); + + if (!d->data) { + if (socketType() == QAbstractSocket::TcpSocket) { + d->initialize(QSocks5SocketEnginePrivate::ConnectMode); +#ifndef QT_NO_UDPSOCKET + } else if (socketType() == QAbstractSocket::UdpSocket) { + d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); + // all udp needs to be bound + if (!bind(QHostAddress(QLatin1String("0.0.0.0")), 0)) + return false; + + setState(QAbstractSocket::ConnectedState); + return true; +#endif + } else { + qFatal("QSocks5SocketEngine::connectToHost: in QTcpServer mode"); + return false; + } + } + + if (d->socks5State == QSocks5SocketEnginePrivate::Uninitialized + && d->socketState != QAbstractSocket::ConnectingState) { + setState(QAbstractSocket::ConnectingState); + d->data->controlSocket->connectToHost(d->proxyInfo.hostName(), d->proxyInfo.port()); + return false; + } + return false; +} + +bool QSocks5SocketEngine::connectToHost(const QHostAddress &address, quint16 port) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_DEBUG << "connectToHost" << address << ':' << port; + + setPeerAddress(address); + setPeerPort(port); + d->peerName.clear(); + + return connectInternal(); +} + +bool QSocks5SocketEngine::connectToHostByName(const QString &hostname, quint16 port) +{ + Q_D(QSocks5SocketEngine); + + setPeerAddress(QHostAddress()); + setPeerPort(port); + d->peerName = hostname; + + return connectInternal(); +} + +void QSocks5SocketEnginePrivate::_q_controlSocketConnected() +{ + QSOCKS5_DEBUG << "_q_controlSocketConnected"; + QByteArray buf(3, 0); + buf[0] = S5_VERSION_5; + buf[1] = 0x01; + buf[2] = data->authenticator->methodId(); + data->controlSocket->write(buf); + socks5State = AuthenticationMethodsSent; +} + +void QSocks5SocketEnginePrivate::_q_controlSocketReadNotification() +{ + QSOCKS5_D_DEBUG << "_q_controlSocketReadNotification socks5state" << s5StateToString(socks5State) + << "bytes available" << data->controlSocket->bytesAvailable(); + + if (data->controlSocket->bytesAvailable() == 0) { + QSOCKS5_D_DEBUG << "########## bogus read why do we get these ... on windows only"; + return; + } + + switch (socks5State) { + case AuthenticationMethodsSent: + parseAuthenticationMethodReply(); + break; + case Authenticating: + parseAuthenticatingReply(); + break; + case RequestMethodSent: + parseRequestMethodReply(); + break; + case Connected: { + QByteArray buf; + if (!data->authenticator->unSeal(data->controlSocket, &buf)) { + // qDebug() << "unseal error maybe need to wait for more data"; + } + if (buf.size()) { + QSOCKS5_DEBUG << dump(buf); + connectData->readBuffer += buf; + emitReadNotification(); + } + break; + } + case BindSuccess: + // only get here if command is bind + if (mode == BindMode) { + parseRequestMethodReply(); + break; + } + + // fall through + default: + qWarning("QSocks5SocketEnginePrivate::_q_controlSocketReadNotification: " + "Unexpectedly received data while in state=%d and mode=%d", + socks5State, mode); + break; + }; +} + +void QSocks5SocketEnginePrivate::_q_controlSocketBytesWritten() +{ + QSOCKS5_DEBUG << "_q_controlSocketBytesWritten"; + + if (socks5State != Connected + || (mode == ConnectMode + && data->controlSocket->bytesToWrite())) + return; + if (data->controlSocket->bytesToWrite() < MaxWriteBufferSize) { + emitWriteNotification(); + writeNotificationActivated = false; + } +} + +void QSocks5SocketEnginePrivate::_q_controlSocketError(QAbstractSocket::SocketError error) +{ + QSOCKS5_D_DEBUG << "controlSocketError" << error << data->controlSocket->errorString(); + + if (error == QAbstractSocket::SocketTimeoutError) + return; // ignore this error -- comes from the waitFor* functions + + if (error == QAbstractSocket::RemoteHostClosedError + && socks5State == Connected) { + // clear the read buffer in connect mode so that bytes available returns 0 + // if there already is a read notification pending then this will be processed first + if (!readNotificationPending) + connectData->readBuffer.clear(); + emitReadNotification(); + data->controlSocket->close(); + // cause a disconnect in the outer socket + emitWriteNotification(); + } else if (socks5State == Uninitialized + || socks5State == AuthenticationMethodsSent + || socks5State == Authenticating + || socks5State == RequestMethodSent) { + setErrorState(socks5State == Uninitialized ? ConnectError : ControlSocketError); + data->controlSocket->close(); + emitConnectionNotification(); + } else { + q_func()->setError(data->controlSocket->error(), data->controlSocket->errorString()); + emitReadNotification(); + emitWriteNotification(); + } +} + +void QSocks5SocketEnginePrivate::_q_controlSocketDisconnected() +{ + QSOCKS5_D_DEBUG << "_q_controlSocketDisconnected"; +} + +void QSocks5SocketEnginePrivate::_q_controlSocketStateChanged(QAbstractSocket::SocketState state) +{ + QSOCKS5_D_DEBUG << "_q_controlSocketStateChanged" << state; +} + +#ifndef QT_NO_UDPSOCKET +void QSocks5SocketEnginePrivate::checkForDatagrams() const +{ + // udp should be unbuffered so we need to do some polling at certain points + if (udpData->udpSocket->hasPendingDatagrams()) + const_cast<QSocks5SocketEnginePrivate *>(this)->_q_udpSocketReadNotification(); +} + +void QSocks5SocketEnginePrivate::_q_udpSocketReadNotification() +{ + QSOCKS5_D_DEBUG << "_q_udpSocketReadNotification()"; + + // check some state stuff + if (!udpData->udpSocket->hasPendingDatagrams()) { + QSOCKS5_D_DEBUG << "false read ??"; + return; + } + + while (udpData->udpSocket->hasPendingDatagrams()) { + QByteArray sealedBuf(udpData->udpSocket->pendingDatagramSize(), 0); + QSOCKS5_D_DEBUG << "new datagram"; + udpData->udpSocket->readDatagram(sealedBuf.data(), sealedBuf.size()); + QByteArray inBuf; + if (!data->authenticator->unSeal(sealedBuf, &inBuf)) { + QSOCKS5_D_DEBUG << "failed unsealing datagram discarding"; + return; + } + QSOCKS5_DEBUG << dump(inBuf); + int pos = 0; + const char *buf = inBuf.constData(); + if (inBuf.size() < 4) { + QSOCKS5_D_DEBUG << "bugus udp data, discarding"; + return; + } + QSocks5RevivedDatagram datagram; + if (buf[pos++] != 0 || buf[pos++] != 0) { + QSOCKS5_D_DEBUG << "invalid datagram discarding"; + return; + } + if (buf[pos++] != 0) { //### add fragmentation reading support + QSOCKS5_D_DEBUG << "don't support fragmentation yet disgarding"; + return; + } + if (!qt_socks5_get_host_address_and_port(inBuf, &datagram.address, &datagram.port, &pos)) { + QSOCKS5_D_DEBUG << "failed to get address from datagram disgarding"; + return; + } + datagram.data = QByteArray(&buf[pos], inBuf.size() - pos); + udpData->pendingDatagrams.enqueue(datagram); + } + emitReadNotification(); +} +#endif // QT_NO_UDPSOCKET + +bool QSocks5SocketEngine::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QSocks5SocketEngine); + + // when bind wee will block until the bind is finished as the info from the proxy server is needed + + if (!d->data) { + if (socketType() == QAbstractSocket::TcpSocket) { + d->initialize(QSocks5SocketEnginePrivate::BindMode); +#ifndef QT_NO_UDPSOCKET + } else if (socketType() == QAbstractSocket::UdpSocket) { + d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); +#endif + } else { + //### something invalid + return false; + } + } + +#ifndef QT_NO_UDPSOCKET + if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { + if (!d->udpData->udpSocket->bind(address, port)) { + QSOCKS5_Q_DEBUG << "local udp bind failed"; + setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); + return false; + } + d->localAddress = d->udpData->udpSocket->localAddress(); + d->localPort = d->udpData->udpSocket->localPort(); + } else +#endif + if (d->mode == QSocks5SocketEnginePrivate::BindMode) { + d->localAddress = address; + d->localPort = port; + } else { + //### something invalid + return false; + } + + int msecs = SOCKS5_BLOCKING_BIND_TIMEOUT; + QElapsedTimer stopWatch; + stopWatch.start(); + d->data->controlSocket->connectToHost(d->proxyInfo.hostName(), d->proxyInfo.port()); + if (!d->waitForConnected(msecs, 0) || + d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) { + // waitForConnected sets the error state and closes the socket + QSOCKS5_Q_DEBUG << "waitForConnected to proxy server" << d->data->controlSocket->errorString(); + return false; + } + if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) { + setState(QAbstractSocket::BoundState); + return true; +#ifndef QT_NO_UDPSOCKET + } else if (d->socks5State == QSocks5SocketEnginePrivate::UdpAssociateSuccess) { + setState(QAbstractSocket::BoundState); + d->udpData->associateAddress = d->localAddress; + d->localAddress = QHostAddress(); + d->udpData->associatePort = d->localPort; + d->localPort = 0; + QUdpSocket dummy; +#ifndef QT_NO_BEARERMANAGEMENT + dummy.setProperty("_q_networksession", property("_q_networksession")); +#endif + dummy.setProxy(QNetworkProxy::NoProxy); + if (!dummy.bind() + || writeDatagram(0,0, d->data->controlSocket->localAddress(), dummy.localPort()) != 0 + || !dummy.waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed())) + || dummy.readDatagram(0,0, &d->localAddress, &d->localPort) != 0) { + QSOCKS5_DEBUG << "udp actual address and port lookup failed"; + setState(QAbstractSocket::UnconnectedState); + setError(dummy.error(), dummy.errorString()); + d->data->controlSocket->close(); + //### reset and error + return false; + } + QSOCKS5_DEBUG << "udp actual address and port" << d->localAddress << ':' << d->localPort; + return true; +#endif // QT_NO_UDPSOCKET + } + + // binding timed out + setError(QAbstractSocket::SocketTimeoutError, + QLatin1String(QT_TRANSLATE_NOOP("QSocks5SocketEngine", "Network operation timed out"))); + +///### delete d->udpSocket; +///### d->udpSocket = 0; + return false; +} + + +bool QSocks5SocketEngine::listen() +{ + Q_D(QSocks5SocketEngine); + + QSOCKS5_Q_DEBUG << "listen()"; + + // check that we are in bound and then go to listening. + if (d->socketState == QAbstractSocket::BoundState) { + d->socketState = QAbstractSocket::ListeningState; + + // check if we already have a connection + if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) + d->emitReadNotification(); + + return true; + } + return false; +} + +int QSocks5SocketEngine::accept() +{ + Q_D(QSocks5SocketEngine); + // check we are listing --- + + QSOCKS5_Q_DEBUG << "accept()"; + + if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) { + QSOCKS5_Q_DEBUG << "BindSuccess adding" << d->socketDescriptor << "to the bind store"; + d->data->controlSocket->disconnect(); + d->data->controlSocket->setParent(0); + d->bindData->localAddress = d->localAddress; + d->bindData->localPort = d->localPort; + int sd = d->socketDescriptor; + socks5BindStore()->add(sd, d->bindData); + d->data = 0; + d->bindData = 0; + d->socketDescriptor = 0; + //### do something about this socket layer ... set it closed and an error about why ... + // reset state and local port/address + d->socks5State = QSocks5SocketEnginePrivate::Uninitialized; // ..?? + d->socketState = QAbstractSocket::UnconnectedState; + return sd; + } + return -1; +} + +void QSocks5SocketEngine::close() +{ + QSOCKS5_Q_DEBUG << "close()"; + Q_D(QSocks5SocketEngine); + if (d->data && d->data->controlSocket) { + if (d->data->controlSocket->state() == QAbstractSocket::ConnectedState) { + int msecs = 100; + QElapsedTimer stopWatch; + stopWatch.start(); + while (!d->data->controlSocket->bytesToWrite()) { + if (!d->data->controlSocket->waitForBytesWritten(qt_timeout_value(msecs, stopWatch.elapsed()))) + break; + } + } + d->data->controlSocket->close(); + } +#ifndef QT_NO_UDPSOCKET + if (d->udpData && d->udpData->udpSocket) + d->udpData->udpSocket->close(); +#endif +} + +qint64 QSocks5SocketEngine::bytesAvailable() const +{ + Q_D(const QSocks5SocketEngine); + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) + return d->connectData->readBuffer.size(); +#ifndef QT_NO_UDPSOCKET + else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode + && !d->udpData->pendingDatagrams.isEmpty()) + return d->udpData->pendingDatagrams.first().data.size(); +#endif + return 0; +} + +qint64 QSocks5SocketEngine::read(char *data, qint64 maxlen) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_Q_DEBUG << "read( , maxlen = " << maxlen << ')'; + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) { + if (d->connectData->readBuffer.size() == 0) { + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) { + //imitate remote closed + close(); + setError(QAbstractSocket::RemoteHostClosedError, + QLatin1String("Remote host closed connection###")); + setState(QAbstractSocket::UnconnectedState); + return -1; + } else { + return 0; // nothing to be read + } + } + qint64 copy = qMin<qint64>(d->connectData->readBuffer.size(), maxlen); + memcpy(data, d->connectData->readBuffer.constData(), copy); + d->connectData->readBuffer.remove(0, copy); + QSOCKS5_DEBUG << "read" << dump(QByteArray(data, copy)); + return copy; +#ifndef QT_NO_UDPSOCKET + } else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { + return readDatagram(data, maxlen); +#endif + } + return 0; +} + +qint64 QSocks5SocketEngine::write(const char *data, qint64 len) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_Q_DEBUG << "write" << dump(QByteArray(data, len)); + + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) { + // clamp down the amount of bytes to transfer at once + len = qMin<qint64>(len, MaxWriteBufferSize) - d->data->controlSocket->bytesToWrite(); + if (len <= 0) + return 0; + + QByteArray buf = QByteArray::fromRawData(data, len); + QByteArray sealedBuf; + if (!d->data->authenticator->seal(buf, &sealedBuf)) { + // ### Handle this error. + } + + d->data->controlSocket->write(sealedBuf); + d->data->controlSocket->waitForBytesWritten(0); + return len; +#ifndef QT_NO_UDPSOCKET + } else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { + // send to connected address + return writeDatagram(data, len, d->peerAddress, d->peerPort); +#endif + } + //### set an error ??? + return -1; +} + +#ifndef QT_NO_UDPSOCKET +#ifndef QT_NO_NETWORKINTERFACE +bool QSocks5SocketEngine::joinMulticastGroup(const QHostAddress &, + const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} + +bool QSocks5SocketEngine::leaveMulticastGroup(const QHostAddress &, + const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} + + +QNetworkInterface QSocks5SocketEngine::multicastInterface() const +{ + return QNetworkInterface(); +} + +bool QSocks5SocketEngine::setMulticastInterface(const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} +#endif // QT_NO_NETWORKINTERFACE + +qint64 QSocks5SocketEngine::readDatagram(char *data, qint64 maxlen, QHostAddress *addr, + quint16 *port) +{ + Q_D(QSocks5SocketEngine); + + d->checkForDatagrams(); + + if (d->udpData->pendingDatagrams.isEmpty()) + return 0; + + QSocks5RevivedDatagram datagram = d->udpData->pendingDatagrams.dequeue(); + int copyLen = qMin<int>(maxlen, datagram.data.size()); + memcpy(data, datagram.data.constData(), copyLen); + if (addr) + *addr = datagram.address; + if (port) + *port = datagram.port; + return copyLen; +} + +qint64 QSocks5SocketEngine::writeDatagram(const char *data, qint64 len, const QHostAddress &address, + quint16 port) +{ + Q_D(QSocks5SocketEngine); + + // it is possible to send with out first binding with udp, but socks5 requires a bind. + if (!d->data) { + d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); + // all udp needs to be bound + if (!bind(QHostAddress(QLatin1String("0.0.0.0")), 0)) { + //### set error + return -1; + } + } + + QByteArray outBuf; + outBuf.reserve(270 + len); + outBuf[0] = 0x00; + outBuf[1] = 0x00; + outBuf[2] = 0x00; + if (!qt_socks5_set_host_address_and_port(address, port, &outBuf)) { + } + outBuf += QByteArray(data, len); + QSOCKS5_DEBUG << "sending" << dump(outBuf); + QByteArray sealedBuf; + if (!d->data->authenticator->seal(outBuf, &sealedBuf)) { + QSOCKS5_DEBUG << "sealing data failed"; + setError(QAbstractSocket::SocketAccessError, d->data->authenticator->errorString()); + return -1; + } + if (d->udpData->udpSocket->writeDatagram(sealedBuf, d->udpData->associateAddress, d->udpData->associatePort) != sealedBuf.size()) { + //### try frgamenting + if (d->udpData->udpSocket->error() == QAbstractSocket::DatagramTooLargeError) + setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); + //### else maybe more serious error + return -1; + } + + return len; +} + +bool QSocks5SocketEngine::hasPendingDatagrams() const +{ + Q_D(const QSocks5SocketEngine); + Q_INIT_CHECK(false); + + d->checkForDatagrams(); + + return !d->udpData->pendingDatagrams.isEmpty(); +} + +qint64 QSocks5SocketEngine::pendingDatagramSize() const +{ + Q_D(const QSocks5SocketEngine); + + d->checkForDatagrams(); + + if (!d->udpData->pendingDatagrams.isEmpty()) + return d->udpData->pendingDatagrams.head().data.size(); + return 0; +} +#endif // QT_NO_UDPSOCKET + +qint64 QSocks5SocketEngine::bytesToWrite() const +{ + Q_D(const QSocks5SocketEngine); + if (d->data && d->data->controlSocket) { + return d->data->controlSocket->bytesToWrite(); + } else { + return 0; + } +} + +int QSocks5SocketEngine::option(SocketOption option) const +{ + Q_D(const QSocks5SocketEngine); + if (d->data && d->data->controlSocket) { + // convert the enum and call the real socket + if (option == QAbstractSocketEngine::LowDelayOption) + return d->data->controlSocket->socketOption(QAbstractSocket::LowDelayOption).toInt(); + if (option == QAbstractSocketEngine::KeepAliveOption) + return d->data->controlSocket->socketOption(QAbstractSocket::KeepAliveOption).toInt(); + } + return -1; +} + +bool QSocks5SocketEngine::setOption(SocketOption option, int value) +{ + Q_D(QSocks5SocketEngine); + if (d->data && d->data->controlSocket) { + // convert the enum and call the real socket + if (option == QAbstractSocketEngine::LowDelayOption) + d->data->controlSocket->setSocketOption(QAbstractSocket::LowDelayOption, value); + if (option == QAbstractSocketEngine::KeepAliveOption) + d->data->controlSocket->setSocketOption(QAbstractSocket::KeepAliveOption, value); + return true; + } + return false; +} + +bool QSocks5SocketEnginePrivate::waitForConnected(int msecs, bool *timedOut) +{ + if (data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return false; + + const Socks5State wantedState = + mode == ConnectMode ? Connected : + mode == BindMode ? BindSuccess : + UdpAssociateSuccess; + + QElapsedTimer stopWatch; + stopWatch.start(); + + while (socks5State != wantedState) { + if (!data->controlSocket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + if (data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + setErrorState(QSocks5SocketEnginePrivate::ControlSocketError); + if (timedOut && data->controlSocket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } + + return true; +} + +bool QSocks5SocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_DEBUG << "waitForRead" << msecs; + + d->readNotificationActivated = false; + + QElapsedTimer stopWatch; + stopWatch.start(); + + // are we connected yet? + if (!d->waitForConnected(msecs, timedOut)) + return false; + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + // we're connected + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode || + d->mode == QSocks5SocketEnginePrivate::BindMode) { + while (!d->readNotificationActivated) { + if (!d->data->controlSocket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + setError(d->data->controlSocket->error(), d->data->controlSocket->errorString()); + if (timedOut && d->data->controlSocket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } +#ifndef QT_NO_UDPSOCKET + } else { + while (!d->readNotificationActivated) { + if (!d->udpData->udpSocket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); + if (timedOut && d->udpData->udpSocket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } +#endif // QT_NO_UDPSOCKET + } + + + bool ret = d->readNotificationActivated; + d->readNotificationActivated = false; + + QSOCKS5_DEBUG << "waitForRead returned" << ret; + return ret; +} + + +bool QSocks5SocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_DEBUG << "waitForWrite" << msecs; + + QElapsedTimer stopWatch; + stopWatch.start(); + + // are we connected yet? + if (!d->waitForConnected(msecs, timedOut)) + return false; + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + // we're connected + + // flush any bytes we may still have buffered in the time that we have left + if (d->data->controlSocket->bytesToWrite()) + d->data->controlSocket->waitForBytesWritten(qt_timeout_value(msecs, stopWatch.elapsed())); + while ((msecs == -1 || stopWatch.elapsed() < msecs) + && d->data->controlSocket->state() == QAbstractSocket::ConnectedState + && d->data->controlSocket->bytesToWrite() >= MaxWriteBufferSize) + d->data->controlSocket->waitForBytesWritten(qt_timeout_value(msecs, stopWatch.elapsed())); + return d->data->controlSocket->bytesToWrite() < MaxWriteBufferSize; +} + +bool QSocks5SocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_UNUSED(checkRead); + if (!checkWrite) { + bool canRead = waitForRead(msecs, timedOut); + if (readyToRead) + *readyToRead = canRead; + return canRead; + } + + bool canWrite = waitForWrite(msecs, timedOut); + if (readyToWrite) + *readyToWrite = canWrite; + return canWrite; +} + +bool QSocks5SocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QSocks5SocketEngine); + return d->readNotificationEnabled; +} + +void QSocks5SocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QSocks5SocketEngine); + + QSOCKS5_Q_DEBUG << "setReadNotificationEnabled(" << enable << ')'; + + bool emitSignal = false; + if (!d->readNotificationEnabled + && enable) { + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) + emitSignal = !d->connectData->readBuffer.isEmpty(); +#ifndef QT_NO_UDPSOCKET + else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) + emitSignal = !d->udpData->pendingDatagrams.isEmpty(); +#endif + else if (d->mode == QSocks5SocketEnginePrivate::BindMode + && d->socketState == QAbstractSocket::ListeningState + && d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) + emitSignal = true; + } + + d->readNotificationEnabled = enable; + + if (emitSignal) + d->emitReadNotification(); +} + +bool QSocks5SocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QSocks5SocketEngine); + return d->writeNotificationEnabled; +} + +void QSocks5SocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QSocks5SocketEngine); + d->writeNotificationEnabled = enable; + if (enable && d->socketState == QAbstractSocket::ConnectedState) { + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode && d->data->controlSocket->bytesToWrite()) + return; // will be emitted as a result of bytes written + d->emitWriteNotification(); + d->writeNotificationActivated = false; + } +} + +bool QSocks5SocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QSocks5SocketEngine); + return d->exceptNotificationEnabled; +} + +void QSocks5SocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QSocks5SocketEngine); + d->exceptNotificationEnabled = enable; +} + +QAbstractSocketEngine * +QSocks5SocketEngineHandler::createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &proxy, QObject *parent) +{ + Q_UNUSED(socketType); + + // proxy type must have been resolved by now + if (proxy.type() != QNetworkProxy::Socks5Proxy) { + QSOCKS5_DEBUG << "not proxying"; + return 0; + } + QScopedPointer<QSocks5SocketEngine> engine(new QSocks5SocketEngine(parent)); + engine->setProxy(proxy); + return engine.take(); +} + +QAbstractSocketEngine *QSocks5SocketEngineHandler::createSocketEngine(int socketDescriptor, QObject *parent) +{ + QSOCKS5_DEBUG << "createSocketEngine" << socketDescriptor; + if (socks5BindStore()->contains(socketDescriptor)) { + QSOCKS5_DEBUG << "bind store contains" << socketDescriptor; + return new QSocks5SocketEngine(parent); + } + return 0; +} + +#endif // QT_NO_SOCKS5 + +QT_END_NAMESPACE diff --git a/src/network/socket/qsocks5socketengine_p.h b/src/network/socket/qsocks5socketengine_p.h new file mode 100644 index 0000000000..9492d4532d --- /dev/null +++ b/src/network/socket/qsocks5socketengine_p.h @@ -0,0 +1,299 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSOCKS5SOCKETENGINE_P_H +#define QSOCKS5SOCKETENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qabstractsocketengine_p.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SOCKS5 + +class QSocks5SocketEnginePrivate; + +class Q_AUTOTEST_EXPORT QSocks5SocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT +public: + QSocks5SocketEngine(QObject *parent = 0); + ~QSocks5SocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + void setProxy(const QNetworkProxy &networkProxy); + + int socketDescriptor() const; + + bool isValid() const; + + bool connectInternal(); + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + +#ifndef QT_NO_UDPSOCKET +#ifndef QT_NO_NETWORKINTERFACE + bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &interface); + bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &interface); + QNetworkInterface multicastInterface() const; + bool setMulticastInterface(const QNetworkInterface &iface); +#endif // QT_NO_NETWORKINTERFACE + + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; +#endif // QT_NO_UDPSOCKET + + qint64 bytesToWrite() const; + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + +private: + Q_DECLARE_PRIVATE(QSocks5SocketEngine) + Q_DISABLE_COPY(QSocks5SocketEngine) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketConnected()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketReadNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketError(QAbstractSocket::SocketError)) +#ifndef QT_NO_UDPSOCKET + Q_PRIVATE_SLOT(d_func(), void _q_udpSocketReadNotification()) +#endif + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketBytesWritten()) + Q_PRIVATE_SLOT(d_func(), void _q_emitPendingReadNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_emitPendingWriteNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_emitPendingConnectionNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketDisconnected()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketStateChanged(QAbstractSocket::SocketState)) + +}; + + +class QTcpSocket; + +class QSocks5Authenticator +{ +public: + QSocks5Authenticator(); + virtual ~QSocks5Authenticator(); + virtual char methodId(); + virtual bool beginAuthenticate(QTcpSocket *socket, bool *completed); + virtual bool continueAuthenticate(QTcpSocket *socket, bool *completed); + + virtual bool seal(const QByteArray buf, QByteArray *sealedBuf); + virtual bool unSeal(const QByteArray sealedBuf, QByteArray *buf); + virtual bool unSeal(QTcpSocket *sealedSocket, QByteArray *buf); + + virtual QString errorString() { return QString(); } +}; + +class QSocks5PasswordAuthenticator : public QSocks5Authenticator +{ +public: + QSocks5PasswordAuthenticator(const QString &userName, const QString &password); + char methodId(); + bool beginAuthenticate(QTcpSocket *socket, bool *completed); + bool continueAuthenticate(QTcpSocket *socket, bool *completed); + + QString errorString(); + +private: + QString userName; + QString password; +}; + +struct QSocks5Data; +struct QSocks5ConnectData; +struct QSocks5UdpAssociateData; +struct QSocks5BindData; + +class QSocks5SocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QSocks5SocketEngine) +public: + QSocks5SocketEnginePrivate(); + ~QSocks5SocketEnginePrivate(); + + enum Socks5State + { + Uninitialized = 0, + ConnectError, + AuthenticationMethodsSent, + Authenticating, + AuthenticatingError, + RequestMethodSent, + RequestError, + Connected, + UdpAssociateSuccess, + BindSuccess, + ControlSocketError, + SocksError, + HostNameLookupError + }; + Socks5State socks5State; + + enum Socks5Mode + { + NoMode, + ConnectMode, + BindMode, + UdpAssociateMode + }; + Socks5Mode mode; + + enum Socks5Error + { + SocksFailure = 0x01, + ConnectionNotAllowed = 0x02, + NetworkUnreachable = 0x03, + HostUnreachable = 0x04, + ConnectionRefused = 0x05, + TTLExpired = 0x06, + CommandNotSupported = 0x07, + AddressTypeNotSupported = 0x08, + LastKnownError = AddressTypeNotSupported, + UnknownError + }; + + void initialize(Socks5Mode socks5Mode); + + void setErrorState(Socks5State state, const QString &extraMessage = QString()); + void setErrorState(Socks5State state, Socks5Error socks5error); + + void reauthenticate(); + void parseAuthenticationMethodReply(); + void parseAuthenticatingReply(); + void sendRequestMethod(); + void parseRequestMethodReply(); + void parseNewConnection(); + + bool waitForConnected(int msecs, bool *timedOut); + + void _q_controlSocketConnected(); + void _q_controlSocketReadNotification(); + void _q_controlSocketError(QAbstractSocket::SocketError); +#ifndef QT_NO_UDPSOCKET + void checkForDatagrams() const; + void _q_udpSocketReadNotification(); +#endif + void _q_controlSocketBytesWritten(); + void _q_controlSocketDisconnected(); + void _q_controlSocketStateChanged(QAbstractSocket::SocketState); + + QNetworkProxy proxyInfo; + + bool readNotificationEnabled, writeNotificationEnabled, exceptNotificationEnabled; + + int socketDescriptor; + + QSocks5Data *data; + QSocks5ConnectData *connectData; +#ifndef QT_NO_UDPSOCKET + QSocks5UdpAssociateData *udpData; +#endif + QSocks5BindData *bindData; + QString peerName; + + mutable bool readNotificationActivated; + mutable bool writeNotificationActivated; + + bool readNotificationPending; + void _q_emitPendingReadNotification(); + void emitReadNotification(); + bool writeNotificationPending; + void _q_emitPendingWriteNotification(); + void emitWriteNotification(); + bool connectionNotificationPending; + void _q_emitPendingConnectionNotification(); + void emitConnectionNotification(); +}; + +class Q_AUTOTEST_EXPORT QSocks5SocketEngineHandler : public QSocketEngineHandler +{ +public: + virtual QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &, QObject *parent); + virtual QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent); +}; + + +QT_END_NAMESPACE +#endif // QT_NO_SOCKS5 +#endif // QSOCKS5SOCKETENGINE_H diff --git a/src/network/socket/qsymbiansocketengine.cpp b/src/network/socket/qsymbiansocketengine.cpp new file mode 100644 index 0000000000..f1b2982626 --- /dev/null +++ b/src/network/socket/qsymbiansocketengine.cpp @@ -0,0 +1,1730 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QNATIVESOCKETENGINE_DEBUG +#include "qsymbiansocketengine_p.h" + +#include "qiodevice.h" +#include "qhostaddress.h" +#include "qelapsedtimer.h" +#include "qvarlengtharray.h" +#include "qnetworkinterface.h" +#include <private/qnetworksession_p.h> +#include <es_sock.h> +#include <in_sock.h> +#include <net/if.h> + +#include <private/qcore_symbian_p.h> + +#if !defined(QT_NO_NETWORKPROXY) +# include "qnetworkproxy.h" +# include "qabstractsocket.h" +# include "qtcpserver.h" +#endif + +#include <QCoreApplication> + +#include <qabstracteventdispatcher.h> +#include <private/qeventdispatcher_symbian_p.h> +#include <qsocketnotifier.h> +#include <qnetworkinterface.h> + +#include <private/qthread_p.h> +#include <private/qobject_p.h> +#include <private/qsystemerror_p.h> + +#if defined QNATIVESOCKETENGINE_DEBUG +#include <qstring.h> +#include <ctype.h> +#endif + +QT_BEGIN_NAMESPACE + +#define Q_VOID +// Common constructs +#define Q_CHECK_VALID_SOCKETLAYER(function, returnValue) do { \ + if (!isValid()) { \ + qWarning(""#function" was called on an uninitialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_INVALID_SOCKETLAYER(function, returnValue) do { \ + if (isValid()) { \ + qWarning(""#function" was called on an already initialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_STATE(function, checkState, returnValue) do { \ + if (d->socketState != (checkState)) { \ + qWarning(""#function" was not called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_NOT_STATE(function, checkState, returnValue) do { \ + if (d->socketState == (checkState)) { \ + qWarning(""#function" was called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_STATES(function, state1, state2, returnValue) do { \ + if (d->socketState != (state1) && d->socketState != (state2)) { \ + qWarning(""#function" was called" \ + " not in "#state1" or "#state2); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_TYPE(function, type, returnValue) do { \ + if (d->socketType != (type)) { \ + qWarning(#function" was called by a" \ + " socket other than "#type""); \ + return (returnValue); \ + } } while (0) + +#if defined QNATIVESOCKETENGINE_DEBUG + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(c)) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1(); + } + } + + if (len < maxSize) + out += "..."; + + return out; +} +#endif + +void QSymbianSocketEnginePrivate::getPortAndAddress(const TInetAddr& a, quint16 *port, QHostAddress *addr) +{ + if (a.Family() == KAfInet6 && !a.IsV4Compat() && !a.IsV4Mapped()) { + Q_IPV6ADDR tmp; + memcpy(&tmp, a.Ip6Address().u.iAddr8, sizeof(tmp)); + if (addr) { + QHostAddress tmpAddress; + tmpAddress.setAddress(tmp); + *addr = tmpAddress; + TPckgBuf<TSoInetIfQuery> query; + query().iSrcAddr = a; + TInt err = nativeSocket.GetOpt(KSoInetIfQueryBySrcAddr, KSolInetIfQuery, query); + if (!err) + addr->setScopeId(qt_TDesC2QString(query().iName)); + else + addr->setScopeId(QString::number(a.Scope())); + } + if (port) + *port = a.Port(); + return; + } + if (port) + *port = a.Port(); + if (addr) { + QHostAddress tmpAddress; + tmpAddress.setAddress(a.Address()); + *addr = tmpAddress; + } +} +/*! \internal + + Creates and returns a new socket descriptor of type \a socketType + and \a socketProtocol. Returns -1 on failure. +*/ +bool QSymbianSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType, + QAbstractSocket::NetworkLayerProtocol socketProtocol) +{ + Q_Q(QSymbianSocketEngine); + TUint family = KAfInet; // KAfInet6 is only used as an address family, not as a protocol family + TUint type = (socketType == QAbstractSocket::UdpSocket) ? KSockDatagram : KSockStream; + TUint protocol = (socketType == QAbstractSocket::UdpSocket) ? KProtocolInetUdp : KProtocolInetTcp; + + //Check if there is a user specified session + QVariant v(q->property("_q_networksession")); + TInt err; + if (v.isValid()) { + QSharedPointer<QNetworkSession> s = qvariant_cast<QSharedPointer<QNetworkSession> >(v); + err = QNetworkSessionPrivate::nativeOpenSocket(*s, nativeSocket, family, type, protocol); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::createNewSocket - _q_networksession was set" << err; +#endif + } else + err = nativeSocket.Open(socketServer, family, type, protocol); //TODO: FIXME - deprecated API, make sure we always have a connection instead + + if (err != KErrNone) { + switch (err) { + case KErrNotSupported: + case KErrNotFound: + setError(QAbstractSocket::UnsupportedSocketOperationError, + ProtocolUnsupportedErrorString); + break; + default: + setError(err); + break; + } + + return false; + } +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::createNewSocket - created" << nativeSocket.SubSessionHandle(); +#endif + socketDescriptor = QSymbianSocketManager::instance().addSocket(nativeSocket); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << " - allocated socket descriptor" << socketDescriptor; +#endif + return true; +} + +void QSymbianSocketEnginePrivate::setPortAndAddress(TInetAddr& nativeAddr, quint16 port, const QHostAddress &addr) +{ + nativeAddr.SetPort(port); + if (addr.protocol() == QAbstractSocket::IPv6Protocol) { + TPckgBuf<TSoInetIfQuery> query; + query().iName = qt_QString2TPtrC(addr.scopeId()); + TInt err = nativeSocket.GetOpt(KSoInetIfQueryByName, KSolInetIfQuery, query); + if (!err) + nativeAddr.SetScope(query().iIndex); + else + nativeAddr.SetScope(0); + Q_IPV6ADDR ip6 = addr.toIPv6Address(); + TIp6Addr v6addr; + memcpy(v6addr.u.iAddr8, ip6.c, 16); + nativeAddr.SetAddress(v6addr); + } else if (addr.protocol() == QAbstractSocket::IPv4Protocol) { + nativeAddr.SetAddress(addr.toIPv4Address()); + } else { + qWarning("unsupported network protocol (%d)", addr.protocol()); + } +} + +QSymbianSocketEnginePrivate::QSymbianSocketEnginePrivate() : + socketDescriptor(-1), + socketServer(QSymbianSocketManager::instance().getSocketServer()), + readNotificationsEnabled(false), + writeNotificationsEnabled(false), + exceptNotificationsEnabled(false), + asyncSelect(0) +{ +} + +QSymbianSocketEnginePrivate::~QSymbianSocketEnginePrivate() +{ +} + + +QSymbianSocketEngine::QSymbianSocketEngine(QObject *parent) + : QAbstractSocketEngine(*new QSymbianSocketEnginePrivate(), parent) +{ +} + + +QSymbianSocketEngine::~QSymbianSocketEngine() +{ + close(); +} + +/*! + Initializes a QSymbianSocketEngine by creating a new socket of type \a + socketType and network layer protocol \a protocol. Returns true on + success; otherwise returns false. + + If the socket was already initialized, this function closes the + socket before reeinitializing it. + + The new socket is non-blocking, and for UDP sockets it's also + broadcast enabled. +*/ +bool QSymbianSocketEngine::initialize(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QSymbianSocketEngine); + if (isValid()) + close(); + + // Create the socket + if (!d->createNewSocket(socketType, protocol)) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString typeStr = QLatin1String("UnknownSocketType"); + if (socketType == QAbstractSocket::TcpSocket) typeStr = QLatin1String("TcpSocket"); + else if (socketType == QAbstractSocket::UdpSocket) typeStr = QLatin1String("UdpSocket"); + QString protocolStr = QLatin1String("UnknownProtocol"); + if (protocol == QAbstractSocket::IPv4Protocol) protocolStr = QLatin1String("IPv4Protocol"); + else if (protocol == QAbstractSocket::IPv6Protocol) protocolStr = QLatin1String("IPv6Protocol"); + qDebug("QSymbianSocketEngine::initialize(type == %s, protocol == %s) failed: %s", + typeStr.toLatin1().constData(), protocolStr.toLatin1().constData(), d->socketErrorString.toLatin1().constData()); +#endif + return false; + } + + // Make the socket nonblocking. + if (!setOption(NonBlockingSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + d->NonBlockingInitFailedErrorString); + close(); + return false; + } + + // Set the broadcasting flag if it's a UDP socket. + if (socketType == QAbstractSocket::UdpSocket + && !setOption(BroadcastSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + d->BroadcastingInitFailedErrorString); + close(); + return false; + } + + + // Make sure we receive out-of-band data + if (socketType == QAbstractSocket::TcpSocket + && !setOption(ReceiveOutOfBandData, 1)) { + qWarning("QSymbianSocketEngine::initialize unable to inline out-of-band data"); + } + + + d->socketType = socketType; + d->socketProtocol = protocol; + return true; +} + +/*! \overload + + Initializes the socket using \a socketDescriptor instead of + creating a new one. The socket type and network layer protocol are + determined automatically. The socket's state is set to \a + socketState. + + If the socket type is either TCP or UDP, it is made non-blocking. + UDP sockets are also broadcast enabled. + */ +bool QSymbianSocketEngine::initialize(int socketDescriptor, QAbstractSocket::SocketState socketState) +{ + Q_D(QSymbianSocketEngine); + + if (isValid()) + close(); + + if (!QSymbianSocketManager::instance().lookupSocket(socketDescriptor, d->nativeSocket)) { + qWarning("QSymbianSocketEngine::initialize - socket descriptor not found"); + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QSymbianSocketEnginePrivate::InvalidSocketErrorString); + return false; + } +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::initialize - attached to" << d->nativeSocket.SubSessionHandle() << socketDescriptor; +#endif + Q_ASSERT(d->socketDescriptor == socketDescriptor || d->socketDescriptor == -1); + d->socketDescriptor = socketDescriptor; + + // determine socket type and protocol + if (!d->fetchConnectionParameters()) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::initialize(socketDescriptor == %i) failed: %s", + socketDescriptor, d->socketErrorString.toLatin1().constData()); +#endif + d->socketDescriptor = -1; + return false; + } + + if (d->socketType != QAbstractSocket::UnknownSocketType) { + // Make the socket nonblocking. + if (!setOption(NonBlockingSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + d->NonBlockingInitFailedErrorString); + close(); + return false; + } + + // Set the broadcasting flag if it's a UDP socket. + if (d->socketType == QAbstractSocket::UdpSocket + && !setOption(BroadcastSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + d->BroadcastingInitFailedErrorString); + close(); + return false; + } + + // Make sure we receive out-of-band data + if (d->socketType == QAbstractSocket::TcpSocket + && !setOption(ReceiveOutOfBandData, 1)) { + qWarning("QSymbianSocketEngine::initialize unable to inline out-of-band data"); + } + } + + d->socketState = socketState; + return true; +} + +/*! + Returns true if the socket is valid; otherwise returns false. A + socket is valid if it has not been successfully initialized, or if + it has been closed. +*/ +bool QSymbianSocketEngine::isValid() const +{ + Q_D(const QSymbianSocketEngine); + return d->socketDescriptor != -1; +} + + +/*! + Returns the native socket descriptor. Any use of this descriptor + stands the risk of being non-portable. +*/ +int QSymbianSocketEngine::socketDescriptor() const +{ + Q_D(const QSymbianSocketEngine); + return d->socketDescriptor; +} + +/* + Sets the socket option \a opt to \a v. +*/ +bool QSymbianSocketEngine::setOption(QAbstractSocketEngine::SocketOption opt, int v) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setOption(), false); + + TUint n = 0; + TUint level = KSOLSocket; // default + + if (!QSymbianSocketEnginePrivate::translateSocketOption(opt, n, level)) + return false; + + if (!level && !n) + return true; + + return (KErrNone == d->nativeSocket.SetOpt(n, level, v)); +} + +/* + Returns the value of the socket option \a opt. +*/ +int QSymbianSocketEngine::option(QAbstractSocketEngine::SocketOption opt) const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::option(), -1); + + TUint n; + TUint level = KSOLSocket; // default + + if (!QSymbianSocketEnginePrivate::translateSocketOption(opt, n, level)) + return false; + + if (!level && !n) + return 1; + + int v = -1; + //GetOpt() is non const + TInt err = d->nativeSocket.GetOpt(n, level, v); + if (!err) + return v; + + return -1; +} + +bool QSymbianSocketEnginePrivate::translateSocketOption(QAbstractSocketEngine::SocketOption opt, TUint &n, TUint &level) +{ + + switch (opt) { + case QAbstractSocketEngine::ReceiveBufferSocketOption: + n = KSORecvBuf; + break; + case QAbstractSocketEngine::SendBufferSocketOption: + n = KSOSendBuf; + break; + case QAbstractSocketEngine::NonBlockingSocketOption: + n = KSONonBlockingIO; + break; + case QAbstractSocketEngine::AddressReusable: + level = KSolInetIp; + n = KSoReuseAddr; + break; + case QAbstractSocketEngine::BroadcastSocketOption: + case QAbstractSocketEngine::BindExclusively: + level = 0; + n = 0; + return true; + case QAbstractSocketEngine::ReceiveOutOfBandData: + level = KSolInetTcp; + n = KSoTcpOobInline; + break; + case QAbstractSocketEngine::LowDelayOption: + level = KSolInetTcp; + n = KSoTcpNoDelay; + break; + case QAbstractSocketEngine::KeepAliveOption: + level = KSolInetTcp; + n = KSoTcpKeepAlive; + break; + case QAbstractSocketEngine::MulticastLoopbackOption: + level = KSolInetIp; + n = KSoIp6MulticastLoop; + break; + case QAbstractSocketEngine::MulticastTtlOption: + level = KSolInetIp; + n = KSoIp6MulticastHops; + break; + default: + return false; + } + return true; +} + +qint64 QSymbianSocketEngine::receiveBufferSize() const +{ + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::receiveBufferSize(), -1); + return option(ReceiveBufferSocketOption); +} + +void QSymbianSocketEngine::setReceiveBufferSize(qint64 size) +{ + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setReceiveBufferSize(), Q_VOID); + setOption(ReceiveBufferSocketOption, size); +} + +qint64 QSymbianSocketEngine::sendBufferSize() const +{ + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setSendBufferSize(), -1); + return option(SendBufferSocketOption); +} + +void QSymbianSocketEngine::setSendBufferSize(qint64 size) +{ + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setSendBufferSize(), Q_VOID); + setOption(SendBufferSocketOption, size); +} + +/*! + Connects to the remote host name given by \a name on port \a + port. When this function is called, the upper-level will not + perform a hostname lookup. + + The native socket engine does not support this operation, + but some other socket engines (notably proxy-based ones) do. +*/ +bool QSymbianSocketEngine::connectToHostByName(const QString &name, quint16 port) +{ + Q_UNUSED(name); + Q_UNUSED(port); + Q_D(QSymbianSocketEngine); + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QSymbianSocketEnginePrivate::OperationUnsupportedErrorString); + return false; +} + +/*! + If there's a connection activity on the socket, process it. Then + notify our parent if there really was activity. +*/ +void QSymbianSocketEngine::connectionNotification() +{ + // FIXME check if we really need to do it like that in Symbian + Q_D(QSymbianSocketEngine); + Q_ASSERT(state() == QAbstractSocket::ConnectingState); + + connectToHost(d->peerAddress, d->peerPort); + if (state() != QAbstractSocket::ConnectingState) { + // we changed states + QAbstractSocketEngine::connectionNotification(); + } +} + + +bool QSymbianSocketEngine::connectToHost(const QHostAddress &addr, quint16 port) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::connectToHost(), false); + +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug("QSymbianSocketEngine::connectToHost() : %d ", d->socketDescriptor); +#endif + + if (!d->checkProxy(addr)) + return false; + + d->peerAddress = addr; + d->peerPort = port; + + TInetAddr nativeAddr; + d->setPortAndAddress(nativeAddr, port, addr); + TRequestStatus status; + d->nativeSocket.Connect(nativeAddr, status); + User::WaitForRequest(status); + TInt err = status.Int(); + //For non blocking connect, KErrAlreadyExists is returned from the second Connect() to indicate + //the connection is up. So treat this the same as KErrNone which would be returned from the first + //call if it wouldn't block. (e.g. winsock wrapper in the emulator ignores the nonblocking flag) + if (err && err != KErrAlreadyExists) { + switch (err) { + case KErrWouldBlock: + d->socketState = QAbstractSocket::ConnectingState; + break; + default: + d->setError(err); + d->socketState = QAbstractSocket::UnconnectedState; + break; + } + + if (d->socketState != QAbstractSocket::ConnectedState) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::connectToHost(%s, %i) == false (%s)", + addr.toString().toLatin1().constData(), port, + d->socketState == QAbstractSocket::ConnectingState + ? "Connection in progress" : d->socketErrorString.toLatin1().constData()); +#endif + return false; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::Connect(%s, %i) == true", + addr.toString().toLatin1().constData(), port); +#endif + + d->socketState = QAbstractSocket::ConnectedState; + d->fetchConnectionParameters(); + return true; +} + +bool QSymbianSocketEngine::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::bind(), false); + + if (!d->checkProxy(address)) + return false; + + Q_CHECK_STATE(QSymbianSocketEngine::bind(), QAbstractSocket::UnconnectedState, false); + + TInetAddr nativeAddr; + if (address == QHostAddress::Any || address == QHostAddress::AnyIPv6) { + //Should allow both IPv4 and IPv6 + //Listening on "0.0.0.0" accepts ONLY ipv4 connections + //Listening on "::" accepts ONLY ipv6 connections + nativeAddr.SetFamily(KAFUnspec); + nativeAddr.SetPort(port); + } else { + d->setPortAndAddress(nativeAddr, port, address); + } + + TInt err = d->nativeSocket.Bind(nativeAddr); +#ifdef __WINS__ + if (err == KErrArgument) // winsock prt returns wrong error code + err = KErrInUse; +#endif + + if (err) { + switch (err) { + case KErrNotFound: + // the specified interface was not found - use the error code expected + d->setError(QAbstractSocket::SocketAddressNotAvailableError, QSymbianSocketEnginePrivate::AddressNotAvailableErrorString); + break; + default: + d->setError(err); + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::bind(%s, %i) == false (%s)", + address.toString().toLatin1().constData(), port, d->socketErrorString.toLatin1().constData()); +#endif + + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::bind(%s, %i) == true", + address.toString().toLatin1().constData(), port); +#endif + d->socketState = QAbstractSocket::BoundState; + + d->fetchConnectionParameters(); + + // When we bind to unspecified address (to get a dual mode socket), report back the + // same type of address that was requested. This is required for SOCKS proxy to work. + if (nativeAddr.Family() == KAFUnspec) + d->localAddress = address; + return true; +} + +bool QSymbianSocketEngine::listen() +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::listen(), false); + Q_CHECK_STATE(QSymbianSocketEngine::listen(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QSymbianSocketEngine::listen(), QAbstractSocket::TcpSocket, false); + TInt err = d->nativeSocket.Listen(50); + if (err) { + d->setError(err); + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::listen() == false (%s)", + d->socketErrorString.toLatin1().constData()); +#endif + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::listen() == true"); +#endif + + d->socketState = QAbstractSocket::ListeningState; + return true; +} + +int QSymbianSocketEngine::accept() +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::accept(), -1); + Q_CHECK_STATE(QSymbianSocketEngine::accept(), QAbstractSocket::ListeningState, false); + Q_CHECK_TYPE(QSymbianSocketEngine::accept(), QAbstractSocket::TcpSocket, false); + RSocket blankSocket; + blankSocket.Open(d->socketServer); + TRequestStatus status; + d->nativeSocket.Accept(blankSocket, status); + User::WaitForRequest(status); + if (status.Int()) { + blankSocket.Close(); + if (status != KErrWouldBlock) + qWarning("QSymbianSocketEngine::accept() - error %d", status.Int()); + return -1; + } + +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::accept - created" << blankSocket.SubSessionHandle(); +#endif + int fd = QSymbianSocketManager::instance().addSocket(blankSocket); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << " - allocated socket descriptor" << fd; +#endif + return fd; +} + +qint64 QSymbianSocketEngine::bytesAvailable() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::bytesAvailable(), -1); + Q_CHECK_NOT_STATE(QSymbianSocketEngine::bytesAvailable(), QAbstractSocket::UnconnectedState, false); + int nbytes = 0; + qint64 available = 0; + TInt err = d->nativeSocket.GetOpt(KSOReadBytesPending, KSOLSocket, nbytes); + if (err) + return 0; + available = (qint64) nbytes; + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::bytesAvailable() == %lli", available); +#endif + return available; +} + +bool QSymbianSocketEngine::hasPendingDatagrams() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::hasPendingDatagrams(), false); + Q_CHECK_NOT_STATE(QSymbianSocketEngine::hasPendingDatagrams(), QAbstractSocket::UnconnectedState, false); + Q_CHECK_TYPE(QSymbianSocketEngine::hasPendingDatagrams(), QAbstractSocket::UdpSocket, false); + int nbytes; + TInt err = d->nativeSocket.GetOpt(KSOReadBytesPending,KSOLSocket, nbytes); + return err == KErrNone && nbytes > 0; +} + +qint64 QSymbianSocketEngine::pendingDatagramSize() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::pendingDatagramSize(), false); + Q_CHECK_TYPE(QSymbianSocketEngine::hasPendingDatagrams(), QAbstractSocket::UdpSocket, false); + int nbytes; + TInt err = d->nativeSocket.GetOpt(KSOReadBytesPending,KSOLSocket, nbytes); + if (nbytes > 0) { + //nbytes includes IP header, which is of variable length (IPv4 with or without options, IPv6...) + QByteArray next(nbytes,0); + TPtr8 buffer((TUint8*)next.data(), next.size()); + TInetAddr addr; + TRequestStatus status; + //TODO: rather than peek, should we save this for next call to readDatagram? + //what if calls don't match though? + d->nativeSocket.RecvFrom(buffer, addr, KSockReadPeek, status); + User::WaitForRequest(status); + if (status.Int()) + return 0; + return buffer.Length(); + } + return qint64(nbytes); +} + + +qint64 QSymbianSocketEngine::readDatagram(char *data, qint64 maxSize, + QHostAddress *address, quint16 *port) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::readDatagram(), -1); + Q_CHECK_TYPE(QSymbianSocketEngine::readDatagram(), QAbstractSocket::UdpSocket, false); + TPtr8 buffer((TUint8*)data, (int)maxSize); + TInetAddr addr; + TRequestStatus status; + d->nativeSocket.RecvFrom(buffer, addr, 0, status); + User::WaitForRequest(status); //Non blocking receive + + if (status.Int()) { + d->setError(QAbstractSocket::NetworkError, d->ReceiveDatagramErrorString); + } else if (port || address) { + d->getPortAndAddress(addr, port, address); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + int len = buffer.Length(); + qDebug("QSymbianSocketEngine::receiveDatagram(%p \"%s\", %lli, %s, %i) == %lli", + data, qt_prettyDebug(data, qMin(len, ssize_t(16)), len).data(), maxSize, + address ? address->toString().toLatin1().constData() : "(nil)", + port ? *port : 0, (qint64) len); +#endif + + if (status.Int()) + return -1; + return qint64(buffer.Length()); +} + + +qint64 QSymbianSocketEngine::writeDatagram(const char *data, qint64 len, + const QHostAddress &host, quint16 port) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::writeDatagram(), -1); + Q_CHECK_TYPE(QSymbianSocketEngine::writeDatagram(), QAbstractSocket::UdpSocket, -1); + TPtrC8 buffer((TUint8*)data, (int)len); + TInetAddr addr; + d->setPortAndAddress(addr, port, host); + TSockXfrLength sentBytes; + TRequestStatus status; + d->nativeSocket.SendTo(buffer, addr, 0, status, sentBytes); + User::WaitForRequest(status); //Non blocking send + TInt err = status.Int(); + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::writeDatagram(%p \"%s\", %lli, \"%s\", %i) == %lli (err=%d)", data, + qt_prettyDebug(data, qMin<int>(len, 16), len).data(), len, host.toString().toLatin1().constData(), + port, (qint64) sentBytes(), err); +#endif + + if (err) { + switch (err) { + case KErrWouldBlock: + // do not error the socket. (otherwise socket layer is reset) + // On symbian^1 and earlier, KErrWouldBlock is returned when interface is not up yet + // On symbian^3, KErrNone is returned but sentBytes = 0 + return 0; + case KErrTooBig: + d->setError(QAbstractSocket::DatagramTooLargeError, d->DatagramTooLargeErrorString); + break; + default: + d->setError(QAbstractSocket::NetworkError, d->SendDatagramErrorString); + } + return -1; + } + + if (QSysInfo::s60Version() <= QSysInfo::SV_S60_5_0) { + // This is evil hack, but for some reason native RSocket::SendTo returns 0, + // for large datagrams (such as 600 bytes). Based on comments from Open C team + // this should happen only in platforms <= S60 5.0. + return len; + } + return sentBytes(); +} + +// FIXME check where the native socket engine called that.. +bool QSymbianSocketEnginePrivate::fetchConnectionParameters() +{ + localPort = 0; + localAddress.clear(); + peerPort = 0; + peerAddress.clear(); + + if (socketDescriptor == -1) + return false; + + if (!nativeSocket.SubSessionHandle()) { + if (!QSymbianSocketManager::instance().lookupSocket(socketDescriptor, nativeSocket)) { + setError(QAbstractSocket::UnsupportedSocketOperationError, InvalidSocketErrorString); + return false; + } + } + + // Determine local address + TSockAddr addr; + nativeSocket.LocalName(addr); + getPortAndAddress(addr, &localPort, &localAddress); + + // Determine protocol family + socketProtocol = localAddress.protocol(); + + // Determine the remote address + nativeSocket.RemoteName(addr); + getPortAndAddress(addr, &peerPort, &peerAddress); + + // Determine the socket type (UDP/TCP) + TProtocolDesc protocol; + TInt err = nativeSocket.Info(protocol); + if (err) { + setError(err); + return false; + } else { + switch (protocol.iProtocol) { + case KProtocolInetTcp: + socketType = QAbstractSocket::TcpSocket; + break; + case KProtocolInetUdp: + socketType = QAbstractSocket::UdpSocket; + break; + default: + socketType = QAbstractSocket::UnknownSocketType; + break; + } + } +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString socketProtocolStr = QLatin1String("UnknownProtocol"); + if (socketProtocol == QAbstractSocket::IPv4Protocol) socketProtocolStr = QLatin1String("IPv4Protocol"); + else if (socketProtocol == QAbstractSocket::IPv6Protocol) socketProtocolStr = QLatin1String("IPv6Protocol"); + + QString socketTypeStr = QLatin1String("UnknownSocketType"); + if (socketType == QAbstractSocket::TcpSocket) socketTypeStr = QLatin1String("TcpSocket"); + else if (socketType == QAbstractSocket::UdpSocket) socketTypeStr = QLatin1String("UdpSocket"); + + qDebug("QSymbianSocketEnginePrivate::fetchConnectionParameters() local == %s:%i," + " peer == %s:%i, socket == %s - %s", + localAddress.toString().toLatin1().constData(), localPort, + peerAddress.toString().toLatin1().constData(), peerPort,socketTypeStr.toLatin1().constData(), + socketProtocolStr.toLatin1().constData()); +#endif + return true; +} + +void QSymbianSocketEngine::close() +{ + if (!isValid()) + return; + Q_D(QSymbianSocketEngine); +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::close()"); +#endif + + d->readNotificationsEnabled = false; + d->writeNotificationsEnabled = false; + d->exceptNotificationsEnabled = false; + if (d->asyncSelect) { + d->asyncSelect->deleteLater(); + d->asyncSelect = 0; + } + + //TODO: call nativeSocket.Shutdown(EImmediate) in some cases? + if (d->socketType == QAbstractSocket::UdpSocket) { + //TODO: Close hangs without this, but only for UDP - why? + TRequestStatus stat; + d->nativeSocket.Shutdown(RSocket::EImmediate, stat); + User::WaitForRequest(stat); + } +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::close - closing socket" << d->nativeSocket.SubSessionHandle() << d->socketDescriptor; +#endif + //remove must come before close to avoid a race where another thread gets the old subsession handle + //reused & asserts when calling QSymbianSocketManager::instance->addSocket + QSymbianSocketManager::instance().removeSocket(d->nativeSocket); + d->nativeSocket.Close(); + d->socketDescriptor = -1; + + d->socketState = QAbstractSocket::UnconnectedState; + d->hasSetSocketError = false; + d->localPort = 0; + d->localAddress.clear(); + d->peerPort = 0; + d->peerAddress.clear(); +} + +qint64 QSymbianSocketEngine::write(const char *data, qint64 len) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::write(), -1); + Q_CHECK_STATE(QSymbianSocketEngine::write(), QAbstractSocket::ConnectedState, -1); + TPtrC8 buffer((TUint8*)data, (int)len); + TSockXfrLength sentBytes = 0; + TRequestStatus status; + d->nativeSocket.Send(buffer, 0, status, sentBytes); + User::WaitForRequest(status); //TODO: on emulator this blocks for write >16kB (non blocking IO not implemented properly?) + TInt err = status.Int(); + + if (err) { + switch (err) { + case KErrDisconnected: + case KErrEof: + sentBytes = -1; + d->setError(QAbstractSocket::RemoteHostClosedError, d->RemoteHostClosedErrorString); + close(); + break; + case KErrTooBig: + d->setError(QAbstractSocket::DatagramTooLargeError, d->DatagramTooLargeErrorString); + break; + case KErrWouldBlock: + break; + default: + sentBytes = -1; + d->setError(err); + close(); + break; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::write(%p \"%s\", %llu) == %i", + data, qt_prettyDebug(data, qMin((int) len, 16), + (int) len).data(), len, (int) sentBytes()); +#endif + + return qint64(sentBytes()); +} +/* +*/ +qint64 QSymbianSocketEngine::read(char *data, qint64 maxSize) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::read(), -1); + Q_CHECK_STATES(QSymbianSocketEngine::read(), QAbstractSocket::ConnectedState, QAbstractSocket::BoundState, -1); + + TPtr8 buffer((TUint8*)data, (int)maxSize); + TSockXfrLength received = 0; + TRequestStatus status; + TSockAddr dummy; + if (d->socketType == QAbstractSocket::UdpSocket) { + //RecvOneOrMore() can only be used with stream-interfaced connected sockets; datagram interface sockets will return KErrNotSupported. + d->nativeSocket.RecvFrom(buffer, dummy, 0, status); + } else { + d->nativeSocket.RecvOneOrMore(buffer, 0, status, received); + } + User::WaitForRequest(status); //Non blocking receive + TInt err = status.Int(); + int r = buffer.Length(); + + if (err == KErrWouldBlock) { + // No data was available for reading + r = -2; + } else if (err != KErrNone) { + d->setError(err); + close(); + r = -1; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::read(%p \"%s\", %llu) == %i (err = %d)", + data, qt_prettyDebug(data, qMin(r, ssize_t(16)), r).data(), + maxSize, r, err); +#endif + + return qint64(r); +} + +int QSymbianSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) const +{ + bool readyRead = false; + bool readyWrite = false; + if (selectForRead) + return nativeSelect(timeout, true, false, &readyRead, &readyWrite); + else + return nativeSelect(timeout, false, true, &readyRead, &readyWrite); +} + +/*! + \internal + \param timeout timeout in milliseconds + \param checkRead caller is interested if the socket is ready to read + \param checkWrite caller is interested if the socket is ready for write + \param selectForRead (out) should set to true if ready to read + \param selectForWrite (out) should set to true if ready to write + \return 0 on timeout, >0 on success, <0 on error + */ +int QSymbianSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const +{ + //cancel asynchronous notifier (only one IOCTL allowed at a time) + if (asyncSelect) + asyncSelect->Cancel(); + + TPckgBuf<TUint> selectFlags; + selectFlags() = KSockSelectExcept; + if (checkRead) + selectFlags() |= KSockSelectRead; + if (checkWrite) + selectFlags() |= KSockSelectWrite; + TInt err; + if (timeout == 0) { + //if timeout is zero, poll + err = nativeSocket.GetOpt(KSOSelectPoll, KSOLSocket, selectFlags); + } else { + TRequestStatus selectStat; + nativeSocket.Ioctl(KIOctlSelect, selectStat, &selectFlags, KSOLSocket); + + if (timeout < 0) + User::WaitForRequest(selectStat); //negative means no timeout + else { + if (!selectTimer.Handle()) + qt_symbian_throwIfError(selectTimer.CreateLocal()); + TRequestStatus timerStat; + selectTimer.HighRes(timerStat, timeout * 1000); + User::WaitForRequest(timerStat, selectStat); + if (selectStat == KRequestPending) { + nativeSocket.CancelIoctl(); + //CancelIoctl completes the request (most likely with KErrCancel) + //We need to wait for this to keep the thread semaphore balanced (or active scheduler will panic) + User::WaitForRequest(selectStat); + //restart asynchronous notifier (only one IOCTL allowed at a time) + if (asyncSelect) + asyncSelect->IssueRequest(); + #ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::nativeSelect: select timeout"; + #endif + return 0; //timeout + } else { + selectTimer.Cancel(); + User::WaitForRequest(timerStat); + } + } + + #ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::nativeSelect: select status" << selectStat.Int() << (int)selectFlags(); + #endif + err = selectStat.Int(); + } + + if (!err && (selectFlags() & KSockSelectExcept)) { + nativeSocket.GetOpt(KSOSelectLastError, KSOLSocket, err); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::nativeSelect: select last error" << err; +#endif + } + if (err) { + //TODO: avoidable cast? + //set the error here, because read won't always return the same error again as select. + const_cast<QSymbianSocketEnginePrivate*>(this)->setError(err); + //restart asynchronous notifier (only one IOCTL allowed at a time) + if (asyncSelect) + asyncSelect->IssueRequest(); //TODO: in error case should we restart or not? + return err; + } + if (checkRead && (selectFlags() & KSockSelectRead)) { + Q_ASSERT(selectForRead); + *selectForRead = true; + } + if (checkWrite && (selectFlags() & KSockSelectWrite)) { + Q_ASSERT(selectForWrite); + *selectForWrite = true; + } + //restart asynchronous notifier (only one IOCTL allowed at a time) + if (asyncSelect) + asyncSelect->IssueRequest(); + return 1; +} + +bool QSymbianSocketEngine::joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::joinMulticastGroup(), false); + Q_CHECK_STATE(QSymbianSocketEngine::joinMulticastGroup(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QSymbianSocketEngine::joinMulticastGroup(), QAbstractSocket::UdpSocket, false); + return d->multicastGroupMembershipHelper(groupAddress, iface, KSoIp6JoinGroup); +} + +bool QSymbianSocketEngine::leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::leaveMulticastGroup(), false); + Q_CHECK_STATE(QSymbianSocketEngine::leaveMulticastGroup(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QSymbianSocketEngine::leaveMulticastGroup(), QAbstractSocket::UdpSocket, false); + return d->multicastGroupMembershipHelper(groupAddress, iface, KSoIp6LeaveGroup); +} + +bool QSymbianSocketEnginePrivate::multicastGroupMembershipHelper(const QHostAddress &groupAddress, + const QNetworkInterface &iface, + TUint operation) +{ +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug() << "QSymbianSocketEnginePrivate::multicastGroupMembershipHelper" << groupAddress << iface << operation; +#endif + //translate address + TPckgBuf<TIp6Mreq> option; + if (groupAddress.protocol() == QAbstractSocket::IPv6Protocol) { + Q_IPV6ADDR ip6 = groupAddress.toIPv6Address(); + memcpy(option().iAddr.u.iAddr8, ip6.c, 16); + } else { + TInetAddr wrapped; + wrapped.SetAddress(groupAddress.toIPv4Address()); + wrapped.ConvertToV4Mapped(); + option().iAddr = wrapped.Ip6Address(); + } + option().iInterface = iface.index(); + //join or leave group + TInt err = nativeSocket.SetOpt(operation, KSolInetIp, option); +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug() << "address" << qt_prettyDebug((const char *)(option().iAddr.u.iAddr8), 16, 16); + qDebug() << "interface" << option().iInterface; + qDebug() << "error" << err; +#endif + if (err) { + setError(err); + } + return (KErrNone == err); +} + +QNetworkInterface QSymbianSocketEngine::multicastInterface() const +{ + //TODO + const Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::multicastInterface(), QNetworkInterface()); + Q_CHECK_TYPE(QSymbianSocketEngine::multicastInterface(), QAbstractSocket::UdpSocket, QNetworkInterface()); + return QNetworkInterface(); +} + +bool QSymbianSocketEngine::setMulticastInterface(const QNetworkInterface &iface) +{ + //TODO - this is possibly a unix'ism as the RConnection on which the socket was created is probably controlling this + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setMulticastInterface(), false); + Q_CHECK_TYPE(QSymbianSocketEngine::setMulticastInterface(), QAbstractSocket::UdpSocket, false); + return false; +} + +bool QSymbianSocketEnginePrivate::checkProxy(const QHostAddress &address) +{ + if (address == QHostAddress::LocalHost || address == QHostAddress::LocalHostIPv6) + return true; + +#if !defined(QT_NO_NETWORKPROXY) + QObject *parent = q_func()->parent(); + QNetworkProxy proxy; + if (QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(parent)) { + proxy = socket->proxy(); + } else if (QTcpServer *server = qobject_cast<QTcpServer *>(parent)) { + proxy = server->proxy(); + } else { + // no parent -> no proxy + return true; + } + + if (proxy.type() == QNetworkProxy::DefaultProxy) + proxy = QNetworkProxy::applicationProxy(); + + if (proxy.type() != QNetworkProxy::DefaultProxy && + proxy.type() != QNetworkProxy::NoProxy) { + // QSymbianSocketEngine doesn't do proxies + setError(QAbstractSocket::UnsupportedSocketOperationError, + InvalidProxyTypeString); + return false; + } +#endif + + return true; +} + +// FIXME this is also in QNativeSocketEngine, unify it +/*! \internal + + Sets the error and error string if not set already. The only + interesting error is the first one that occurred, and not the last + one. +*/ +void QSymbianSocketEnginePrivate::setError(QAbstractSocket::SocketError error, ErrorString errorString) const +{ + if (hasSetSocketError) { + // Only set socket errors once for one engine; expect the + // socket to recreate its engine after an error. Note: There's + // one exception: SocketError(11) bypasses this as it's purely + // a temporary internal error condition. + // Another exception is the way the waitFor*() functions set + // an error when a timeout occurs. After the call to setError() + // they reset the hasSetSocketError to false + return; + } + if (error != QAbstractSocket::SocketError(11)) + hasSetSocketError = true; + + socketError = error; + + switch (errorString) { + case NonBlockingInitFailedErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unable to initialize non-blocking socket"); + break; + case BroadcastingInitFailedErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unable to initialize broadcast socket"); + break; + case NoIpV6ErrorString: + socketErrorString = QSymbianSocketEngine::tr("Attempt to use IPv6 socket on a platform with no IPv6 support"); + break; + case RemoteHostClosedErrorString: + socketErrorString = QSymbianSocketEngine::tr("The remote host closed the connection"); + break; + case TimeOutErrorString: + socketErrorString = QSymbianSocketEngine::tr("Network operation timed out"); + break; + case ResourceErrorString: + socketErrorString = QSymbianSocketEngine::tr("Out of resources"); + break; + case OperationUnsupportedErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unsupported socket operation"); + break; + case ProtocolUnsupportedErrorString: + socketErrorString = QSymbianSocketEngine::tr("Protocol type not supported"); + break; + case InvalidSocketErrorString: + socketErrorString = QSymbianSocketEngine::tr("Invalid socket descriptor"); + break; + case HostUnreachableErrorString: + socketErrorString = QSymbianSocketEngine::tr("Host unreachable"); + break; + case NetworkUnreachableErrorString: + socketErrorString = QSymbianSocketEngine::tr("Network unreachable"); + break; + case AccessErrorString: + socketErrorString = QSymbianSocketEngine::tr("Permission denied"); + break; + case ConnectionTimeOutErrorString: + socketErrorString = QSymbianSocketEngine::tr("Connection timed out"); + break; + case ConnectionRefusedErrorString: + socketErrorString = QSymbianSocketEngine::tr("Connection refused"); + break; + case AddressInuseErrorString: + socketErrorString = QSymbianSocketEngine::tr("The bound address is already in use"); + break; + case AddressNotAvailableErrorString: + socketErrorString = QSymbianSocketEngine::tr("The address is not available"); + break; + case AddressProtectedErrorString: + socketErrorString = QSymbianSocketEngine::tr("The address is protected"); + break; + case DatagramTooLargeErrorString: + socketErrorString = QSymbianSocketEngine::tr("Datagram was too large to send"); + break; + case SendDatagramErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unable to send a message"); + break; + case ReceiveDatagramErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unable to receive a message"); + break; + case WriteErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unable to write"); + break; + case ReadErrorString: + socketErrorString = QSymbianSocketEngine::tr("Network error"); + break; + case PortInuseErrorString: + socketErrorString = QSymbianSocketEngine::tr("Another socket is already listening on the same port"); + break; + case NotSocketErrorString: + socketErrorString = QSymbianSocketEngine::tr("Operation on non-socket"); + break; + case InvalidProxyTypeString: + socketErrorString = QSymbianSocketEngine::tr("The proxy type is invalid for this operation"); + break; + case InvalidAddressErrorString: + socketErrorString = QSymbianSocketEngine::tr("The address is invalid for this operation"); + break; + case SessionNotOpenErrorString: + socketErrorString = QSymbianSocketEngine::tr("The specified network session is not opened"); + break; + case UnknownSocketErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unknown error"); + break; + } +} + +void QSymbianSocketEnginePrivate::setError(TInt symbianError) +{ + switch (symbianError) { + case KErrDisconnected: + case KErrEof: + case KErrConnectionTerminated: //interface stopped externally - RConnection::Stop(EStopAuthoritative) + setError(QAbstractSocket::RemoteHostClosedError, + QSymbianSocketEnginePrivate::RemoteHostClosedErrorString); + break; + case KErrNetUnreach: + setError(QAbstractSocket::NetworkError, + QSymbianSocketEnginePrivate::NetworkUnreachableErrorString); + break; + case KErrHostUnreach: + setError(QAbstractSocket::NetworkError, + QSymbianSocketEnginePrivate::HostUnreachableErrorString); + break; + case KErrNoProtocolOpt: + setError(QAbstractSocket::NetworkError, + QSymbianSocketEnginePrivate::ProtocolUnsupportedErrorString); + break; + case KErrInUse: + setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString); + break; + case KErrPermissionDenied: + setError(QAbstractSocket::SocketAccessError, AccessErrorString); + break; + case KErrNotSupported: + setError(QAbstractSocket::UnsupportedSocketOperationError, OperationUnsupportedErrorString); + break; + case KErrNoMemory: + setError(QAbstractSocket::SocketResourceError, ResourceErrorString); + break; + case KErrCouldNotConnect: + setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); + break; + case KErrTimedOut: + setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); + break; + case KErrBadName: + setError(QAbstractSocket::NetworkError, InvalidAddressErrorString); + break; + default: + socketError = QAbstractSocket::NetworkError; + socketErrorString = QSystemError(symbianError, QSystemError::NativeError).toString(); + break; + } + hasSetSocketError = true; +} + +void QSymbianSocketEngine::startNotifications() +{ + Q_D(QSymbianSocketEngine); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::startNotifications" << d->readNotificationsEnabled << d->writeNotificationsEnabled << d->exceptNotificationsEnabled; +#endif + if (!d->asyncSelect && (d->readNotificationsEnabled || d->writeNotificationsEnabled || d->exceptNotificationsEnabled)) { + if (d->threadData->eventDispatcher) { + d->asyncSelect = q_check_ptr(new QAsyncSelect( + static_cast<QEventDispatcherSymbian*> (d->threadData->eventDispatcher), d->nativeSocket, + this)); + } else { + // call again when event dispatcher has been created + QMetaObject::invokeMethod(this, "startNotifications", Qt::QueuedConnection); + } + } + if (d->asyncSelect) + d->asyncSelect->IssueRequest(); +} + +bool QSymbianSocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::isReadNotificationEnabled(), false); + return d->readNotificationsEnabled; +} + +void QSymbianSocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setReadNotificationEnabled(), Q_VOID); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::setReadNotificationEnabled" << enable << "socket" << d->socketDescriptor; +#endif + d->readNotificationsEnabled = enable; + startNotifications(); +} + +bool QSymbianSocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::isWriteNotificationEnabled(), false); + return d->writeNotificationsEnabled; +} + +void QSymbianSocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setWriteNotificationEnabled(), Q_VOID); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::setWriteNotificationEnabled" << enable << "socket" << d->socketDescriptor; +#endif + d->writeNotificationsEnabled = enable; + startNotifications(); +} + +bool QSymbianSocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::isExceptionNotificationEnabled(), false); + return d->exceptNotificationsEnabled; + return false; +} + +void QSymbianSocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setExceptionNotificationEnabled(), Q_VOID); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::setExceptionNotificationEnabled" << enable << "socket" << d->socketDescriptor; +#endif + d->exceptNotificationsEnabled = enable; + startNotifications(); +} + +bool QSymbianSocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::waitForRead(), false); + Q_CHECK_NOT_STATE(QSymbianSocketEngine::waitForRead(), + QAbstractSocket::UnconnectedState, false); + + if (timedOut) + *timedOut = false; + + int ret = d->nativeSelect(msecs, true); + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + d->TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +bool QSymbianSocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::waitForWrite(), false); + Q_CHECK_NOT_STATE(QSymbianSocketEngine::waitForWrite(), + QAbstractSocket::UnconnectedState, false); + + if (timedOut) + *timedOut = false; + + int ret = d->nativeSelect(msecs, false); + + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + d->TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +bool QSymbianSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::waitForWrite(), false); + Q_CHECK_NOT_STATE(QSymbianSocketEngine::waitForReadOrWrite(), + QAbstractSocket::UnconnectedState, false); + + int ret = d->nativeSelect(msecs, checkRead, checkWrite, readyToRead, readyToWrite); + + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + d->TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +qint64 QSymbianSocketEngine::bytesToWrite() const +{ + // This is what the QNativeSocketEngine does + return 0; +} + +bool QSymbianSocketEngine::event(QEvent* ev) +{ + Q_D(QSymbianSocketEngine); + if (ev->type() == QEvent::ThreadChange) { +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::event - ThreadChange" << d->readNotificationsEnabled << d->writeNotificationsEnabled << d->exceptNotificationsEnabled; +#endif + if (d->asyncSelect) { + delete d->asyncSelect; + d->asyncSelect = 0; + // recreate select in new thread (because it is queued, the method is called in the new thread context) + QMetaObject::invokeMethod(this, "startNotifications", Qt::QueuedConnection); + } + d->selectTimer.Close(); + return true; + } + return QAbstractSocketEngine::event(ev); +} + +QAsyncSelect::QAsyncSelect(QEventDispatcherSymbian *dispatcher, RSocket& sock, QSymbianSocketEngine *parent) + : QActiveObject(CActive::EPriorityStandard, dispatcher), + m_inSocketEvent(false), + m_deleteLater(false), + m_socket(sock), + m_selectFlags(0), + engine(parent) +{ + CActiveScheduler::Add(this); +} + +QAsyncSelect::~QAsyncSelect() +{ + Cancel(); +} + +void QAsyncSelect::DoCancel() +{ + m_socket.CancelIoctl(); +} + +void QAsyncSelect::RunL() +{ + QT_TRYCATCH_LEAVING(run()); +} + +//RunError is called by the active scheduler if RunL leaves. +//Typically this will happen if a std::bad_alloc propagates down from the application +TInt QAsyncSelect::RunError(TInt aError) +{ + if (engine) { + QT_TRY { + engine->d_func()->setError(aError); + if (engine->isExceptionNotificationEnabled()) + engine->exceptionNotification(); + if (engine->isReadNotificationEnabled()) + engine->readNotification(); + } + QT_CATCH(...) {} + } +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QAsyncSelect::RunError" << aError; +#endif + return KErrNone; +} + +void QAsyncSelect::run() +{ + //when event loop disabled socket events, defer until later + if (maybeDeferSocketEvent()) + return; + m_inSocketEvent = true; + m_selectBuf() &= m_selectFlags; //the select ioctl reports everything, so mask to only what we requested + //KSockSelectReadContinuation is for reading datagrams in a mode that doesn't discard when the + //datagram is larger than the read buffer - Qt doesn't need to use this. + if (engine && engine->isReadNotificationEnabled() + && ((m_selectBuf() & KSockSelectRead) || iStatus != KErrNone)) { + engine->readNotification(); + } + if (engine && engine->isWriteNotificationEnabled() + && ((m_selectBuf() & KSockSelectWrite) || iStatus != KErrNone)) { + if (engine->state() == QAbstractSocket::ConnectingState) + engine->connectionNotification(); + else + engine->writeNotification(); + } + if (engine && engine->isExceptionNotificationEnabled() + && ((m_selectBuf() & KSockSelectExcept) || iStatus != KErrNone)) { + engine->exceptionNotification(); + } + m_inSocketEvent = false; + if (m_deleteLater) { + delete this; + return; + } + // select again (unless disabled by one of the callbacks) + IssueRequest(); +} + +void QAsyncSelect::deleteLater() +{ + if (m_inSocketEvent) { + engine = 0; + m_deleteLater = true; + } else { + delete this; + } +} + +void QAsyncSelect::IssueRequest() +{ + if (m_inSocketEvent) + return; //prevent thrashing during a callback - socket engine enables/disables multiple notifiers + TUint selectFlags = 0; + if (engine->isReadNotificationEnabled()) + selectFlags |= KSockSelectRead; + if (engine->isWriteNotificationEnabled()) + selectFlags |= KSockSelectWrite; + if (engine->isExceptionNotificationEnabled()) + selectFlags |= KSockSelectExcept; + if (selectFlags != m_selectFlags) { +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QAsyncSelect::IssueRequest() - select flags" << m_selectFlags << "->" << selectFlags; +#endif + Cancel(); + m_selectFlags = selectFlags; + } + if (m_selectFlags && !IsActive()) { + //always request errors (write notification does not complete on connect errors) + m_selectBuf() = m_selectFlags | KSockSelectExcept; + m_socket.Ioctl(KIOctlSelect, iStatus, &m_selectBuf, KSOLSocket); + SetActive(); + } +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QAsyncSelect::IssueRequest() - IsActive" << IsActive(); +#endif +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qsymbiansocketengine_p.h b/src/network/socket/qsymbiansocketengine_p.h new file mode 100644 index 0000000000..85ab54af12 --- /dev/null +++ b/src/network/socket/qsymbiansocketengine_p.h @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSYMBIANSOCKETENGINE_P_H +#define QSYMBIANSOCKETENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// +#include "QtNetwork/qhostaddress.h" +#include "private/qabstractsocketengine_p.h" +#include "qplatformdefs.h" + +#include <private/qeventdispatcher_symbian_p.h> +#include <unistd.h> +#include <es_sock.h> +#include <in_sock.h> + +QT_BEGIN_NAMESPACE + + +class QSymbianSocketEnginePrivate; +class QNetworkInterface; + +class Q_AUTOTEST_EXPORT QSymbianSocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT + friend class QAsyncSelect; +public: + QSymbianSocketEngine(QObject *parent = 0); + ~QSymbianSocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + int socketDescriptor() const; + + bool isValid() const; + + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + + bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + QNetworkInterface multicastInterface() const; + bool setMulticastInterface(const QNetworkInterface &iface); + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; + + qint64 bytesToWrite() const; + + qint64 receiveBufferSize() const; + void setReceiveBufferSize(qint64 bufferSize); + + qint64 sendBufferSize() const; + void setSendBufferSize(qint64 bufferSize); + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + + bool event(QEvent* ev); + + Q_INVOKABLE void startNotifications(); + +public Q_SLOTS: + // TODO: Why do we do this? This is private Qt implementation stuff anyway, no need for it + // non-virtual override; + void connectionNotification(); + +private: + Q_DECLARE_PRIVATE(QSymbianSocketEngine) + Q_DISABLE_COPY(QSymbianSocketEngine) +}; + +class QSocketNotifier; + +class QReadNotifier; +class QWriteNotifier; +class QExceptionNotifier; +class QAsyncSelect : public QActiveObject +{ +public: + QAsyncSelect(QEventDispatcherSymbian *dispatcher, RSocket& sock, QSymbianSocketEngine *parent); + ~QAsyncSelect(); + + void deleteLater(); + void IssueRequest(); + + void refresh(); + +protected: + void DoCancel(); + void RunL(); + void run(); + TInt RunError(TInt aError); + +private: + bool m_inSocketEvent; + bool m_deleteLater; + RSocket &m_socket; + + TUint m_selectFlags; + TPckgBuf<TUint> m_selectBuf; //in & out IPC buffer + QSymbianSocketEngine *engine; +}; + +class QSymbianSocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QSymbianSocketEngine) +public: + QSymbianSocketEnginePrivate(); + ~QSymbianSocketEnginePrivate(); + + int socketDescriptor; + mutable RSocket nativeSocket; + // From QtCore: + RSocketServ& socketServer; + mutable RTimer selectTimer; + + bool readNotificationsEnabled; + bool writeNotificationsEnabled; + bool exceptNotificationsEnabled; + QAsyncSelect* asyncSelect; + + // FIXME this is duplicated from qnativesocketengine_p.h + enum ErrorString { + NonBlockingInitFailedErrorString, + BroadcastingInitFailedErrorString, + NoIpV6ErrorString, + RemoteHostClosedErrorString, + TimeOutErrorString, + ResourceErrorString, + OperationUnsupportedErrorString, + ProtocolUnsupportedErrorString, + InvalidSocketErrorString, + HostUnreachableErrorString, + NetworkUnreachableErrorString, + AccessErrorString, + ConnectionTimeOutErrorString, + ConnectionRefusedErrorString, + AddressInuseErrorString, + AddressNotAvailableErrorString, + AddressProtectedErrorString, + DatagramTooLargeErrorString, + SendDatagramErrorString, + ReceiveDatagramErrorString, + WriteErrorString, + ReadErrorString, + PortInuseErrorString, + NotSocketErrorString, + InvalidProxyTypeString, + //symbian specific + InvalidAddressErrorString, + SessionNotOpenErrorString, + + UnknownSocketErrorString = -1 + }; + void setError(QAbstractSocket::SocketError error, ErrorString errorString) const; + + void getPortAndAddress(const TInetAddr& a, quint16 *port, QHostAddress *addr); + void setPortAndAddress(TInetAddr& nativeAddr, quint16 port, const QHostAddress &addr); + void setError(TInt symbianError); + + int nativeSelect(int timeout, bool selectForRead) const; + int nativeSelect(int timeout, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const; + + bool createNewSocket(QAbstractSocket::SocketType socketType, + QAbstractSocket::NetworkLayerProtocol socketProtocol); + + bool checkProxy(const QHostAddress &address); + bool fetchConnectionParameters(); + + bool multicastGroupMembershipHelper(const QHostAddress &groupAddress, + const QNetworkInterface &iface, + TUint operation); + static bool translateSocketOption(QAbstractSocketEngine::SocketOption opt, TUint &n, TUint &level); +}; + +QT_END_NAMESPACE + +#endif // QSYMBIANSOCKETENGINE_P_H diff --git a/src/network/socket/qtcpserver.cpp b/src/network/socket/qtcpserver.cpp new file mode 100644 index 0000000000..5a60764a3b --- /dev/null +++ b/src/network/socket/qtcpserver.cpp @@ -0,0 +1,691 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QTCPSERVER_DEBUG + +/*! \class QTcpServer + + \brief The QTcpServer class provides a TCP-based server. + + \reentrant + \ingroup network + \inmodule QtNetwork + + This class makes it possible to accept incoming TCP connections. + You can specify the port or have QTcpServer pick one + automatically. You can listen on a specific address or on all the + machine's addresses. + + Call listen() to have the server listen for incoming connections. + The newConnection() signal is then emitted each time a client + connects to the server. + + Call nextPendingConnection() to accept the pending connection as + a connected QTcpSocket. The function returns a pointer to a + QTcpSocket in QAbstractSocket::ConnectedState that you can use for + communicating with the client. + + If an error occurs, serverError() returns the type of error, and + errorString() can be called to get a human readable description of + what happened. + + When listening for connections, the address and port on which the + server is listening are available as serverAddress() and + serverPort(). + + Calling close() makes QTcpServer stop listening for incoming + connections. + + Although QTcpServer is mostly designed for use with an event + loop, it's possible to use it without one. In that case, you must + use waitForNewConnection(), which blocks until either a + connection is available or a timeout expires. + + \section1 Symbian Platform Security Requirements + + On Symbian, processes which use this class must have the + \c NetworkServices platform security capability. If the client + process lacks this capability, it will lead to a panic. + + Platform security capabilities are added via the + \l{qmake-variable-reference.html#target-capability}{TARGET.CAPABILITY} + qmake variable. + + \sa QTcpSocket, {Fortune Server Example}, {Threaded Fortune Server Example}, + {Loopback Example}, {Torrent Example} +*/ + +/*! \fn void QTcpServer::newConnection() + + This signal is emitted every time a new connection is available. + + \sa hasPendingConnections(), nextPendingConnection() +*/ + +#include "private/qobject_p.h" +#include "qalgorithms.h" +#include "qhostaddress.h" +#include "qlist.h" +#include "qpointer.h" +#include "qabstractsocketengine_p.h" +#include "qtcpserver.h" +#include "qtcpsocket.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +#define Q_CHECK_SOCKETENGINE(returnValue) do { \ + if (!d->socketEngine) { \ + return returnValue; \ + } } while (0) + +class QTcpServerPrivate : public QObjectPrivate, public QAbstractSocketEngineReceiver +{ + Q_DECLARE_PUBLIC(QTcpServer) +public: + QTcpServerPrivate(); + ~QTcpServerPrivate(); + + QList<QTcpSocket *> pendingConnections; + + quint16 port; + QHostAddress address; + + QAbstractSocket::SocketState state; + QAbstractSocketEngine *socketEngine; + + QAbstractSocket::SocketError serverSocketError; + QString serverSocketErrorString; + + int maxConnections; + +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy; + QNetworkProxy resolveProxy(const QHostAddress &address, quint16 port); +#endif + + // from QAbstractSocketEngineReceiver + void readNotification(); + inline void writeNotification() {} + inline void exceptionNotification() {} + inline void connectionNotification() {} +#ifndef QT_NO_NETWORKPROXY + inline void proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *) {} +#endif + +}; + +/*! \internal +*/ +QTcpServerPrivate::QTcpServerPrivate() + : port(0) + , state(QAbstractSocket::UnconnectedState) + , socketEngine(0) + , serverSocketError(QAbstractSocket::UnknownSocketError) + , maxConnections(30) +{ +} + +/*! \internal +*/ +QTcpServerPrivate::~QTcpServerPrivate() +{ +} + +#ifndef QT_NO_NETWORKPROXY +/*! \internal + + Resolve the proxy to its final value. +*/ +QNetworkProxy QTcpServerPrivate::resolveProxy(const QHostAddress &address, quint16 port) +{ + if (address == QHostAddress::LocalHost || + address == QHostAddress::LocalHostIPv6) + return QNetworkProxy::NoProxy; + + QList<QNetworkProxy> proxies; + if (proxy.type() != QNetworkProxy::DefaultProxy) { + // a non-default proxy was set with setProxy + proxies << proxy; + } else { + // try the application settings instead + QNetworkProxyQuery query(port, QString(), QNetworkProxyQuery::TcpServer); + proxies = QNetworkProxyFactory::proxyForQuery(query); + } + + // return the first that we can use + foreach (const QNetworkProxy &p, proxies) { + if (p.capabilities() & QNetworkProxy::ListeningCapability) + return p; + } + + // no proxy found + // DefaultProxy will raise an error + return QNetworkProxy(QNetworkProxy::DefaultProxy); +} +#endif + +/*! \internal +*/ +void QTcpServerPrivate::readNotification() +{ + Q_Q(QTcpServer); + for (;;) { + if (pendingConnections.count() >= maxConnections) { +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServerPrivate::_q_processIncomingConnection() too many connections"); +#endif + if (socketEngine->isReadNotificationEnabled()) + socketEngine->setReadNotificationEnabled(false); + return; + } + + int descriptor = socketEngine->accept(); + if (descriptor == -1) + break; +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServerPrivate::_q_processIncomingConnection() accepted socket %i", descriptor); +#endif + q->incomingConnection(descriptor); + + QPointer<QTcpServer> that = q; + emit q->newConnection(); + if (!that || !q->isListening()) + return; + } +} + +/*! + Constructs a QTcpServer object. + + \a parent is passed to the QObject constructor. + + \sa listen(), setSocketDescriptor() +*/ +QTcpServer::QTcpServer(QObject *parent) + : QObject(*new QTcpServerPrivate, parent) +{ +} + +/*! + Destroys the QTcpServer object. If the server is listening for + connections, the socket is automatically closed. + + Any client \l{QTcpSocket}s that are still connected must either + disconnect or be reparented before the server is deleted. + + \sa close() +*/ +QTcpServer::~QTcpServer() +{ + close(); +} + +/*! + Tells the server to listen for incoming connections on address \a + address and port \a port. If \a port is 0, a port is chosen + automatically. If \a address is QHostAddress::Any, the server + will listen on all network interfaces. + + Returns true on success; otherwise returns false. + + \sa isListening() +*/ +bool QTcpServer::listen(const QHostAddress &address, quint16 port) +{ + Q_D(QTcpServer); + if (d->state == QAbstractSocket::ListeningState) { + qWarning("QTcpServer::listen() called when already listening"); + return false; + } + + QAbstractSocket::NetworkLayerProtocol proto = address.protocol(); + +#ifdef QT_NO_NETWORKPROXY + static const QNetworkProxy &proxy = *(QNetworkProxy *)0; +#else + QNetworkProxy proxy = d->resolveProxy(address, port); +#endif + + delete d->socketEngine; + d->socketEngine = QAbstractSocketEngine::createSocketEngine(QAbstractSocket::TcpSocket, proxy, this); + if (!d->socketEngine) { + d->serverSocketError = QAbstractSocket::UnsupportedSocketOperationError; + d->serverSocketErrorString = tr("Operation on socket is not supported"); + return false; + } +#ifndef QT_NO_BEARERMANAGEMENT + //copy network session down to the socket engine (if it has been set) + d->socketEngine->setProperty("_q_networksession", property("_q_networksession")); +#endif + if (!d->socketEngine->initialize(QAbstractSocket::TcpSocket, proto)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + +#if defined(Q_OS_UNIX) + // Under Unix, we want to be able to bind to the port, even if a socket on + // the same address-port is in TIME_WAIT. Under Windows this is possible + // anyway -- furthermore, the meaning of reusable on Windows is different: + // it means that you can use the same address-port for multiple listening + // sockets. + // Don't abort though if we can't set that option. For example the socks + // engine doesn't support that option, but that shouldn't prevent us from + // trying to bind/listen. + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); +#endif + + if (!d->socketEngine->bind(address, port)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + + if (!d->socketEngine->listen()) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + + d->socketEngine->setReceiver(d); + d->socketEngine->setReadNotificationEnabled(true); + + d->state = QAbstractSocket::ListeningState; + d->address = d->socketEngine->localAddress(); + d->port = d->socketEngine->localPort(); + +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::listen(%i, \"%s\") == true (listening on port %i)", port, + address.toString().toLatin1().constData(), d->socketEngine->localPort()); +#endif + return true; +} + +/*! + Returns true if the server is currently listening for incoming + connections; otherwise returns false. + + \sa listen() +*/ +bool QTcpServer::isListening() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(false); + return d->socketEngine->state() == QAbstractSocket::ListeningState; +} + +/*! + Closes the server. The server will no longer listen for incoming + connections. + + \sa listen() +*/ +void QTcpServer::close() +{ + Q_D(QTcpServer); + + qDeleteAll(d->pendingConnections); + d->pendingConnections.clear(); + + if (d->socketEngine) { + d->socketEngine->close(); + QT_TRY { + d->socketEngine->deleteLater(); + } QT_CATCH(const std::bad_alloc &) { + // in out of memory situations, the socketEngine + // will be deleted in ~QTcpServer (it's a child-object of this) + } + d->socketEngine = 0; + } + + d->state = QAbstractSocket::UnconnectedState; +} + +/*! + Returns the native socket descriptor the server uses to listen + for incoming instructions, or -1 if the server is not listening. + + If the server is using QNetworkProxy, the returned descriptor may + not be usable with native socket functions. + + \sa setSocketDescriptor(), isListening() +*/ +int QTcpServer::socketDescriptor() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(-1); + return d->socketEngine->socketDescriptor(); +} + +/*! + Sets the socket descriptor this server should use when listening + for incoming connections to \a socketDescriptor. Returns true if + the socket is set successfully; otherwise returns false. + + The socket is assumed to be in listening state. + + \sa socketDescriptor(), isListening() +*/ +bool QTcpServer::setSocketDescriptor(int socketDescriptor) +{ + Q_D(QTcpServer); + if (isListening()) { + qWarning("QTcpServer::setSocketDescriptor() called when already listening"); + return false; + } + + if (d->socketEngine) + delete d->socketEngine; + d->socketEngine = QAbstractSocketEngine::createSocketEngine(socketDescriptor, this); +#ifndef QT_NO_BEARERMANAGEMENT + //copy network session down to the socket engine (if it has been set) + d->socketEngine->setProperty("_q_networksession", property("_q_networksession")); +#endif + if (!d->socketEngine->initialize(socketDescriptor, QAbstractSocket::ListeningState)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::setSocketDescriptor(%i) failed (%s)", socketDescriptor, + d->serverSocketErrorString.toLatin1().constData()); +#endif + return false; + } + + d->socketEngine->setReceiver(d); + d->socketEngine->setReadNotificationEnabled(true); + + d->state = d->socketEngine->state(); + d->address = d->socketEngine->localAddress(); + d->port = d->socketEngine->localPort(); + +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::setSocketDescriptor(%i) succeeded.", socketDescriptor); +#endif + return true; +} + +/*! + Returns the server's port if the server is listening for + connections; otherwise returns 0. + + \sa serverAddress(), listen() +*/ +quint16 QTcpServer::serverPort() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(0); + return d->socketEngine->localPort(); +} + +/*! + Returns the server's address if the server is listening for + connections; otherwise returns QHostAddress::Null. + + \sa serverPort(), listen() +*/ +QHostAddress QTcpServer::serverAddress() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(QHostAddress(QHostAddress::Null)); + return d->socketEngine->localAddress(); +} + +/*! + Waits for at most \a msec milliseconds or until an incoming + connection is available. Returns true if a connection is + available; otherwise returns false. If the operation timed out + and \a timedOut is not 0, *\a timedOut will be set to true. + + This is a blocking function call. Its use is disadvised in a + single-threaded GUI application, since the whole application will + stop responding until the function returns. + waitForNewConnection() is mostly useful when there is no event + loop available. + + The non-blocking alternative is to connect to the newConnection() + signal. + + If msec is -1, this function will not time out. + + \sa hasPendingConnections(), nextPendingConnection() +*/ +bool QTcpServer::waitForNewConnection(int msec, bool *timedOut) +{ + Q_D(QTcpServer); + if (d->state != QAbstractSocket::ListeningState) + return false; + + if (!d->socketEngine->waitForRead(msec, timedOut)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + + if (timedOut && *timedOut) + return false; + + d->readNotification(); + + return true; +} + +/*! + Returns true if the server has a pending connection; otherwise + returns false. + + \sa nextPendingConnection(), setMaxPendingConnections() +*/ +bool QTcpServer::hasPendingConnections() const +{ + return !d_func()->pendingConnections.isEmpty(); +} + +/*! + Returns the next pending connection as a connected QTcpSocket + object. + + The socket is created as a child of the server, which means that + it is automatically deleted when the QTcpServer object is + destroyed. It is still a good idea to delete the object + explicitly when you are done with it, to avoid wasting memory. + + 0 is returned if this function is called when there are no pending + connections. + + \note The returned QTcpSocket object cannot be used from another + thread. If you want to use an incoming connection from another thread, + you need to override incomingConnection(). + + \sa hasPendingConnections() +*/ +QTcpSocket *QTcpServer::nextPendingConnection() +{ + Q_D(QTcpServer); + if (d->pendingConnections.isEmpty()) + return 0; + + if (!d->socketEngine->isReadNotificationEnabled()) + d->socketEngine->setReadNotificationEnabled(true); + + return d->pendingConnections.takeFirst(); +} + +/*! + This virtual function is called by QTcpServer when a new + connection is available. The \a socketDescriptor argument is the + native socket descriptor for the accepted connection. + + The base implementation creates a QTcpSocket, sets the socket + descriptor and then stores the QTcpSocket in an internal list of + pending connections. Finally newConnection() is emitted. + + Reimplement this function to alter the server's behavior when a + connection is available. + + If this server is using QNetworkProxy then the \a socketDescriptor + may not be usable with native socket functions, and should only be + used with QTcpSocket::setSocketDescriptor(). + + \note If you want to handle an incoming connection as a new QTcpSocket + object in another thread you have to pass the socketDescriptor + to the other thread and create the QTcpSocket object there and + use its setSocketDescriptor() method. + + \sa newConnection(), nextPendingConnection(), addPendingConnection() +*/ +void QTcpServer::incomingConnection(int socketDescriptor) +{ +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::incomingConnection(%i)", socketDescriptor); +#endif + + QTcpSocket *socket = new QTcpSocket(this); + socket->setSocketDescriptor(socketDescriptor); + addPendingConnection(socket); +} + +/*! + This function is called by QTcpServer::incomingConnection() + to add the \a socket to the list of pending incoming connections. + + \note Don't forget to call this member from reimplemented + incomingConnection() if you do not want to break the + Pending Connections mechanism. + + \sa incomingConnection() + \since 4.7 +*/ +void QTcpServer::addPendingConnection(QTcpSocket* socket) +{ + d_func()->pendingConnections.append(socket); +} + +/*! + Sets the maximum number of pending accepted connections to \a + numConnections. QTcpServer will accept no more than \a + numConnections incoming connections before + nextPendingConnection() is called. By default, the limit is 30 + pending connections. + + Clients may still able to connect after the server has reached + its maximum number of pending connections (i.e., QTcpSocket can + still emit the connected() signal). QTcpServer will stop + accepting the new connections, but the operating system may + still keep them in queue. + + \sa maxPendingConnections(), hasPendingConnections() +*/ +void QTcpServer::setMaxPendingConnections(int numConnections) +{ + d_func()->maxConnections = numConnections; +} + +/*! + Returns the maximum number of pending accepted connections. The + default is 30. + + \sa setMaxPendingConnections(), hasPendingConnections() +*/ +int QTcpServer::maxPendingConnections() const +{ + return d_func()->maxConnections; +} + +/*! + Returns an error code for the last error that occurred. + + \sa errorString() +*/ +QAbstractSocket::SocketError QTcpServer::serverError() const +{ + return d_func()->serverSocketError; +} + +/*! + Returns a human readable description of the last error that + occurred. + + \sa serverError() +*/ +QString QTcpServer::errorString() const +{ + return d_func()->serverSocketErrorString; +} + +#ifndef QT_NO_NETWORKPROXY +/*! + \since 4.1 + + Sets the explicit network proxy for this socket to \a networkProxy. + + To disable the use of a proxy for this socket, use the + QNetworkProxy::NoProxy proxy type: + + \snippet doc/src/snippets/code/src_network_socket_qtcpserver.cpp 0 + + \sa proxy(), QNetworkProxy +*/ +void QTcpServer::setProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QTcpServer); + d->proxy = networkProxy; +} + +/*! + \since 4.1 + + Returns the network proxy for this socket. + By default QNetworkProxy::DefaultProxy is used. + + \sa setProxy(), QNetworkProxy +*/ +QNetworkProxy QTcpServer::proxy() const +{ + Q_D(const QTcpServer); + return d->proxy; +} +#endif // QT_NO_NETWORKPROXY + +QT_END_NAMESPACE + +#include "moc_qtcpserver.cpp" + diff --git a/src/network/socket/qtcpserver.h b/src/network/socket/qtcpserver.h new file mode 100644 index 0000000000..4018da6d00 --- /dev/null +++ b/src/network/socket/qtcpserver.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTCPSERVER_H +#define QTCPSERVER_H + +#include <QtCore/qobject.h> +#include <QtNetwork/qabstractsocket.h> +#include <QtNetwork/qhostaddress.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QTcpServerPrivate; +#ifndef QT_NO_NETWORKPROXY +class QNetworkProxy; +#endif +class QTcpSocket; + +class Q_NETWORK_EXPORT QTcpServer : public QObject +{ + Q_OBJECT +public: + explicit QTcpServer(QObject *parent = 0); + virtual ~QTcpServer(); + + bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); + void close(); + + bool isListening() const; + + void setMaxPendingConnections(int numConnections); + int maxPendingConnections() const; + + quint16 serverPort() const; + QHostAddress serverAddress() const; + + int socketDescriptor() const; + bool setSocketDescriptor(int socketDescriptor); + + bool waitForNewConnection(int msec = 0, bool *timedOut = 0); + virtual bool hasPendingConnections() const; + virtual QTcpSocket *nextPendingConnection(); + + QAbstractSocket::SocketError serverError() const; + QString errorString() const; + +#ifndef QT_NO_NETWORKPROXY + void setProxy(const QNetworkProxy &networkProxy); + QNetworkProxy proxy() const; +#endif + +protected: + virtual void incomingConnection(int handle); + void addPendingConnection(QTcpSocket* socket); + +Q_SIGNALS: + void newConnection(); + +private: + Q_DISABLE_COPY(QTcpServer) + Q_DECLARE_PRIVATE(QTcpServer) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTCPSERVER_H diff --git a/src/network/socket/qtcpsocket.cpp b/src/network/socket/qtcpsocket.cpp new file mode 100644 index 0000000000..32edc2f8ab --- /dev/null +++ b/src/network/socket/qtcpsocket.cpp @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QTCPSOCKET_DEBUG + +/*! + \class QTcpSocket + + \brief The QTcpSocket class provides a TCP socket. + + \reentrant + \ingroup network + \inmodule QtNetwork + + TCP (Transmission Control Protocol) is a reliable, + stream-oriented, connection-oriented transport protocol. It is + especially well suited for continuous transmission of data. + + QTcpSocket is a convenience subclass of QAbstractSocket that + allows you to establish a TCP connection and transfer streams of + data. See the QAbstractSocket documentation for details. + + \bold{Note:} TCP sockets cannot be opened in QIODevice::Unbuffered mode. + + \section1 Symbian Platform Security Requirements + + On Symbian, processes which use this class must have the + \c NetworkServices platform security capability. If the client + process lacks this capability, it will result in a panic. + + Platform security capabilities are added via the + \l{qmake-variable-reference.html#target-capability}{TARGET.CAPABILITY} + qmake variable. + + \sa QTcpServer, QUdpSocket, QFtp, QNetworkAccessManager, + {Fortune Server Example}, {Fortune Client Example}, + {Threaded Fortune Server Example}, {Blocking Fortune Client Example}, + {Loopback Example}, {Torrent Example} +*/ + +#include "qlist.h" +#include "qtcpsocket_p.h" +#include "qtcpsocket.h" +#include "qhostaddress.h" + +QT_BEGIN_NAMESPACE + +/*! + Creates a QTcpSocket object in state \c UnconnectedState. + + \a parent is passed on to the QObject constructor. + + \sa socketType() +*/ +QTcpSocket::QTcpSocket(QObject *parent) + : QAbstractSocket(TcpSocket, *new QTcpSocketPrivate, parent) +{ +#if defined(QTCPSOCKET_DEBUG) + qDebug("QTcpSocket::QTcpSocket()"); +#endif + d_func()->isBuffered = true; +} + +/*! + Destroys the socket, closing the connection if necessary. + + \sa close() +*/ + +QTcpSocket::~QTcpSocket() +{ +#if defined(QTCPSOCKET_DEBUG) + qDebug("QTcpSocket::~QTcpSocket()"); +#endif +} + +/*! + \internal +*/ +QTcpSocket::QTcpSocket(QTcpSocketPrivate &dd, QObject *parent) + : QAbstractSocket(TcpSocket, dd, parent) +{ + d_func()->isBuffered = true; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qtcpsocket.h b/src/network/socket/qtcpsocket.h new file mode 100644 index 0000000000..a50e0feca9 --- /dev/null +++ b/src/network/socket/qtcpsocket.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTCPSOCKET_H +#define QTCPSOCKET_H + +#include <QtNetwork/qabstractsocket.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QTcpSocketPrivate; + +class Q_NETWORK_EXPORT QTcpSocket : public QAbstractSocket +{ + Q_OBJECT +public: + explicit QTcpSocket(QObject *parent = 0); + virtual ~QTcpSocket(); + +protected: + QTcpSocket(QTcpSocketPrivate &dd, QObject *parent = 0); + +private: + Q_DISABLE_COPY(QTcpSocket) + Q_DECLARE_PRIVATE(QTcpSocket) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTCPSOCKET_H diff --git a/src/network/socket/qtcpsocket_p.h b/src/network/socket/qtcpsocket_p.h new file mode 100644 index 0000000000..12414df2ed --- /dev/null +++ b/src/network/socket/qtcpsocket_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTCPSOCKET_P_H +#define QTCPSOCKET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/qtcpsocket.h> +#include <private/qabstractsocket_p.h> + +QT_BEGIN_NAMESPACE + +class QTcpSocketPrivate : public QAbstractSocketPrivate +{ + Q_DECLARE_PUBLIC(QTcpSocket) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/socket/qudpsocket.cpp b/src/network/socket/qudpsocket.cpp new file mode 100644 index 0000000000..f8bcd1b967 --- /dev/null +++ b/src/network/socket/qudpsocket.cpp @@ -0,0 +1,567 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QUDPSOCKET_DEBUG + +/*! \class QUdpSocket + + \reentrant + \brief The QUdpSocket class provides a UDP socket. + + \ingroup network + \inmodule QtNetwork + + UDP (User Datagram Protocol) is a lightweight, unreliable, + datagram-oriented, connectionless protocol. It can be used when + reliability isn't important. QUdpSocket is a subclass of + QAbstractSocket that allows you to send and receive UDP + datagrams. + + The most common way to use this class is to bind to an address and port + using bind(), then call writeDatagram() and readDatagram() to transfer + data. If you want to use the standard QIODevice functions read(), + readLine(), write(), etc., you must first connect the socket directly to a + peer by calling connectToHost(). + + The socket emits the bytesWritten() signal every time a datagram + is written to the network. If you just want to send datagrams, + you don't need to call bind(). + + The readyRead() signal is emitted whenever datagrams arrive. In + that case, hasPendingDatagrams() returns true. Call + pendingDatagramSize() to obtain the size of the first pending + datagram, and readDatagram() to read it. + + \note An incoming datagram should be read when you receive the readyRead() + signal, otherwise this signal will not be emitted for the next datagram. + + Example: + + \snippet doc/src/snippets/code/src_network_socket_qudpsocket.cpp 0 + + QUdpSocket also supports UDP multicast. Use joinMulticastGroup() and + leaveMulticastGroup() to control group membership, and + QAbstractSocket::MulticastTtlOption and + QAbstractSocket::MulticastLoopbackOption to set the TTL and loopback socket + options. Use setMulticastInterface() to control the outgoing interface for + multicast datagrams, and multicastInterface() to query it. + + With QUdpSocket, you can also establish a virtual connection to a + UDP server using connectToHost() and then use read() and write() + to exchange datagrams without specifying the receiver for each + datagram. + + The \l{network/broadcastsender}{Broadcast Sender}, + \l{network/broadcastreceiver}{Broadcast Receiver}, + \l{network/multicastsender}{Multicast Sender}, and + \l{network/multicastreceiver}{Multicast Receiver} examples illustrate how + to use QUdpSocket in applications. + + \section1 Symbian Platform Security Requirements + + On Symbian, processes which use this class must have the + \c NetworkServices platform security capability. If the client + process lacks this capability, operations will result in a panic. + + Platform security capabilities are added via the + \l{qmake-variable-reference.html#target-capability}{TARGET.CAPABILITY} + qmake variable. + + \sa QTcpSocket +*/ + +/*! \enum QUdpSocket::BindFlag + \since 4.1 + + This enum describes the different flags you can pass to modify the + behavior of QUdpSocket::bind(). + + \note On Symbian OS bind flags behaviour depends on process capabilties. + If process has NetworkControl capability, the bind attempt with + ReuseAddressHint will always succeed even if the address and port is already + bound by another socket with any flags. If process does not have + NetworkControl capability, the bind attempt to address and port already + bound by another socket will always fail. + + \value ShareAddress Allow other services to bind to the same address + and port. This is useful when multiple processes share + the load of a single service by listening to the same address and port + (e.g., a web server with several pre-forked listeners can greatly + improve response time). However, because any service is allowed to + rebind, this option is subject to certain security considerations. + Note that by combining this option with ReuseAddressHint, you will + also allow your service to rebind an existing shared address. On + Unix, this is equivalent to the SO_REUSEADDR socket option. On Windows, + this option is ignored. + + \value DontShareAddress Bind the address and port exclusively, so that + no other services are allowed to rebind. By passing this option to + QUdpSocket::bind(), you are guaranteed that on successs, your service + is the only one that listens to the address and port. No services are + allowed to rebind, even if they pass ReuseAddressHint. This option + provides more security than ShareAddress, but on certain operating + systems, it requires you to run the server with administrator privileges. + On Unix and Mac OS X, not sharing is the default behavior for binding + an address and port, so this option is ignored. On Windows, this + option uses the SO_EXCLUSIVEADDRUSE socket option. + + \value ReuseAddressHint Provides a hint to QUdpSocket that it should try + to rebind the service even if the address and port are already bound by + another socket. On Windows, this is equivalent to the SO_REUSEADDR + socket option. On Unix, this option is ignored. + + \value DefaultForPlatform The default option for the current platform. + On Unix and Mac OS X, this is equivalent to (DontShareAddress + + ReuseAddressHint), and on Windows, its equivalent to ShareAddress. +*/ + +#include "qhostaddress.h" +#include "qnetworkinterface.h" +#include "qabstractsocket_p.h" +#include "qudpsocket.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_UDPSOCKET + +#define QT_CHECK_BOUND(function, a) do { \ + if (!isValid()) { \ + qWarning(function" called on a QUdpSocket when not in QUdpSocket::BoundState"); \ + return (a); \ + } } while (0) + +class QUdpSocketPrivate : public QAbstractSocketPrivate +{ + Q_DECLARE_PUBLIC(QUdpSocket) + + bool doEnsureInitialized(const QHostAddress &bindAddress, quint16 bindPort, + const QHostAddress &remoteAddress); +public: + inline bool ensureInitialized(const QHostAddress &bindAddress, quint16 bindPort) + { return doEnsureInitialized(bindAddress, bindPort, QHostAddress()); } + + inline bool ensureInitialized(const QHostAddress &remoteAddress) + { return doEnsureInitialized(QHostAddress(), 0, remoteAddress); } +}; + +bool QUdpSocketPrivate::doEnsureInitialized(const QHostAddress &bindAddress, quint16 bindPort, + const QHostAddress &remoteAddress) +{ + const QHostAddress *address = &bindAddress; + QAbstractSocket::NetworkLayerProtocol proto = address->protocol(); + if (proto == QUdpSocket::UnknownNetworkLayerProtocol) { + address = &remoteAddress; + proto = address->protocol(); + } + +#if defined(QT_NO_IPV6) + Q_Q(QUdpSocket); + if (proto == QUdpSocket::IPv6Protocol) { + socketError = QUdpSocket::UnsupportedSocketOperationError; + q->setErrorString(QUdpSocket::tr("This platform does not support IPv6")); + return false; + } +#endif + + // now check if the socket engine is initialized and to the right type + if (!socketEngine || !socketEngine->isValid()) { + resolveProxy(remoteAddress.toString(), bindPort); + if (!initSocketLayer(address->protocol())) + return false; + } + + return true; +} + +/*! + Creates a QUdpSocket object. + + \a parent is passed to the QObject constructor. + + \sa socketType() +*/ +QUdpSocket::QUdpSocket(QObject *parent) + : QAbstractSocket(UdpSocket, *new QUdpSocketPrivate, parent) +{ + d_func()->isBuffered = false; +} + +/*! + Destroys the socket, closing the connection if necessary. + + \sa close() +*/ +QUdpSocket::~QUdpSocket() +{ +} + +/*! + Binds this socket to the address \a address and the port \a port. + When bound, the signal readyRead() is emitted whenever a UDP + datagram arrives on the specified address and port. This function + is useful to write UDP servers. + + On success, the functions returns true and the socket enters + BoundState; otherwise it returns false. + + The socket is bound using the DefaultForPlatform BindMode. + + \sa readDatagram() +*/ +bool QUdpSocket::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QUdpSocket); + if (!d->ensureInitialized(address, port)) + return false; + + bool result = d_func()->socketEngine->bind(address, port); + d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); + + if (!result) { + d->socketError = d_func()->socketEngine->error(); + setErrorString(d_func()->socketEngine->errorString()); + emit error(d_func()->socketError); + return false; + } + + d->state = BoundState; + d->localAddress = d->socketEngine->localAddress(); + d->localPort = d->socketEngine->localPort(); + + emit stateChanged(d_func()->state); + d_func()->socketEngine->setReadNotificationEnabled(true); + return true; +} + +/*! + \since 4.1 + \overload + + Binds to \a address on port \a port, using the BindMode \a mode. +*/ +bool QUdpSocket::bind(const QHostAddress &address, quint16 port, BindMode mode) +{ + Q_D(QUdpSocket); + if (!d->ensureInitialized(address, port)) + return false; + +#ifdef Q_OS_UNIX + if ((mode & ShareAddress) || (mode & ReuseAddressHint)) + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0); +#endif +#ifdef Q_OS_WIN + if (mode & ReuseAddressHint) + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0); + if (mode & DontShareAddress) + d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 0); +#endif + bool result = d_func()->socketEngine->bind(address, port); + d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); + + if (!result) { + d->socketError = d_func()->socketEngine->error(); + setErrorString(d_func()->socketEngine->errorString()); + emit error(d_func()->socketError); + return false; + } + + d->state = BoundState; + d->localAddress = d->socketEngine->localAddress(); + d->localPort = d->socketEngine->localPort(); + + emit stateChanged(d_func()->state); + d_func()->socketEngine->setReadNotificationEnabled(true); + return true; +} + +/*! \overload + + Binds to QHostAddress:Any on port \a port. +*/ +bool QUdpSocket::bind(quint16 port) +{ + return bind(QHostAddress::Any, port); +} + +/*! + \since 4.1 + \overload + + Binds to QHostAddress:Any on port \a port, using the BindMode \a mode. +*/ +bool QUdpSocket::bind(quint16 port, BindMode mode) +{ + return bind(QHostAddress::Any, port, mode); +} + +#ifndef QT_NO_NETWORKINTERFACE + +/*! + \since 4.8 + + Joins the the multicast group specified by \a groupAddress on the default + interface chosen by the operating system. The socket must be in BoundState, + otherwise an error occurs. + + This function returns true if successful; otherwise it returns false + and sets the socket error accordingly. + + \sa leaveMulticastGroup() +*/ +bool QUdpSocket::joinMulticastGroup(const QHostAddress &groupAddress) +{ + return joinMulticastGroup(groupAddress, QNetworkInterface()); +} + +/*! + \since 4.8 + \overload + + Joins the multicast group address \a groupAddress on the interface \a + iface. + + \sa leaveMulticastGroup() +*/ +bool QUdpSocket::joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + Q_D(QUdpSocket); + QT_CHECK_BOUND("QUdpSocket::joinMulticastGroup()", false); + return d->socketEngine->joinMulticastGroup(groupAddress, iface); +} + +/*! + \since 4.8 + + Leaves the multicast group specified by \a groupAddress on the default + interface chosen by the operating system. The socket must be in BoundState, + otherwise an error occurs. + + This function returns true if successful; otherwise it returns false and + sets the socket error accordingly. + + \sa joinMulticastGroup() +*/ +bool QUdpSocket::leaveMulticastGroup(const QHostAddress &groupAddress) +{ + return leaveMulticastGroup(groupAddress, QNetworkInterface()); +} + +/*! + \since 4.8 + \overload + + Leaves the multicast group specified by \a groupAddress on the interface \a + iface. + + \sa joinMulticastGroup() +*/ +bool QUdpSocket::leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + QT_CHECK_BOUND("QUdpSocket::leaveMulticastGroup()", false); + return d_func()->socketEngine->leaveMulticastGroup(groupAddress, iface); +} + +/*! + \since 4.8 + + Returns the interface for the outgoing interface for multicast datagrams. + This corresponds to the IP_MULTICAST_IF socket option for IPv4 sockets and + the IPV6_MULTICAST_IF socket option for IPv6 sockets. If no interface has + been previously set, this function returns an invalid QNetworkInterface. + The socket must be in BoundState, otherwise an invalid QNetworkInterface is + returned. + + \sa setMulticastInterface() +*/ +QNetworkInterface QUdpSocket::multicastInterface() const +{ + Q_D(const QUdpSocket); + QT_CHECK_BOUND("QUdpSocket::multicastInterface()", QNetworkInterface()); + return d->socketEngine->multicastInterface(); +} + +/*! + \since 4.8 + + Sets the outgoing interface for multicast datagrams to the interface \a + iface. This corresponds to the IP_MULTICAST_IF socket option for IPv4 + sockets and the IPV6_MULTICAST_IF socket option for IPv6 sockets. The + socket must be in BoundState, otherwise this function does nothing. + + \sa multicastInterface(), joinMulticastGroup(), leaveMulticastGroup() +*/ +void QUdpSocket::setMulticastInterface(const QNetworkInterface &iface) +{ + Q_D(QUdpSocket); + if (!isValid()) { + qWarning("QUdpSocket::setMulticastInterface() called on a QUdpSocket when not in QUdpSocket::BoundState"); + return; + } + d->socketEngine->setMulticastInterface(iface); +} + +#endif // QT_NO_NETWORKINTERFACE + +/*! + Returns true if at least one datagram is waiting to be read; + otherwise returns false. + + \sa pendingDatagramSize(), readDatagram() +*/ +bool QUdpSocket::hasPendingDatagrams() const +{ + QT_CHECK_BOUND("QUdpSocket::hasPendingDatagrams()", false); + return d_func()->socketEngine->hasPendingDatagrams(); +} + +/*! + Returns the size of the first pending UDP datagram. If there is + no datagram available, this function returns -1. + + \sa hasPendingDatagrams(), readDatagram() +*/ +qint64 QUdpSocket::pendingDatagramSize() const +{ + QT_CHECK_BOUND("QUdpSocket::pendingDatagramSize()", -1); + return d_func()->socketEngine->pendingDatagramSize(); +} + +/*! + Sends the datagram at \a data of size \a size to the host + address \a address at port \a port. Returns the number of + bytes sent on success; otherwise returns -1. + + Datagrams are always written as one block. The maximum size of a + datagram is highly platform-dependent, but can be as low as 8192 + bytes. If the datagram is too large, this function will return -1 + and error() will return DatagramTooLargeError. + + Sending datagrams larger than 512 bytes is in general disadvised, + as even if they are sent successfully, they are likely to be + fragmented by the IP layer before arriving at their final + destination. + + \warning In S60 5.0 and earlier versions, the writeDatagram return + value is not reliable for large datagrams. + + \warning Calling this function on a connected UDP socket may + result in an error and no packet being sent. If you are using a + connected socket, use write() to send datagrams. + + \sa readDatagram(), write() +*/ +qint64 QUdpSocket::writeDatagram(const char *data, qint64 size, const QHostAddress &address, + quint16 port) +{ + Q_D(QUdpSocket); +#if defined QUDPSOCKET_DEBUG + qDebug("QUdpSocket::writeDatagram(%p, %llu, \"%s\", %i)", data, size, + address.toString().toLatin1().constData(), port); +#endif + if (!d->ensureInitialized(address)) + return -1; + + qint64 sent = d->socketEngine->writeDatagram(data, size, address, port); + d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); + + if (sent >= 0) { + emit bytesWritten(sent); + } else { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + emit error(d->socketError); + } + return sent; +} + +/*! + \fn qint64 QUdpSocket::writeDatagram(const QByteArray &datagram, + const QHostAddress &host, quint16 port) + \overload + + Sends the datagram \a datagram to the host address \a host and at + port \a port. +*/ + +/*! + Receives a datagram no larger than \a maxSize bytes and stores + it in \a data. The sender's host address and port is stored in + *\a address and *\a port (unless the pointers are 0). + + Returns the size of the datagram on success; otherwise returns + -1. + + If \a maxSize is too small, the rest of the datagram will be + lost. To avoid loss of data, call pendingDatagramSize() to + determine the size of the pending datagram before attempting to + read it. If \a maxSize is 0, the datagram will be discarded. + + \sa writeDatagram(), hasPendingDatagrams(), pendingDatagramSize() +*/ +qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *address, + quint16 *port) +{ + Q_D(QUdpSocket); + +#if defined QUDPSOCKET_DEBUG + qDebug("QUdpSocket::readDatagram(%p, %llu, %p, %p)", data, maxSize, address, port); +#endif + QT_CHECK_BOUND("QUdpSocket::readDatagram()", -1); + qint64 readBytes = d->socketEngine->readDatagram(data, maxSize, address, port); + d_func()->socketEngine->setReadNotificationEnabled(true); + if (readBytes < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + emit error(d->socketError); + } + return readBytes; +} +#endif // QT_NO_UDPSOCKET + +QT_END_NAMESPACE diff --git a/src/network/socket/qudpsocket.h b/src/network/socket/qudpsocket.h new file mode 100644 index 0000000000..7502349c7a --- /dev/null +++ b/src/network/socket/qudpsocket.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QUDPSOCKET_H +#define QUDPSOCKET_H + +#include <QtNetwork/qabstractsocket.h> +#include <QtNetwork/qhostaddress.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_UDPSOCKET + +class QNetworkInterface; +class QUdpSocketPrivate; + +class Q_NETWORK_EXPORT QUdpSocket : public QAbstractSocket +{ + Q_OBJECT +public: + enum BindFlag { + DefaultForPlatform = 0x0, + ShareAddress = 0x1, + DontShareAddress = 0x2, + ReuseAddressHint = 0x4 + }; + Q_DECLARE_FLAGS(BindMode, BindFlag) + + explicit QUdpSocket(QObject *parent = 0); + virtual ~QUdpSocket(); + + bool bind(const QHostAddress &address, quint16 port); + bool bind(quint16 port = 0); + bool bind(const QHostAddress &address, quint16 port, BindMode mode); + bool bind(quint16 port, BindMode mode); + // ### Qt 5: Merge the bind functions + +#ifndef QT_NO_NETWORKINTERFACE + bool joinMulticastGroup(const QHostAddress &groupAddress); + bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + bool leaveMulticastGroup(const QHostAddress &groupAddress); + bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + + QNetworkInterface multicastInterface() const; + void setMulticastInterface(const QNetworkInterface &iface); +#endif + + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *host = 0, quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port); + inline qint64 writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port) + { return writeDatagram(datagram.constData(), datagram.size(), host, port); } + +private: + Q_DISABLE_COPY(QUdpSocket) + Q_DECLARE_PRIVATE(QUdpSocket) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QUdpSocket::BindMode) + +#endif // QT_NO_UDPSOCKET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QUDPSOCKET_H diff --git a/src/network/socket/socket.pri b/src/network/socket/socket.pri new file mode 100644 index 0000000000..ac9001247a --- /dev/null +++ b/src/network/socket/socket.pri @@ -0,0 +1,70 @@ +# Qt network socket + +HEADERS += socket/qabstractsocketengine_p.h \ + socket/qhttpsocketengine_p.h \ + socket/qsocks5socketengine_p.h \ + socket/qabstractsocket.h \ + socket/qabstractsocket_p.h \ + socket/qtcpsocket.h \ + socket/qudpsocket.h \ + socket/qtcpserver.h \ + socket/qlocalserver.h \ + socket/qlocalserver_p.h \ + socket/qlocalsocket.h \ + socket/qlocalsocket_p.h + +SOURCES += socket/qabstractsocketengine.cpp \ + socket/qhttpsocketengine.cpp \ + socket/qsocks5socketengine.cpp \ + socket/qabstractsocket.cpp \ + socket/qtcpsocket.cpp \ + socket/qudpsocket.cpp \ + socket/qtcpserver.cpp \ + socket/qlocalsocket.cpp \ + socket/qlocalserver.cpp + +# On Symbian we use QSymbianSocketEngine +symbian:SOURCES += socket/qsymbiansocketengine.cpp +symbian:HEADERS += socket/qsymbiansocketengine_p.h +# On others we use QNativeSocketEngine +!symbian:SOURCES += socket/qnativesocketengine.cpp +!symbian:HEADERS += socket/qnativesocketengine_p.h + +unix:!symbian: { + SOURCES += socket/qnativesocketengine_unix.cpp \ + socket/qlocalsocket_unix.cpp \ + socket/qlocalserver_unix.cpp +} + +symbian: { + SOURCES += socket/qlocalsocket_tcp.cpp \ + socket/qlocalserver_tcp.cpp + + DEFINES += QT_LOCALSOCKET_TCP +} + +unix:HEADERS += \ + socket/qnet_unix_p.h + +win32:SOURCES += socket/qnativesocketengine_win.cpp \ + socket/qlocalsocket_win.cpp \ + socket/qlocalserver_win.cpp + +wince*: { + SOURCES -= socket/qlocalsocket_win.cpp \ + socket/qlocalserver_win.cpp + SOURCES += socket/qlocalsocket_tcp.cpp \ + socket/qlocalserver_tcp.cpp + + DEFINES += QT_LOCALSOCKET_TCP +} + +integrity: { + SOURCES -= socket/qlocalsocket_unix.cpp \ + socket/qlocalserver_unix.cpp + SOURCES += socket/qlocalsocket_tcp.cpp \ + socket/qlocalserver_tcp.cpp \ + socket/qnativesocketengine_unix.cpp + + DEFINES += QT_LOCALSOCKET_TCP +} |