From 553c6416bb7219d7ab1b40579895bc3ebaeb87c7 Mon Sep 17 00:00:00 2001 From: Oliver Wolff Date: Thu, 13 Feb 2014 11:32:38 +0100 Subject: WinRT: Added socket engine implementation Added basic functionality to socket for WinRT. Even though not all auto tests pass yet, this patch can be seen as a foundation for upcoming work in this area. Reading from and writing to TCP socket works and one can listen for tcp connections. Change-Id: Id4c25ba1c7187ed92b6368c785c4f62837faded7 Reviewed-by: Andrew Knight --- src/network/socket/qnativesocketengine_winrt.cpp | 1025 ++++++++++++++++++++-- src/network/socket/qnativesocketengine_winrt_p.h | 64 ++ 2 files changed, 1004 insertions(+), 85 deletions(-) (limited to 'src/network/socket') diff --git a/src/network/socket/qnativesocketengine_winrt.cpp b/src/network/socket/qnativesocketengine_winrt.cpp index 8550e0b0d1..2a61325471 100644 --- a/src/network/socket/qnativesocketengine_winrt.cpp +++ b/src/network/socket/qnativesocketengine_winrt.cpp @@ -39,12 +39,161 @@ ** ****************************************************************************/ +#include + #include "qnativesocketengine_winrt_p.h" +#include +#include +#include +#include #include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Storage::Streams; +using namespace ABI::Windows::Networking; +using namespace ABI::Windows::Networking::Connectivity; +using namespace ABI::Windows::Networking::Sockets; + +typedef ITypedEventHandler ClientConnectedHandler; +typedef ITypedEventHandler DatagramReceivedHandler; +typedef IAsyncOperationWithProgressCompletedHandler SocketReadCompletedHandler; +typedef IAsyncOperationWithProgressCompletedHandler SocketWriteCompletedHandler; QT_BEGIN_NAMESPACE +// 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) + +typedef QHash TcpSocketHash; + +struct SocketHandler +{ + SocketHandler() : socketCount(0) {} + qintptr socketCount; + TcpSocketHash pendingTcpSockets; +}; + +Q_GLOBAL_STATIC(SocketHandler, gSocketHandler) + +QString qt_QStringFromHSTRING(HSTRING string) +{ + UINT32 length; + PCWSTR rawString = WindowsGetStringRawBuffer(string, &length); + return QString::fromWCharArray(rawString, length); +} + +class ByteArrayBuffer : public Microsoft::WRL::RuntimeClass, + IBuffer, Windows::Storage::Streams::IBufferByteAccess> +{ +public: + ByteArrayBuffer(int size) : m_bytes(size, Qt::Uninitialized), m_length(0) + { + } + + ByteArrayBuffer(const char *data, int size) : m_bytes(data, size), m_length(size) + { + } + + HRESULT __stdcall Buffer(byte **value) + { + *value = reinterpret_cast(m_bytes.data()); + return S_OK; + } + + HRESULT __stdcall get_Capacity(UINT32 *value) + { + *value = m_bytes.size(); + return S_OK; + } + + HRESULT __stdcall get_Length(UINT32 *value) + { + *value = m_length; + return S_OK; + } + + HRESULT __stdcall put_Length(UINT32 value) + { + Q_ASSERT(value <= UINT32(m_bytes.size())); + m_length = value; + return S_OK; + } + + QNativeSocketEngine *engine() const + { + return m_engine; + } + + void setEngine(QNativeSocketEngine *engine) + { + m_engine = engine; + } + + ComPtr inputStream() const + { + return m_stream; + } + + void setInputStream(ComPtr stream) + { + m_stream = stream; + } + +private: + QByteArray m_bytes; + UINT32 m_length; + QPointer m_engine; + ComPtr m_stream; +}; + QNativeSocketEngine::QNativeSocketEngine(QObject *parent) : QAbstractSocketEngine(*new QNativeSocketEnginePrivate(), parent) { @@ -57,85 +206,271 @@ QNativeSocketEngine::~QNativeSocketEngine() bool QNativeSocketEngine::initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol) { - Q_UNIMPLEMENTED(); - Q_UNUSED(type); - Q_UNUSED(protocol); - return false; + Q_D(QNativeSocketEngine); + if (isValid()) + close(); + + // Create the socket + if (!d->createNewSocket(type, protocol)) + return false; + + d->socketType = type; + d->socketProtocol = protocol; + return true; } bool QNativeSocketEngine::initialize(qintptr socketDescriptor, QAbstractSocket::SocketState socketState) { - Q_UNIMPLEMENTED(); - Q_UNUSED(socketDescriptor); - Q_UNUSED(socketState); - return false; + Q_D(QNativeSocketEngine); + + if (isValid()) + close(); + + d->socketDescriptor = socketDescriptor; + + // Currently, only TCP sockets are initialized this way. + SocketHandler *handler = gSocketHandler(); + d->tcp = handler->pendingTcpSockets.value(socketDescriptor, Q_NULLPTR); + d->socketType = QAbstractSocket::TcpSocket; + + if (!d->tcp || !d->fetchConnectionParameters()) + return false; + + d->socketState = socketState; + return true; } qintptr QNativeSocketEngine::socketDescriptor() const { - Q_UNIMPLEMENTED(); - return -1; + Q_D(const QNativeSocketEngine); + return d->socketDescriptor; } bool QNativeSocketEngine::isValid() const { - Q_UNIMPLEMENTED(); - return false; + Q_D(const QNativeSocketEngine); + return d->socketDescriptor != -1; } bool QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port) { - Q_UNIMPLEMENTED(); - Q_UNUSED(address); - Q_UNUSED(port); - return false; + const QString addressString = address.toString(); + return connectToHostByName(addressString, port); } bool QNativeSocketEngine::connectToHostByName(const QString &name, quint16 port) { - Q_UNIMPLEMENTED(); - Q_UNUSED(name); - Q_UNUSED(port); - return false; + Q_D(QNativeSocketEngine); + HStringReference hostNameRef(reinterpret_cast(name.utf16())); + ComPtr hostNameFactory; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(), + &hostNameFactory); + ComPtr remoteHost; + if (FAILED(hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost))) { + qWarning("QNativeSocketEnginePrivate::nativeConnect:: Could not create hostname"); + return false; + } + + const QString portString = QString::number(port); + HStringReference portReference(reinterpret_cast(portString.utf16())); + ComPtr action; + HRESULT hr = E_FAIL; + if (d->socketType == QAbstractSocket::TcpSocket) + hr = d->tcp->ConnectAsync(remoteHost.Get(), portReference.Get(), &action); + else if (d->socketType == QAbstractSocket::UdpSocket) + hr = d->udp->ConnectAsync(remoteHost.Get(), portReference.Get(), &action); + if (FAILED(hr)) { + qWarning("QNativeSocketEnginePrivate::nativeConnect:: Could not obtain connect action"); + return false; + } + + action->put_Completed(Callback(&QNativeSocketEnginePrivate::interruptEventDispatcher).Get()); + hr = action->GetResults(); + while ((hr = action->GetResults()) == E_ILLEGAL_METHOD_CALL) + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents); + if (hr == 0x8007274c) { // A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. + d->setError(QAbstractSocket::NetworkError, d->ConnectionTimeOutErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } + if (hr == 0x8007274d) { // No connection could be made because the target machine actively refused it. + d->setError(QAbstractSocket::ConnectionRefusedError, d->ConnectionRefusedErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } + if (FAILED(hr)) { + d->setError(QAbstractSocket::UnknownSocketError, d->UnknownSocketErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } + + if (d->socketType == QAbstractSocket::TcpSocket) { + UINT32 capacity; + hr = d->inputBuffer->get_Capacity(&capacity); + if (FAILED(hr)) + return false; + IInputStream *stream; + hr = d->tcp->get_InputStream(&stream); + if (FAILED(hr)) + return false; + ByteArrayBuffer *buffer = static_cast(d->inputBuffer.Get()); + buffer->setEngine(this); + buffer->setInputStream(stream); + ComPtr> op; + hr = stream->ReadAsync(buffer, capacity, InputStreamOptions_Partial, &op); + if (FAILED(hr)) + return false; + hr = op->put_Completed(Callback(&QNativeSocketEnginePrivate::handleReadyRead).Get()); + if (FAILED(hr)) + return false; + } + d->socketState = QAbstractSocket::ConnectedState; + return true; } bool QNativeSocketEngine::bind(const QHostAddress &address, quint16 port) { - Q_UNIMPLEMENTED(); - Q_UNUSED(address); - Q_UNUSED(port); + Q_D(QNativeSocketEngine); + ComPtr hostAddress; + if (address != QHostAddress::Any && address != QHostAddress::AnyIPv4 && address != QHostAddress::AnyIPv6) { + ComPtr hostNameFactory; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(), + &hostNameFactory); + const QString addressString = address.toString(); + HStringReference addressRef(reinterpret_cast(addressString.utf16())); + hostNameFactory->CreateHostName(addressRef.Get(), &hostAddress); + } + + HRESULT hr; + QString portQString = port ? QString::number(port) : QString(); + HStringReference portString(reinterpret_cast(portQString.utf16())); + + ComPtr op; + if (d->socketType == QAbstractSocket::TcpSocket) { + if (!d->tcpListener + && FAILED(RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_StreamSocketListener).Get(), + &d->tcpListener))) { + qWarning("Failed to create listener"); + return false; + } + + EventRegistrationToken token; + d->tcpListener->add_ConnectionReceived(Callback(d, &QNativeSocketEnginePrivate::handleClientConnection).Get(), &token); + hr = d->tcpListener->BindEndpointAsync(hostAddress.Get(), portString.Get(), &op); + if (FAILED(hr)) { + qWarning("Unable to bind"); // ### Set error message + return false; + } + } else if (d->socketType == QAbstractSocket::UdpSocket) { + hr = d->udp->BindEndpointAsync(hostAddress.Get(), portString.Get(), &op); + if (FAILED(hr)) { + qWarning("unable to bind"); // ### Set error message + return false; + } + } + + if (op) { + // Wait for connection to enter bound state - TODO: timeout, check result + while ((hr = op->GetResults()) == E_ILLEGAL_METHOD_CALL) + QCoreApplication::processEvents(); + + d->socketState = QAbstractSocket::BoundState; + d->fetchConnectionParameters(); + return true; + } + return false; } bool QNativeSocketEngine::listen() { - Q_UNIMPLEMENTED(); + 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); + + if (d->tcpListener && d->socketDescriptor != -1) { + d->socketState = QAbstractSocket::ListeningState; + return true; + } return false; } int QNativeSocketEngine::accept() { - Q_UNIMPLEMENTED(); + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::accept(), -1); + Q_CHECK_STATE(QNativeSocketEngine::accept(), QAbstractSocket::ListeningState, -1); + Q_CHECK_TYPE(QNativeSocketEngine::accept(), QAbstractSocket::TcpSocket, -1); + + if (d->socketDescriptor == -1 || d->pendingConnections.isEmpty()) + return -1; + + // Start processing incoming data + if (d->socketType == QAbstractSocket::TcpSocket) { + IStreamSocket *socket = d->pendingConnections.takeFirst(); + + UINT32 capacity; + d->inputBuffer->get_Capacity(&capacity); + IInputStream *stream; + socket->get_InputStream(&stream); + // TODO: delete buffer and stream on socket close + ByteArrayBuffer *buffer = static_cast(d->inputBuffer.Get()); + buffer->setEngine(this); + buffer->setInputStream(stream); + ComPtr> op; + stream->ReadAsync(buffer, capacity, InputStreamOptions_Partial, &op); + op->put_Completed(Callback(&QNativeSocketEnginePrivate::handleReadyRead).Get()); + d->currentConnections.append(socket); + + SocketHandler *handler = gSocketHandler(); + handler->pendingTcpSockets.insert(++handler->socketCount, socket); + return handler->socketCount; + } + return -1; } void QNativeSocketEngine::close() { + Q_D(QNativeSocketEngine); + if (d->socketDescriptor != -1) { + IClosable *socket = 0; + if (d->socketType == QAbstractSocket::TcpSocket) + d->tcp->QueryInterface(IID_PPV_ARGS(&socket)); + else if (d->socketType == QAbstractSocket::UdpSocket) + d->udp->QueryInterface(IID_PPV_ARGS(&socket)); + + if (socket) { + d->closingDown = true; + socket->Close(); + socket->Release(); + closeNotification(); + d->socketDescriptor = -1; + } + d->socketDescriptor = -1; + } + d->socketState = QAbstractSocket::UnconnectedState; + d->hasSetSocketError = false; + d->localPort = 0; + d->localAddress.clear(); + d->peerPort = 0; + d->peerAddress.clear(); } bool QNativeSocketEngine::joinMulticastGroup(const QHostAddress &groupAddress, const QNetworkInterface &iface) { - Q_UNIMPLEMENTED(); Q_UNUSED(groupAddress); Q_UNUSED(iface); + Q_UNIMPLEMENTED(); return false; } bool QNativeSocketEngine::leaveMulticastGroup(const QHostAddress &groupAddress, const QNetworkInterface &iface) { - Q_UNIMPLEMENTED(); Q_UNUSED(groupAddress); Q_UNUSED(iface); + Q_UNIMPLEMENTED(); return false; } @@ -147,121 +482,258 @@ QNetworkInterface QNativeSocketEngine::multicastInterface() const bool QNativeSocketEngine::setMulticastInterface(const QNetworkInterface &iface) { - Q_UNIMPLEMENTED(); Q_UNUSED(iface); + Q_UNIMPLEMENTED(); return false; } qint64 QNativeSocketEngine::bytesAvailable() const { - Q_UNIMPLEMENTED(); + Q_D(const QNativeSocketEngine); + if (d->socketType != QAbstractSocket::TcpSocket) + return -1; + + if (d->inputBuffer) { + UINT32 len; + d->inputBuffer->get_Length(&len); + return len; + } + return -1; } qint64 QNativeSocketEngine::read(char *data, qint64 maxlen) { - Q_UNIMPLEMENTED(); - Q_UNUSED(data); - Q_UNUSED(maxlen); - return -1; + Q_D(QNativeSocketEngine); + if (d->socketType != QAbstractSocket::TcpSocket) + return -1; + + ComPtr dataReaderStatics; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataReader).Get(), &dataReaderStatics); + ComPtr reader; + + dataReaderStatics->FromBuffer(d->inputBuffer.Get(), &reader); + + UINT32 bufferCapacity; + d->inputBuffer->get_Capacity(&bufferCapacity); + qint64 lengthToRead = maxlen < bufferCapacity ? maxlen : bufferCapacity; + + UINT32 bufferLength; + d->inputBuffer->get_Length(&bufferLength); + + lengthToRead = bufferLength < lengthToRead ? bufferLength : lengthToRead; + reader->ReadBytes(lengthToRead, (unsigned char*)data); + return lengthToRead; +} + +template +static qint64 nativeWrite(T *socket, const char *data, qint64 len) +{ + ComPtr stream; + HRESULT hr = socket->get_OutputStream(&stream); + if (FAILED(hr)) + return -1; + ComPtr buffer = Make(data, len); + ComPtr> op; + hr = stream->WriteAsync(buffer.Get(), &op); + if (FAILED(hr)) + return -1; + UINT32 bytesWritten; + while ((hr = op->GetResults(&bytesWritten)) == E_ILLEGAL_METHOD_CALL) + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + return bytesWritten; } qint64 QNativeSocketEngine::write(const char *data, qint64 len) { - Q_UNIMPLEMENTED(); - Q_UNUSED(data); - Q_UNUSED(len); - return -1; + Q_D(QNativeSocketEngine); + qint64 bytesWritten = -1; + if (d->socketType == QAbstractSocket::TcpSocket) + bytesWritten = ::nativeWrite(d->tcp, data, len); + else if (d->socketType == QAbstractSocket::UdpSocket) + bytesWritten = ::nativeWrite(d->udp, data, len); + if (bytesWritten != -1 && d->notifyOnWrite) + writeNotification(); + return bytesWritten; + } qint64 QNativeSocketEngine::readDatagram(char *data, qint64 maxlen, QHostAddress *addr, quint16 *port) { - Q_UNIMPLEMENTED(); - Q_UNUSED(data); - Q_UNUSED(maxlen); - Q_UNUSED(addr); - Q_UNUSED(port); + Q_D(QNativeSocketEngine); + if (d->socketType != QAbstractSocket::UdpSocket) + return -1; + + QHostAddress returnAddress; + quint16 returnPort; + + for (int i = 0; i < d->pendingDatagrams.size(); ++i) { + IDatagramSocketMessageReceivedEventArgs *arg = d->pendingDatagrams.at(i); + ComPtr remoteHost; + HSTRING remoteHostString; + HSTRING remotePort; + arg->get_RemoteAddress(&remoteHost); + arg->get_RemotePort(&remotePort); + remoteHost->get_CanonicalName(&remoteHostString); + returnAddress.setAddress(qt_QStringFromHSTRING(remoteHostString)); + returnPort = qt_QStringFromHSTRING(remotePort).toInt(); + ComPtr reader; + arg->GetDataReader(&reader); + if (!reader) + continue; + + BYTE buffer[1024]; + reader->ReadBytes(maxlen, buffer); + *addr = returnAddress; + *port = returnPort; + arg = d->pendingDatagrams.takeFirst(); + + // TODO: fill data + Q_UNUSED(data); + arg->Release(); + delete arg; + --i; + return maxlen; + } + return -1; } qint64 QNativeSocketEngine::writeDatagram(const char *data, qint64 len, const QHostAddress &addr, quint16 port) { - Q_UNIMPLEMENTED(); - Q_UNUSED(data); - Q_UNUSED(len); - Q_UNUSED(addr); - Q_UNUSED(port); - return -1; + Q_D(QNativeSocketEngine); + if (d->socketType != QAbstractSocket::UdpSocket) + return -1; + + ComPtr remoteHost; + ComPtr hostNameFactory; + if (FAILED(GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(), + &hostNameFactory))) { + qWarning("QNativeSocketEnginePrivate::nativeSendDatagram: could not obtain hostname factory"); + return -1; + } + const QString addressString = addr.toString(); + HStringReference hostNameRef(reinterpret_cast(addressString.utf16())); + hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost); + + ComPtr> streamOperation; + ComPtr stream; + const QString portString = QString::number(port); + HStringReference portRef(reinterpret_cast(portString.utf16())); + if (FAILED(d->udp->GetOutputStreamAsync(remoteHost.Get(), portRef.Get(), &streamOperation))) + return -1; + HRESULT hr; + while (hr = streamOperation->GetResults(&stream) == E_ILLEGAL_METHOD_CALL) + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + ComPtr dataWriterFactory; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(), &dataWriterFactory); + ComPtr writer; + dataWriterFactory->CreateDataWriter(stream.Get(), &writer); + writer->WriteBytes(len, (unsigned char *)data); + return len; } bool QNativeSocketEngine::hasPendingDatagrams() const { - Q_UNIMPLEMENTED(); - return false; + Q_D(const QNativeSocketEngine); + return d->pendingDatagrams.length() > 0; } qint64 QNativeSocketEngine::pendingDatagramSize() const { - Q_UNIMPLEMENTED(); - return 0; + Q_D(const QNativeSocketEngine); + qint64 ret = 0; + foreach (IDatagramSocketMessageReceivedEventArgs *arg, d->pendingDatagrams) { + ComPtr reader; + UINT32 unconsumedBufferLength; + arg->GetDataReader(&reader); + if (!reader) + return -1; + reader->get_UnconsumedBufferLength(&unconsumedBufferLength); + ret += unconsumedBufferLength; + } + return ret; } qint64 QNativeSocketEngine::bytesToWrite() const { - Q_UNIMPLEMENTED(); return 0; } qint64 QNativeSocketEngine::receiveBufferSize() const { - Q_UNIMPLEMENTED(); - return 0; + Q_D(const QNativeSocketEngine); + return d->option(QAbstractSocketEngine::ReceiveBufferSocketOption); } void QNativeSocketEngine::setReceiveBufferSize(qint64 bufferSize) { - Q_UNIMPLEMENTED(); - Q_UNUSED(bufferSize); + Q_D(QNativeSocketEngine); + d->setOption(QAbstractSocketEngine::ReceiveBufferSocketOption, bufferSize); } qint64 QNativeSocketEngine::sendBufferSize() const { - Q_UNIMPLEMENTED(); - return 0; + Q_D(const QNativeSocketEngine); + return d->option(QAbstractSocketEngine::SendBufferSocketOption); } void QNativeSocketEngine::setSendBufferSize(qint64 bufferSize) { - Q_UNIMPLEMENTED(); - Q_UNUSED(bufferSize); + Q_D(QNativeSocketEngine); + d->setOption(QAbstractSocketEngine::SendBufferSocketOption, bufferSize); } int QNativeSocketEngine::option(QAbstractSocketEngine::SocketOption option) const { - Q_UNIMPLEMENTED(); - Q_UNUSED(option); - return -1; + Q_D(const QNativeSocketEngine); + return d->option(option); } bool QNativeSocketEngine::setOption(QAbstractSocketEngine::SocketOption option, int value) { - Q_UNIMPLEMENTED(); - Q_UNUSED(option); - Q_UNUSED(value); - return false; + Q_D(QNativeSocketEngine); + return d->setOption(option, value); } bool QNativeSocketEngine::waitForRead(int msecs, bool *timedOut) { - Q_UNIMPLEMENTED(); - Q_UNUSED(msecs); - Q_UNUSED(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; + + QElapsedTimer timer; + timer.start(); + while (msecs > timer.elapsed()) { + // Servers with active connections are ready for reading + if (!d->currentConnections.isEmpty()) + return true; + + // If we are a client, we are ready to read if our buffer has data + UINT32 length; + if (FAILED(d->inputBuffer->get_Length(&length))) + return false; + if (length) + return true; + + // Nothing to do, wait for more events + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents|QEventLoop::WaitForMoreEvents); + } + + d->setError(QAbstractSocket::SocketTimeoutError, + QNativeSocketEnginePrivate::TimeOutErrorString); + + if (timedOut) + *timedOut = true; return false; } bool QNativeSocketEngine::waitForWrite(int msecs, bool *timedOut) { - Q_UNIMPLEMENTED(); Q_UNUSED(msecs); Q_UNUSED(timedOut); return false; @@ -269,7 +741,6 @@ bool QNativeSocketEngine::waitForWrite(int msecs, bool *timedOut) bool QNativeSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, bool checkRead, bool checkWrite, int msecs, bool *timedOut) { - Q_UNIMPLEMENTED(); Q_UNUSED(readyToRead); Q_UNUSED(readyToWrite); Q_UNUSED(checkRead); @@ -281,47 +752,431 @@ bool QNativeSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWri bool QNativeSocketEngine::isReadNotificationEnabled() const { - Q_UNIMPLEMENTED(); - return false; + Q_D(const QNativeSocketEngine); + return d->notifyOnRead; } void QNativeSocketEngine::setReadNotificationEnabled(bool enable) { - Q_UNIMPLEMENTED(); - Q_UNUSED(enable); + Q_D(QNativeSocketEngine); + d->notifyOnRead = enable; } bool QNativeSocketEngine::isWriteNotificationEnabled() const { - Q_UNIMPLEMENTED(); - return false; + Q_D(const QNativeSocketEngine); + return d->notifyOnWrite; } void QNativeSocketEngine::setWriteNotificationEnabled(bool enable) { - Q_UNIMPLEMENTED(); - Q_UNUSED(enable); + Q_D(QNativeSocketEngine); + d->notifyOnWrite = enable; + if (enable && d->socketState == QAbstractSocket::ConnectedState) { + if (bytesToWrite()) + return; // will be emitted as a result of bytes written + writeNotification(); + d->notifyOnWrite = false; + } } bool QNativeSocketEngine::isExceptionNotificationEnabled() const { - Q_UNIMPLEMENTED(); - return false; + Q_D(const QNativeSocketEngine); + return d->notifyOnException; } void QNativeSocketEngine::setExceptionNotificationEnabled(bool enable) { - Q_UNIMPLEMENTED(); - Q_UNUSED(enable); + Q_D(QNativeSocketEngine); + d->notifyOnException = enable; +} + +bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol &socketProtocol) +{ + Q_UNUSED(socketProtocol); + SocketHandler *handler = gSocketHandler(); + switch (socketType) { + case QAbstractSocket::TcpSocket: { + if (FAILED(RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_StreamSocket).Get(), + reinterpret_cast(&tcp)))) { + qWarning("Failed to create StreamSocket instance"); + return false; + } + socketDescriptor = ++handler->socketCount; + return true; + } + case QAbstractSocket::UdpSocket: { + if (FAILED(RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_DatagramSocket).Get(), + reinterpret_cast(&udp)))) { + qWarning("Failed to create stream socket"); + return false; + } + EventRegistrationToken token; + udp->add_MessageReceived(Callback(this, &QNativeSocketEnginePrivate::handleNewDatagram).Get(), &token); + socketDescriptor = ++handler->socketCount; + return true; + } + default: + qWarning("Invalid socket type"); + return false; + } + return false; } QNativeSocketEnginePrivate::QNativeSocketEnginePrivate() : QAbstractSocketEnginePrivate() + , notifyOnRead(true) + , notifyOnWrite(true) + , notifyOnException(false) + , closingDown(false) + , socketDescriptor(-1) { + ComPtr buffer = Make(8192); + inputBuffer = buffer; } QNativeSocketEnginePrivate::~QNativeSocketEnginePrivate() { } +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; + // should not happen anymore + 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 TemporaryErrorString: + socketErrorString = QNativeSocketEngine::tr("Temporary error"); + break; + case UnknownSocketErrorString: + socketErrorString = QNativeSocketEngine::tr("Unknown error"); + break; + } +} + +int QNativeSocketEnginePrivate::option(QAbstractSocketEngine::SocketOption opt) const +{ + ComPtr control; + if (socketType == QAbstractSocket::TcpSocket) { + if (FAILED(tcp->get_Control(&control))) { + qWarning("QNativeSocketEnginePrivate::option: Could not obtain socket control"); + return -1; + } + } + switch (opt) { + case QAbstractSocketEngine::NonBlockingSocketOption: + case QAbstractSocketEngine::BroadcastSocketOption: + case QAbstractSocketEngine::ReceiveOutOfBandData: + return 1; + case QAbstractSocketEngine::SendBufferSocketOption: + if (socketType == QAbstractSocket::UdpSocket) + return -1; + + UINT32 bufferSize; + if (FAILED(control->get_OutboundBufferSizeInBytes(&bufferSize))) { + qWarning("Could not obtain OutboundBufferSizeInBytes information vom socket control"); + return -1; + } + return bufferSize; + case QAbstractSocketEngine::LowDelayOption: + if (socketType == QAbstractSocket::UdpSocket) + return -1; + + boolean noDelay; + if (FAILED(control->get_NoDelay(&noDelay))) { + qWarning("Could not obtain NoDelay information from socket control"); + return -1; + } + return noDelay; + case QAbstractSocketEngine::KeepAliveOption: + if (socketType == QAbstractSocket::UdpSocket) + return -1; + + boolean keepAlive; + if (FAILED(control->get_KeepAlive(&keepAlive))) { + qWarning("Could not obtain KeepAlive information from socket control"); + return -1; + } + return keepAlive; + case QAbstractSocketEngine::ReceiveBufferSocketOption: + case QAbstractSocketEngine::AddressReusable: + case QAbstractSocketEngine::BindExclusively: + case QAbstractSocketEngine::MulticastTtlOption: + case QAbstractSocketEngine::MulticastLoopbackOption: + case QAbstractSocketEngine::TypeOfServiceOption: + default: + return -1; + } + return -1; +} + +bool QNativeSocketEnginePrivate::setOption(QAbstractSocketEngine::SocketOption opt, int v) +{ + ComPtr control; + if (socketType == QAbstractSocket::TcpSocket) { + if (FAILED(tcp->get_Control(&control))) { + qWarning("QNativeSocketEnginePrivate::setOption: Could not obtain socket control"); + return false; + } + } + switch (opt) { + case QAbstractSocketEngine::NonBlockingSocketOption: + case QAbstractSocketEngine::BroadcastSocketOption: + case QAbstractSocketEngine::ReceiveOutOfBandData: + return v != 0; + case QAbstractSocketEngine::SendBufferSocketOption: + if (socketType == QAbstractSocket::UdpSocket) + return false; + + if (FAILED(control->put_OutboundBufferSizeInBytes(v))) { + qWarning("Could not set OutboundBufferSizeInBytes"); + return false; + } + return true; + case QAbstractSocketEngine::LowDelayOption: { + if (socketType == QAbstractSocket::UdpSocket) + return false; + + boolean noDelay = v; + if (FAILED(control->put_NoDelay(noDelay))) { + qWarning("Could not obtain NoDelay information from socket control"); + return false; + } + return true; + } + case QAbstractSocketEngine::KeepAliveOption: { + if (socketType == QAbstractSocket::UdpSocket) + return false; + + boolean keepAlive = v; + if (FAILED(control->put_KeepAlive(keepAlive))) { + qWarning("Could not set KeepAlive value"); + return false; + } + return true; + } + case QAbstractSocketEngine::ReceiveBufferSocketOption: + case QAbstractSocketEngine::AddressReusable: + case QAbstractSocketEngine::BindExclusively: + case QAbstractSocketEngine::MulticastTtlOption: + case QAbstractSocketEngine::MulticastLoopbackOption: + case QAbstractSocketEngine::TypeOfServiceOption: + default: + return false; + } + return false; +} + +bool QNativeSocketEnginePrivate::fetchConnectionParameters() +{ + localPort = 0; + localAddress.clear(); + peerPort = 0; + peerAddress.clear(); + + if (socketType == QAbstractSocket::TcpSocket) { + ComPtr hostName; + HSTRING tmpHString; + ComPtr info; + if (FAILED(tcp->get_Information(&info))) { + qWarning("QNativeSocketEnginePrivate::fetchConnectionParameters: Could not obtain socket info"); + return false; + } + info->get_LocalAddress(&hostName); + if (hostName) { + hostName->get_CanonicalName(&tmpHString); + localAddress.setAddress(qt_QStringFromHSTRING(tmpHString)); + info->get_LocalPort(&tmpHString); + localPort = qt_QStringFromHSTRING(tmpHString).toInt(); + } + if (!localPort && tcpListener) { + ComPtr listenerInfo = 0; + tcpListener->get_Information(&listenerInfo); + listenerInfo->get_LocalPort(&tmpHString); + localPort = qt_QStringFromHSTRING(tmpHString).toInt(); + localAddress == QHostAddress::Any; + } + info->get_RemoteAddress(&hostName); + if (hostName) { + hostName->get_CanonicalName(&tmpHString); + peerAddress.setAddress(qt_QStringFromHSTRING(tmpHString)); + info->get_RemotePort(&tmpHString); + peerPort = qt_QStringFromHSTRING(tmpHString).toInt(); + } + } else if (socketType == QAbstractSocket::UdpSocket) { + ComPtr hostName; + HSTRING tmpHString; + ComPtr info; + if (FAILED(udp->get_Information(&info))) { + qWarning("QNativeSocketEnginePrivate::fetchConnectionParameters: Could not obtain socket information"); + return false; + } + info->get_LocalAddress(&hostName); + if (hostName) { + hostName->get_CanonicalName(&tmpHString); + localAddress.setAddress(qt_QStringFromHSTRING(tmpHString)); + info->get_LocalPort(&tmpHString); + localPort = qt_QStringFromHSTRING(tmpHString).toInt(); + } + + info->get_RemoteAddress(&hostName); + if (hostName) { + hostName->get_CanonicalName(&tmpHString); + peerAddress.setAddress(qt_QStringFromHSTRING(tmpHString)); + info->get_RemotePort(&tmpHString); + peerPort = qt_QStringFromHSTRING(tmpHString).toInt(); + } + } + return true; +} + +HRESULT QNativeSocketEnginePrivate::handleClientConnection(IStreamSocketListener *listener, IStreamSocketListenerConnectionReceivedEventArgs *args) +{ + Q_Q(QNativeSocketEngine); + Q_ASSERT(tcpListener.Get() == listener); + IStreamSocket *socket; + args->get_Socket(&socket); + pendingConnections.append(socket); + q->connectionNotification(); + q->readNotification(); + return interruptEventDispatcher(0, Completed); +} + +HRESULT QNativeSocketEnginePrivate::interruptEventDispatcher(IAsyncAction *, AsyncStatus) +{ + if (QThread *thread = QThread::currentThread()) { + if (QAbstractEventDispatcher *dispatcher = thread->eventDispatcher()) + dispatcher->interrupt(); + } + return S_OK; +} + +HRESULT QNativeSocketEnginePrivate::handleReadyRead(IAsyncOperationWithProgress *asyncInfo, AsyncStatus) +{ + ByteArrayBuffer *buffer = 0; + HRESULT hr = asyncInfo->GetResults((IBuffer **)&buffer); + if (FAILED(hr)) + return hr; + UINT32 len; + buffer->get_Length(&len); + QNativeSocketEngine *q = buffer->engine(); + if (!q) + return S_OK; + if (len > 0 && q->isReadNotificationEnabled()) { + q->readNotification(); + } + + // Continue reading ### TODO: read into offset!!! + UINT32 capacity; + buffer->get_Capacity(&capacity); + ComPtr> op; + if (SUCCEEDED(buffer->inputStream()->ReadAsync(buffer, capacity, InputStreamOptions_Partial, &op))) { + if (q) + return op->put_Completed(Callback(&QNativeSocketEnginePrivate::handleReadyRead).Get()); + else + return op->put_Completed(nullptr); + } + + return E_FAIL; +} + +HRESULT QNativeSocketEnginePrivate::handleNewDatagram(IDatagramSocket *socket, IDatagramSocketMessageReceivedEventArgs *args) +{ + Q_Q(QNativeSocketEngine); + Q_ASSERT(udp == socket); + pendingDatagrams.append(args); + q->readNotification(); + + return S_OK; +} + QT_END_NAMESPACE diff --git a/src/network/socket/qnativesocketengine_winrt_p.h b/src/network/socket/qnativesocketengine_winrt_p.h index 47ba3ecf91..b5be5fa830 100644 --- a/src/network/socket/qnativesocketengine_winrt_p.h +++ b/src/network/socket/qnativesocketengine_winrt_p.h @@ -138,6 +138,70 @@ class QNativeSocketEnginePrivate : public QAbstractSocketEnginePrivate public: QNativeSocketEnginePrivate(); ~QNativeSocketEnginePrivate(); + + qintptr socketDescriptor; + + bool notifyOnRead, notifyOnWrite, notifyOnException; + bool closingDown; + + 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, + TemporaryErrorString, + + 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 checkProxy(const QHostAddress &address); + bool fetchConnectionParameters(); +private: + union { + ABI::Windows::Networking::Sockets::IStreamSocket *tcp; + ABI::Windows::Networking::Sockets::IDatagramSocket *udp; + }; + Microsoft::WRL::ComPtr tcpListener; + Microsoft::WRL::ComPtr inputBuffer; + QList pendingDatagrams; + QList pendingConnections; + QList currentConnections; + + HRESULT handleNewDatagram(ABI::Windows::Networking::Sockets::IDatagramSocket *socket, + ABI::Windows::Networking::Sockets::IDatagramSocketMessageReceivedEventArgs *args); + HRESULT handleClientConnection(ABI::Windows::Networking::Sockets::IStreamSocketListener *tcpListener, + ABI::Windows::Networking::Sockets::IStreamSocketListenerConnectionReceivedEventArgs *args); + static HRESULT interruptEventDispatcher(ABI::Windows::Foundation::IAsyncAction *, ABI::Windows::Foundation::AsyncStatus); + static HRESULT handleReadyRead(ABI::Windows::Foundation::IAsyncOperationWithProgress *asyncInfo, ABI::Windows::Foundation::AsyncStatus); }; QT_END_NAMESPACE -- cgit v1.2.3