blob: 2eb2141feefdca7a2e59facd3f8e63bb5c065420 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qt_windows.h>
#include "qnativesocketengine_winrt_p.h"
#include <qcoreapplication.h>
#include <qabstracteventdispatcher.h>
#include <qsocketnotifier.h>
#include <qdatetime.h>
#include <qnetworkinterface.h>
#include <qelapsedtimer.h>
#include <qthread.h>
#include <qabstracteventdispatcher.h>
#include <qfunctions_winrt.h>
#include <private/qthread_p.h>
#include <private/qabstractsocket_p.h>
#include <private/qeventdispatcher_winrt_p.h>
#ifndef QT_NO_SSL
#include <QSslSocket>
#endif
#include <functional>
#include <wrl.h>
#include <windows.foundation.collections.h>
#include <windows.storage.streams.h>
#include <windows.networking.h>
#include <windows.networking.sockets.h>
#include <robuffer.h>
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;
#if _MSC_VER >= 1900
using namespace ABI::Windows::Security::EnterpriseData;
#endif
typedef ITypedEventHandler<StreamSocketListener *, StreamSocketListenerConnectionReceivedEventArgs *> ClientConnectedHandler;
typedef ITypedEventHandler<DatagramSocket *, DatagramSocketMessageReceivedEventArgs *> DatagramReceivedHandler;
typedef IAsyncOperationWithProgressCompletedHandler<IBuffer *, UINT32> SocketReadCompletedHandler;
typedef IAsyncOperationWithProgressCompletedHandler<UINT32, UINT32> SocketWriteCompletedHandler;
typedef IAsyncOperationWithProgress<IBuffer *, UINT32> IAsyncBufferOperation;
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcNetworkSocket, "qt.network.socket");
Q_LOGGING_CATEGORY(lcNetworkSocketVerbose, "qt.network.socket.verbose");
#if _MSC_VER >= 1900
static HRESULT qt_winrt_try_create_thread_network_context(QString host, ComPtr<IThreadNetworkContext> &context)
{
HRESULT hr;
ComPtr<IProtectionPolicyManagerStatics> protectionPolicyManager;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Security_EnterpriseData_ProtectionPolicyManager).Get(),
&protectionPolicyManager);
RETURN_HR_IF_FAILED("Could not access ProtectionPolicyManager statics.");
ComPtr<IHostNameFactory> hostNameFactory;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(),
&hostNameFactory);
RETURN_HR_IF_FAILED("Could not access HostName factory.");
ComPtr<IHostName> hostName;
HStringReference hostRef(reinterpret_cast<LPCWSTR>(host.utf16()), host.length());
hr = hostNameFactory->CreateHostName(hostRef.Get(), &hostName);
RETURN_HR_IF_FAILED("Could not create hostname.");
ComPtr<IAsyncOperation<HSTRING>> op;
hr = protectionPolicyManager->GetPrimaryManagedIdentityForNetworkEndpointAsync(hostName.Get(), &op);
RETURN_HR_IF_FAILED("Could not get identity operation.");
HSTRING hIdentity;
hr = QWinRTFunctions::await(op, &hIdentity);
RETURN_HR_IF_FAILED("Could not wait for identity operation.");
// Implies there is no need for a network context for this address
if (hIdentity == nullptr)
return S_OK;
hr = protectionPolicyManager->CreateCurrentThreadNetworkContext(hIdentity, &context);
RETURN_HR_IF_FAILED("Could not create thread network context");
return S_OK;
}
#endif // _MSC_VER >= 1900
typedef QHash<qintptr, IStreamSocket *> TcpSocketHash;
struct SocketHandler
{
SocketHandler() : socketCount(0) {}
qintptr socketCount;
TcpSocketHash pendingTcpSockets;
};
Q_GLOBAL_STATIC(SocketHandler, gSocketHandler)
struct SocketGlobal
{
SocketGlobal()
{
HRESULT hr;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(),
&bufferFactory);
Q_ASSERT_SUCCEEDED(hr);
}
ComPtr<IBufferFactory> bufferFactory;
};
Q_GLOBAL_STATIC(SocketGlobal, g)
#define READ_BUFFER_SIZE 65536
static inline QString qt_QStringFromHString(const HString &string)
{
UINT32 length;
PCWSTR rawString = string.GetRawBuffer(&length);
return QString::fromWCharArray(rawString, length);
}
class SocketEngineWorker : public QObject
{
Q_OBJECT
public:
SocketEngineWorker(QNativeSocketEnginePrivate *engine)
: enginePrivate(engine)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << engine;
}
~SocketEngineWorker()
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
if (Q_UNLIKELY(initialReadOp)) {
qCDebug(lcNetworkSocket) << Q_FUNC_INFO << "Closing initial read operation";
ComPtr<IAsyncInfo> info;
HRESULT hr = initialReadOp.As(&info);
Q_ASSERT_SUCCEEDED(hr);
if (info) {
hr = info->Cancel();
Q_ASSERT_SUCCEEDED(hr);
hr = info->Close();
Q_ASSERT_SUCCEEDED(hr);
}
}
if (readOp) {
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << "Closing read operation";
ComPtr<IAsyncInfo> info;
HRESULT hr = readOp.As(&info);
Q_ASSERT_SUCCEEDED(hr);
if (info) {
hr = info->Cancel();
Q_ASSERT_SUCCEEDED(hr);
hr = info->Close();
Q_ASSERT_SUCCEEDED(hr);
}
}
if (connectOp) {
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << "Closing connect operation";
ComPtr<IAsyncInfo> info;
HRESULT hr = connectOp.As(&info);
Q_ASSERT_SUCCEEDED(hr);
if (info) {
hr = info->Cancel();
Q_ASSERT_SUCCEEDED(hr);
hr = info->Close();
Q_ASSERT_SUCCEEDED(hr);
}
}
}
signals:
void connectOpFinished(bool success, QAbstractSocket::SocketError error, WinRTSocketEngine::ErrorString errorString);
void newDataReceived();
void socketErrorOccured(QAbstractSocket::SocketError error);
public:
void startReading()
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
ComPtr<IBuffer> buffer;
HRESULT hr = g->bufferFactory->Create(READ_BUFFER_SIZE, &buffer);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IInputStream> stream;
hr = tcpSocket->get_InputStream(&stream);
Q_ASSERT_SUCCEEDED(hr);
hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, initialReadOp.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
enginePrivate->socketState = QAbstractSocket::ConnectedState;
hr = initialReadOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &SocketEngineWorker::onReadyRead).Get());
Q_ASSERT_SUCCEEDED(hr);
}
HRESULT onConnectOpFinished(IAsyncAction *action, AsyncStatus)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
HRESULT hr = action->GetResults();
if (FAILED(hr)) {
if (hr == HRESULT_FROM_WIN32(WSAETIMEDOUT)) {
emit connectOpFinished(false, QAbstractSocket::NetworkError, WinRTSocketEngine::ConnectionTimeOutErrorString);
return S_OK;
} else if (hr == HRESULT_FROM_WIN32(WSAEHOSTUNREACH)) {
emit connectOpFinished(false, QAbstractSocket::HostNotFoundError, WinRTSocketEngine::HostUnreachableErrorString);
return S_OK;
} else if (hr == HRESULT_FROM_WIN32(WSAECONNREFUSED)) {
emit connectOpFinished(false, QAbstractSocket::ConnectionRefusedError, WinRTSocketEngine::ConnectionRefusedErrorString);
return S_OK;
} else {
emit connectOpFinished(false, QAbstractSocket::UnknownSocketError, WinRTSocketEngine::UnknownSocketErrorString);
return S_OK;
}
}
// The callback might be triggered several times if we do not cancel/reset it here
if (connectOp) {
ComPtr<IAsyncInfo> info;
hr = connectOp.As(&info);
Q_ASSERT_SUCCEEDED(hr);
if (info) {
hr = info->Cancel();
Q_ASSERT_SUCCEEDED(hr);
hr = info->Close();
Q_ASSERT_SUCCEEDED(hr);
}
hr = connectOp.Reset();
Q_ASSERT_SUCCEEDED(hr);
}
emit connectOpFinished(true, QAbstractSocket::UnknownSocketError, WinRTSocketEngine::UnknownSocketErrorString);
return S_OK;
}
HRESULT OnNewDatagramReceived(IDatagramSocket *, IDatagramSocketMessageReceivedEventArgs *args)
{
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO;
WinRtDatagram datagram;
QHostAddress returnAddress;
ComPtr<IHostName> remoteHost;
HRESULT hr = args->get_RemoteAddress(&remoteHost);
RETURN_OK_IF_FAILED("Could not obtain remote host");
HString remoteHostString;
hr = remoteHost->get_CanonicalName(remoteHostString.GetAddressOf());
RETURN_OK_IF_FAILED("Could not obtain remote host's canonical name");
returnAddress.setAddress(qt_QStringFromHString(remoteHostString));
datagram.header.senderAddress = returnAddress;
HString remotePort;
hr = args->get_RemotePort(remotePort.GetAddressOf());
RETURN_OK_IF_FAILED("Could not obtain remote port");
datagram.header.senderPort = qt_QStringFromHString(remotePort).toInt();
ComPtr<IDataReader> reader;
hr = args->GetDataReader(&reader);
RETURN_OK_IF_FAILED("Could not obtain data reader");
quint32 length;
hr = reader->get_UnconsumedBufferLength(&length);
RETURN_OK_IF_FAILED("Could not obtain unconsumed buffer length");
datagram.data.resize(length);
hr = reader->ReadBytes(length, reinterpret_cast<BYTE *>(datagram.data.data()));
RETURN_OK_IF_FAILED("Could not read datagram");
QMutexLocker locker(&mutex);
// Notify the engine about new datagrams being present at the next event loop iteration
if (emitDataReceived)
emit newDataReceived();
pendingDatagrams << datagram;
return S_OK;
}
HRESULT onReadyRead(IAsyncBufferOperation *asyncInfo, AsyncStatus status)
{
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO;
if (asyncInfo == initialReadOp.Get()) {
initialReadOp.Reset();
} else if (asyncInfo == readOp.Get()) {
readOp.Reset();
} else {
Q_ASSERT(false);
}
// A read in UnconnectedState will close the socket and return -1 and thus tell the caller,
// that the connection was closed. The socket cannot be closed here, as the subsequent read
// might fail then.
if (status == Error || status == Canceled) {
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << "Remote host closed";
emit socketErrorOccured(QAbstractSocket::RemoteHostClosedError);
return S_OK;
}
ComPtr<IBuffer> buffer;
HRESULT hr = asyncInfo->GetResults(&buffer);
if (FAILED(hr)) {
qErrnoWarning(hr, "Failed to get read results buffer");
emit socketErrorOccured(QAbstractSocket::UnknownSocketError);
return S_OK;
}
UINT32 bufferLength;
hr = buffer->get_Length(&bufferLength);
if (FAILED(hr)) {
qErrnoWarning(hr, "Failed to get buffer length");
emit socketErrorOccured(QAbstractSocket::UnknownSocketError);
return S_OK;
}
// A zero sized buffer length signals, that the remote host closed the connection. The socket
// cannot be closed though, as the following read might have socket descriptor -1 and thus and
// the closing of the socket won't be communicated to the caller. So only the error is set. The
// actual socket close happens inside of read.
if (!bufferLength) {
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << "Remote host closed";
emit socketErrorOccured(QAbstractSocket::RemoteHostClosedError);
return S_OK;
}
ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteArrayAccess;
hr = buffer.As(&byteArrayAccess);
if (FAILED(hr)) {
qErrnoWarning(hr, "Failed to get cast buffer");
emit socketErrorOccured(QAbstractSocket::UnknownSocketError);
return S_OK;
}
byte *data;
hr = byteArrayAccess->Buffer(&data);
if (FAILED(hr)) {
qErrnoWarning(hr, "Failed to access buffer data");
emit socketErrorOccured(QAbstractSocket::UnknownSocketError);
return S_OK;
}
QByteArray newData(reinterpret_cast<const char*>(data), qint64(bufferLength));
QMutexLocker readLocker(&mutex);
emit newDataReceived();
pendingData.append(newData);
readLocker.unlock();
hr = QEventDispatcherWinRT::runOnXamlThread([buffer, this]() {
UINT32 readBufferLength;
ComPtr<IInputStream> stream;
HRESULT hr = tcpSocket->get_InputStream(&stream);
if (FAILED(hr)) {
qErrnoWarning(hr, "Failed to obtain input stream");
emit socketErrorOccured(QAbstractSocket::UnknownSocketError);
return S_OK;
}
// Reuse the stream buffer
hr = buffer->get_Capacity(&readBufferLength);
if (FAILED(hr)) {
qErrnoWarning(hr, "Failed to get buffer capacity");
emit socketErrorOccured(QAbstractSocket::UnknownSocketError);
return S_OK;
}
hr = buffer->put_Length(0);
if (FAILED(hr)) {
qErrnoWarning(hr, "Failed to set buffer length");
emit socketErrorOccured(QAbstractSocket::UnknownSocketError);
return S_OK;
}
hr = stream->ReadAsync(buffer.Get(), readBufferLength, InputStreamOptions_Partial, &readOp);
if (FAILED(hr)) {
qErrnoWarning(hr, "onReadyRead(): Could not read into socket stream buffer.");
emit socketErrorOccured(QAbstractSocket::UnknownSocketError);
return S_OK;
}
hr = readOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &SocketEngineWorker::onReadyRead).Get());
if (FAILED(hr)) {
qErrnoWarning(hr, "onReadyRead(): Failed to set socket read callback.");
emit socketErrorOccured(QAbstractSocket::UnknownSocketError);
return S_OK;
}
return S_OK;
});
Q_ASSERT_SUCCEEDED(hr);
return S_OK;
}
void setTcpSocket(ComPtr<IStreamSocket> socket) { tcpSocket = socket; }
private:
friend class QNativeSocketEngine;
ComPtr<IStreamSocket> tcpSocket;
QList<WinRtDatagram> pendingDatagrams;
bool emitDataReceived = true;
QByteArray pendingData;
// Protects pendingData/pendingDatagrams which are accessed from native callbacks
QMutex mutex;
ComPtr<IAsyncAction> connectOp;
ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> initialReadOp;
ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> readOp;
QNativeSocketEnginePrivate *enginePrivate;
};
static QByteArray socketDescription(const QAbstractSocketEngine *s)
{
QByteArray result;
if (const QObject *o = s->parent()) {
const QString name = o->objectName();
if (!name.isEmpty()) {
result += '"';
result += name.toLocal8Bit();
result += "\"/";
}
result += o->metaObject()->className();
}
return result;
}
// 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_STATES3(function, state1, state2, state3, returnValue) do { \
if (d->socketState != (state1) && d->socketState != (state2) && d->socketState != (state3)) { \
qWarning(""#function" was called" \
" not in "#state1", "#state2" or "#state3); \
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)
template <typename T>
static AsyncStatus opStatus(const ComPtr<T> &op)
{
ComPtr<IAsyncInfo> info;
HRESULT hr = op.As(&info);
Q_ASSERT_SUCCEEDED(hr);
AsyncStatus status;
hr = info->get_Status(&status);
Q_ASSERT_SUCCEEDED(hr);
return status;
}
static qint64 writeIOStream(ComPtr<IOutputStream> stream, const char *data, qint64 len)
{
qCDebug(lcNetworkSocket) << Q_FUNC_INFO << data << len;
ComPtr<IBuffer> buffer;
HRESULT hr = g->bufferFactory->Create(len, &buffer);
Q_ASSERT_SUCCEEDED(hr);
hr = buffer->put_Length(len);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteArrayAccess;
hr = buffer.As(&byteArrayAccess);
Q_ASSERT_SUCCEEDED(hr);
byte *bytes;
hr = byteArrayAccess->Buffer(&bytes);
Q_ASSERT_SUCCEEDED(hr);
memcpy(bytes, data, len);
ComPtr<IAsyncOperationWithProgress<UINT32, UINT32>> op;
hr = stream->WriteAsync(buffer.Get(), &op);
RETURN_IF_FAILED("Failed to write to stream", return -1);
UINT32 bytesWritten;
hr = QWinRTFunctions::await(op, &bytesWritten);
RETURN_IF_FAILED("Failed to write to stream", return -1);
return bytesWritten;
}
QNativeSocketEngine::QNativeSocketEngine(QObject *parent)
: QAbstractSocketEngine(*new QNativeSocketEnginePrivate(), parent)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << parent;
qRegisterMetaType<WinRtDatagram>();
qRegisterMetaType<WinRTSocketEngine::ErrorString>();
Q_D(QNativeSocketEngine);
#ifndef QT_NO_SSL
if (parent)
d->sslSocket = qobject_cast<QSslSocket *>(parent->parent());
#endif
connect(this, &QNativeSocketEngine::connectionReady,
this, &QNativeSocketEngine::connectionNotification, Qt::QueuedConnection);
connect(this, &QNativeSocketEngine::readReady,
this, &QNativeSocketEngine::processReadReady, Qt::QueuedConnection);
connect(this, &QNativeSocketEngine::writeReady,
this, &QNativeSocketEngine::writeNotification, Qt::QueuedConnection);
connect(d->worker, &SocketEngineWorker::connectOpFinished,
this, &QNativeSocketEngine::handleConnectOpFinished, Qt::QueuedConnection);
connect(d->worker, &SocketEngineWorker::newDataReceived, this, &QNativeSocketEngine::handleNewData, Qt::QueuedConnection);
connect(d->worker, &SocketEngineWorker::socketErrorOccured,
this, &QNativeSocketEngine::handleTcpError, Qt::QueuedConnection);
}
QNativeSocketEngine::~QNativeSocketEngine()
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
close();
}
bool QNativeSocketEngine::initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << type << protocol;
Q_D(QNativeSocketEngine);
if (isValid())
close();
// Create the socket
if (!d->createNewSocket(type, protocol))
return false;
if (type == QAbstractSocket::UdpSocket) {
// Set the broadcasting flag if it's a UDP socket.
if (!setOption(BroadcastSocketOption, 1)) {
d->setError(QAbstractSocket::UnsupportedSocketOperationError,
WinRTSocketEngine::BroadcastingInitFailedErrorString);
close();
return false;
}
// Set some extra flags that are interesting to us, but accept failure
setOption(ReceivePacketInformation, 1);
setOption(ReceiveHopLimit, 1);
}
// Make sure we receive out-of-band data
if (type == QAbstractSocket::TcpSocket
&& !setOption(ReceiveOutOfBandData, 1)) {
qWarning("QNativeSocketEngine::initialize unable to inline out-of-band data");
}
d->socketType = type;
d->socketProtocol = protocol;
return true;
}
bool QNativeSocketEngine::initialize(qintptr socketDescriptor, QAbstractSocket::SocketState socketState)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << socketDescriptor << socketState;
Q_D(QNativeSocketEngine);
if (isValid())
close();
// Currently, only TCP sockets are initialized this way.
IStreamSocket *socket = gSocketHandler->pendingTcpSockets.take(socketDescriptor);
d->socketDescriptor = qintptr(socket);
d->socketType = QAbstractSocket::TcpSocket;
if (!d->socketDescriptor || !d->fetchConnectionParameters()) {
d->setError(QAbstractSocket::UnsupportedSocketOperationError,
WinRTSocketEngine::InvalidSocketErrorString);
d->socketDescriptor = -1;
return false;
}
// Start processing incoming data
if (d->socketType == QAbstractSocket::TcpSocket) {
HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([d, socket, this]() {
d->worker->setTcpSocket(socket);
d->worker->startReading();
return S_OK;
});
if (FAILED(hr))
return false;
} else {
d->socketState = socketState;
}
return true;
}
qintptr QNativeSocketEngine::socketDescriptor() const
{
Q_D(const QNativeSocketEngine);
return d->socketDescriptor;
}
bool QNativeSocketEngine::isValid() const
{
Q_D(const QNativeSocketEngine);
return d->socketDescriptor != -1;
}
bool QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << address << port;
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::connectToHost(), false);
Q_CHECK_STATES3(QNativeSocketEngine::connectToHost(), QAbstractSocket::BoundState,
QAbstractSocket::UnconnectedState, QAbstractSocket::ConnectingState, false);
const QString addressString = address.toString();
return connectToHostByName(addressString, port);
}
bool QNativeSocketEngine::connectToHostByName(const QString &name, quint16 port)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << name << port;
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::connectToHostByName(), false);
Q_CHECK_STATES3(QNativeSocketEngine::connectToHostByName(), QAbstractSocket::BoundState,
QAbstractSocket::UnconnectedState, QAbstractSocket::ConnectingState, false);
HRESULT hr;
#if _MSC_VER >= 1900
ComPtr<IThreadNetworkContext> networkContext;
if (!qEnvironmentVariableIsEmpty("QT_WINRT_USE_THREAD_NETWORK_CONTEXT")) {
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << "Creating network context";
hr = qt_winrt_try_create_thread_network_context(name, networkContext);
if (FAILED(hr)) {
setError(QAbstractSocket::ConnectionRefusedError, QLatin1String("Could not create thread network context."));
d->socketState = QAbstractSocket::ConnectedState;
return true;
}
}
#endif // _MSC_VER >= 1900
HStringReference hostNameRef(reinterpret_cast<LPCWSTR>(name.utf16()));
ComPtr<IHostNameFactory> hostNameFactory;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(),
&hostNameFactory);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IHostName> remoteHost;
hr = hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost);
RETURN_FALSE_IF_FAILED("QNativeSocketEngine::connectToHostByName: Could not create hostname.");
const QString portString = QString::number(port);
HStringReference portReference(reinterpret_cast<LPCWSTR>(portString.utf16()));
if (d->socketType == QAbstractSocket::TcpSocket)
hr = d->tcpSocket()->ConnectAsync(remoteHost.Get(), portReference.Get(), &d->worker->connectOp);
else if (d->socketType == QAbstractSocket::UdpSocket)
hr = d->udpSocket()->ConnectAsync(remoteHost.Get(), portReference.Get(), &d->worker->connectOp);
if (hr == E_ACCESSDENIED) {
qErrnoWarning(hr, "QNativeSocketEngine::connectToHostByName: Unable to connect to host (%s:%hu/%s). "
"Please check your manifest capabilities.",
qPrintable(name), port, socketDescription(this).constData());
return false;
}
Q_ASSERT_SUCCEEDED(hr);
#if _MSC_VER >= 1900
if (networkContext != nullptr) {
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << "Closing network context";
ComPtr<IClosable> networkContextCloser;
hr = networkContext.As(&networkContextCloser);
Q_ASSERT_SUCCEEDED(hr);
hr = networkContextCloser->Close();
Q_ASSERT_SUCCEEDED(hr);
}
#endif // _MSC_VER >= 1900
d->socketState = QAbstractSocket::ConnectingState;
QEventDispatcherWinRT::runOnXamlThread([d, &hr]() {
hr = d->worker->connectOp->put_Completed(Callback<IAsyncActionCompletedHandler>(
d->worker, &SocketEngineWorker::onConnectOpFinished).Get());
RETURN_OK_IF_FAILED("connectToHostByName: Could not register \"connectOp\" callback");
return S_OK;
});
if (FAILED(hr))
return false;
return d->socketState == QAbstractSocket::ConnectedState;
}
bool QNativeSocketEngine::bind(const QHostAddress &address, quint16 port)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << address << port;
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::bind(), false);
Q_CHECK_STATE(QNativeSocketEngine::bind(), QAbstractSocket::UnconnectedState, false);
HRESULT hr;
// runOnXamlThread may only return S_OK (will assert otherwise) so no need to check its result.
// hr is set inside the lambda though. If an error occurred hr will point that out.
bool specificErrorSet = false;
QEventDispatcherWinRT::runOnXamlThread([address, d, &hr, port, &specificErrorSet, this]() {
ComPtr<IHostName> hostAddress;
if (address != QHostAddress::Any && address != QHostAddress::AnyIPv4 && address != QHostAddress::AnyIPv6) {
ComPtr<IHostNameFactory> hostNameFactory;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(),
&hostNameFactory);
RETURN_OK_IF_FAILED("QNativeSocketEngine::bind: Could not obtain hostname factory");
const QString addressString = address.toString();
HStringReference addressRef(reinterpret_cast<LPCWSTR>(addressString.utf16()));
hr = hostNameFactory->CreateHostName(addressRef.Get(), &hostAddress);
RETURN_OK_IF_FAILED("QNativeSocketEngine::bind: Could not create hostname.");
}
QString portQString = port ? QString::number(port) : QString();
HStringReference portString(reinterpret_cast<LPCWSTR>(portQString.utf16()));
ComPtr<IAsyncAction> op;
if (d->socketType == QAbstractSocket::TcpSocket) {
if (!d->tcpListener) {
hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_StreamSocketListener).Get(),
&d->tcpListener);
RETURN_OK_IF_FAILED("QNativeSocketEngine::bind: Could not create tcp listener");
}
hr = d->tcpListener->add_ConnectionReceived(
Callback<ClientConnectedHandler>(d, &QNativeSocketEnginePrivate::handleClientConnection).Get(),
&d->connectionToken);
RETURN_OK_IF_FAILED("QNativeSocketEngine::bind: Could not register client connection callback");
hr = d->tcpListener->BindEndpointAsync(hostAddress.Get(), portString.Get(), &op);
} else if (d->socketType == QAbstractSocket::UdpSocket) {
hr = d->udpSocket()->BindEndpointAsync(hostAddress.Get(), portString.Get(), &op);
}
if (hr == E_ACCESSDENIED) {
qErrnoWarning(hr, "Unable to bind socket (%s:%hu/%s). Please check your manifest capabilities.",
qPrintable(address.toString()), port, socketDescription(this).constData());
d->setError(QAbstractSocket::SocketAccessError,
WinRTSocketEngine::AccessErrorString);
d->socketState = QAbstractSocket::UnconnectedState;
specificErrorSet = true;
return S_OK;
}
RETURN_OK_IF_FAILED("QNativeSocketEngine::bind: Unable to bind socket");
hr = QWinRTFunctions::await(op);
if (hr == 0x80072741) { // The requested address is not valid in its context
d->setError(QAbstractSocket::SocketAddressNotAvailableError,
WinRTSocketEngine::AddressNotAvailableErrorString);
d->socketState = QAbstractSocket::UnconnectedState;
specificErrorSet = true;
return S_OK;
// Only one usage of each socket address (protocol/network address/port) is normally permitted
} else if (hr == 0x80072740) {
d->setError(QAbstractSocket::AddressInUseError,
WinRTSocketEngine::AddressInuseErrorString);
d->socketState = QAbstractSocket::UnconnectedState;
specificErrorSet = true;
return S_OK;
}
RETURN_OK_IF_FAILED("QNativeSocketEngine::bind: Could not wait for bind to finish");
return S_OK;
});
if (FAILED(hr)) {
if (!specificErrorSet) {
d->setError(QAbstractSocket::UnknownSocketError,
WinRTSocketEngine::UnknownSocketErrorString);
d->socketState = QAbstractSocket::UnconnectedState;
}
return false;
}
d->socketState = QAbstractSocket::BoundState;
return d->fetchConnectionParameters();
}
bool QNativeSocketEngine::listen()
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::listen(), false);
Q_CHECK_STATE(QNativeSocketEngine::listen(), QAbstractSocket::BoundState, false);
#if QT_CONFIG(sctp)
Q_CHECK_TYPES(QNativeSocketEngine::listen(), QAbstractSocket::TcpSocket,
QAbstractSocket::SctpSocket, false);
#else
Q_CHECK_TYPE(QNativeSocketEngine::listen(), QAbstractSocket::TcpSocket, false);
#endif
if (d->tcpListener && d->socketDescriptor != -1) {
d->socketState = QAbstractSocket::ListeningState;
return true;
}
return false;
}
int QNativeSocketEngine::accept()
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::accept(), -1);
Q_CHECK_STATE(QNativeSocketEngine::accept(), QAbstractSocket::ListeningState, -1);
#if QT_CONFIG(sctp)
Q_CHECK_TYPES(QNativeSocketEngine::accept(), QAbstractSocket::TcpSocket,
QAbstractSocket::SctpSocket, -1);
#else
Q_CHECK_TYPE(QNativeSocketEngine::accept(), QAbstractSocket::TcpSocket, -1);
#endif
if (d->socketDescriptor == -1 || d->pendingConnections.isEmpty()) {
d->setError(QAbstractSocket::TemporaryError, WinRTSocketEngine::TemporaryErrorString);
return -1;
}
if (d->socketType == QAbstractSocket::TcpSocket) {
IStreamSocket *socket = d->pendingConnections.takeFirst();
SocketHandler *handler = gSocketHandler();
handler->pendingTcpSockets.insert(++handler->socketCount, socket);
return handler->socketCount;
}
return -1;
}
void QNativeSocketEngine::close()
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
Q_D(QNativeSocketEngine);
if (d->closingDown)
return;
if (d->pendingReadNotification) {
// We use QPointer here to see if this QNativeSocketEngine was deleted as a result of
// finishing and cleaning up a network request when calling "processReadReady".
QPointer<QNativeSocketEngine> alive(this);
processReadReady();
if (alive.isNull())
return;
}
d->closingDown = true;
d->notifyOnRead = false;
d->notifyOnWrite = false;
d->notifyOnException = false;
d->emitReadReady = false;
HRESULT hr;
if (d->socketType == QAbstractSocket::TcpSocket) {
hr = QEventDispatcherWinRT::runOnXamlThread([d]() {
HRESULT hr;
// To close the connection properly (not with a hard reset) all pending read operation have to
// be finished or cancelled. The API isn't available on Windows 8.1 though.
ComPtr<IStreamSocket3> socket3;
hr = d->tcpSocket()->QueryInterface(IID_PPV_ARGS(&socket3));
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IAsyncAction> action;
hr = socket3->CancelIOAsync(&action);
Q_ASSERT_SUCCEEDED(hr);
hr = QWinRTFunctions::await(action, QWinRTFunctions::YieldThread, 5000);
// If there is no pending IO (no read established before) the function will fail with
// "function was called at an unexpected time" which is fine.
// Timeout is fine as well. The result will be the socket being hard reset instead of
// being closed gracefully
if (hr != E_ILLEGAL_METHOD_CALL && hr != ERROR_TIMEOUT)
Q_ASSERT_SUCCEEDED(hr);
return S_OK;
});
Q_ASSERT_SUCCEEDED(hr);
}
if (d->socketDescriptor != -1) {
ComPtr<IClosable> socket;
if (d->socketType == QAbstractSocket::TcpSocket) {
hr = d->tcpSocket()->QueryInterface(IID_PPV_ARGS(&socket));
Q_ASSERT_SUCCEEDED(hr);
hr = d->tcpSocket()->Release();
Q_ASSERT_SUCCEEDED(hr);
} else if (d->socketType == QAbstractSocket::UdpSocket) {
hr = d->udpSocket()->QueryInterface(IID_PPV_ARGS(&socket));
Q_ASSERT_SUCCEEDED(hr);
hr = d->udpSocket()->Release();
Q_ASSERT_SUCCEEDED(hr);
}
if (socket) {
hr = socket->Close();
Q_ASSERT_SUCCEEDED(hr);
}
d->socketDescriptor = -1;
}
d->socketState = QAbstractSocket::UnconnectedState;
d->hasSetSocketError = false;
d->localPort = 0;
d->localAddress.clear();
d->peerPort = 0;
d->peerAddress.clear();
d->inboundStreamCount = d->outboundStreamCount = 0;
}
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);
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << groupAddress << iface;
Q_UNIMPLEMENTED();
return false;
}
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);
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << groupAddress << iface;
Q_UNIMPLEMENTED();
return false;
}
QNetworkInterface QNativeSocketEngine::multicastInterface() const
{
Q_D(const QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::multicastInterface(), QNetworkInterface());
Q_CHECK_TYPE(QNativeSocketEngine::multicastInterface(), QAbstractSocket::UdpSocket, QNetworkInterface());
Q_UNIMPLEMENTED();
return QNetworkInterface();
}
bool QNativeSocketEngine::setMulticastInterface(const QNetworkInterface &iface)
{
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setMulticastInterface(), false);
Q_CHECK_TYPE(QNativeSocketEngine::setMulticastInterface(), QAbstractSocket::UdpSocket, false);
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << iface;
Q_UNIMPLEMENTED();
return false;
}
qint64 QNativeSocketEngine::bytesAvailable() const
{
Q_D(const QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::bytesAvailable(), -1);
Q_CHECK_NOT_STATE(QNativeSocketEngine::bytesAvailable(), QAbstractSocket::UnconnectedState, -1);
if (d->socketType != QAbstractSocket::TcpSocket)
return -1;
QMutexLocker locker(&d->worker->mutex);
const qint64 bytesAvailable = d->worker->pendingData.length();
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO << bytesAvailable;
return bytesAvailable;
}
qint64 QNativeSocketEngine::read(char *data, qint64 maxlen)
{
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO << maxlen;
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::read(), -1);
Q_CHECK_STATES(QNativeSocketEngine::read(), QAbstractSocket::ConnectedState, QAbstractSocket::BoundState, -1);
if (d->socketType != QAbstractSocket::TcpSocket)
return -1;
// There will be a read notification when the socket was closed by the remote host. If that
// happens and there isn't anything left in the buffer, we have to return -1 in order to signal
// the closing of the socket.
QMutexLocker mutexLocker(&d->worker->mutex);
if (d->worker->pendingData.isEmpty() && d->socketState != QAbstractSocket::ConnectedState) {
close();
return -1;
}
QByteArray readData;
const int copyLength = qMin(maxlen, qint64(d->worker->pendingData.length()));
if (maxlen >= d->worker->pendingData.length()) {
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO << "Reading full buffer";
readData = d->worker->pendingData;
d->worker->pendingData.clear();
d->emitReadReady = true;
} else {
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO << "Reading part of the buffer ("
<< copyLength << "of" << d->worker->pendingData.length() << "bytes";
readData = d->worker->pendingData.left(maxlen);
d->worker->pendingData.remove(0, maxlen);
if (d->notifyOnRead) {
d->pendingReadNotification = true;
emit readReady();
}
}
mutexLocker.unlock();
memcpy(data, readData, copyLength);
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO << "Read" << copyLength << "bytes";
return copyLength;
}
qint64 QNativeSocketEngine::write(const char *data, qint64 len)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << data << len;
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::write(), -1);
Q_CHECK_STATE(QNativeSocketEngine::write(), QAbstractSocket::ConnectedState, -1);
HRESULT hr = E_FAIL;
ComPtr<IOutputStream> stream;
if (d->socketType == QAbstractSocket::TcpSocket)
hr = d->tcpSocket()->get_OutputStream(&stream);
else if (d->socketType == QAbstractSocket::UdpSocket)
hr = d->udpSocket()->get_OutputStream(&stream);
Q_ASSERT_SUCCEEDED(hr);
qint64 bytesWritten = writeIOStream(stream, data, len);
if (bytesWritten < 0)
d->setError(QAbstractSocket::SocketAccessError, WinRTSocketEngine::AccessErrorString);
else if (bytesWritten > 0 && d->notifyOnWrite)
emit writeReady();
return bytesWritten;
}
qint64 QNativeSocketEngine::readDatagram(char *data, qint64 maxlen, QIpPacketHeader *header,
PacketHeaderOptions)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << maxlen;
#ifndef QT_NO_UDPSOCKET
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::readDatagram(), -1);
Q_CHECK_STATES(QNativeSocketEngine::readDatagram(), QAbstractSocket::BoundState,
QAbstractSocket::ConnectedState, -1);
QMutexLocker locker(&d->worker->mutex);
if (d->socketType != QAbstractSocket::UdpSocket || d->worker->pendingDatagrams.isEmpty()) {
if (header)
header->clear();
return -1;
}
WinRtDatagram datagram = d->worker->pendingDatagrams.takeFirst();
if (header)
*header = datagram.header;
QByteArray readOrigin;
if (maxlen < datagram.data.length())
readOrigin = datagram.data.left(maxlen);
else
readOrigin = datagram.data;
if (d->worker->pendingDatagrams.isEmpty()) {
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << "That's all folks";
d->worker->emitDataReceived = true;
d->emitReadReady = true;
}
locker.unlock();
memcpy(data, readOrigin, qMin(maxlen, qint64(datagram.data.length())));
return readOrigin.length();
#else
Q_UNUSED(data)
Q_UNUSED(maxlen)
Q_UNUSED(header)
return -1;
#endif // QT_NO_UDPSOCKET
}
qint64 QNativeSocketEngine::writeDatagram(const char *data, qint64 len, const QIpPacketHeader &header)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << data << len;
#ifndef QT_NO_UDPSOCKET
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::writeDatagram(), -1);
Q_CHECK_STATES(QNativeSocketEngine::writeDatagram(), QAbstractSocket::BoundState,
QAbstractSocket::ConnectedState, -1);
if (d->socketType != QAbstractSocket::UdpSocket)
return -1;
ComPtr<IHostName> remoteHost;
ComPtr<IHostNameFactory> hostNameFactory;
HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(),
&hostNameFactory);
Q_ASSERT_SUCCEEDED(hr);
const QString addressString = header.destinationAddress.toString();
HStringReference hostNameRef(reinterpret_cast<LPCWSTR>(addressString.utf16()));
hr = hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost);
RETURN_IF_FAILED("QNativeSocketEngine::writeDatagram: Could not create hostname.", return -1);
ComPtr<IAsyncOperation<IOutputStream *>> streamOperation;
ComPtr<IOutputStream> stream;
const QString portString = QString::number(header.destinationPort);
HStringReference portRef(reinterpret_cast<LPCWSTR>(portString.utf16()));
hr = d->udpSocket()->GetOutputStreamAsync(remoteHost.Get(), portRef.Get(), &streamOperation);
Q_ASSERT_SUCCEEDED(hr);
hr = QWinRTFunctions::await(streamOperation, stream.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
return writeIOStream(stream, data, len);
#else
Q_UNUSED(data)
Q_UNUSED(len)
Q_UNUSED(header)
return -1;
#endif // QT_NO_UDPSOCKET
}
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);
QMutexLocker locker(&d->worker->mutex);
return d->worker->pendingDatagrams.length() > 0;
}
qint64 QNativeSocketEngine::pendingDatagramSize() const
{
Q_D(const QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::pendingDatagramSize(), -1);
Q_CHECK_TYPE(QNativeSocketEngine::pendingDatagramSize(), QAbstractSocket::UdpSocket, -1);
QMutexLocker locker(&d->worker->mutex);
if (d->worker->pendingDatagrams.isEmpty())
return -1;
return d->worker->pendingDatagrams.at(0).data.length();
}
qint64 QNativeSocketEngine::bytesToWrite() const
{
return 0;
}
qint64 QNativeSocketEngine::receiveBufferSize() const
{
Q_D(const QNativeSocketEngine);
return d->option(QAbstractSocketEngine::ReceiveBufferSocketOption);
}
void QNativeSocketEngine::setReceiveBufferSize(qint64 bufferSize)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << bufferSize;
Q_D(QNativeSocketEngine);
d->setOption(QAbstractSocketEngine::ReceiveBufferSocketOption, bufferSize);
}
qint64 QNativeSocketEngine::sendBufferSize() const
{
Q_D(const QNativeSocketEngine);
return d->option(QAbstractSocketEngine::SendBufferSocketOption);
}
void QNativeSocketEngine::setSendBufferSize(qint64 bufferSize)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << bufferSize;
Q_D(QNativeSocketEngine);
d->setOption(QAbstractSocketEngine::SendBufferSocketOption, bufferSize);
}
int QNativeSocketEngine::option(QAbstractSocketEngine::SocketOption option) const
{
Q_D(const QNativeSocketEngine);
return d->option(option);
}
bool QNativeSocketEngine::setOption(QAbstractSocketEngine::SocketOption option, int value)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << option << value;
Q_D(QNativeSocketEngine);
return d->setOption(option, value);
}
bool QNativeSocketEngine::waitForRead(int msecs, bool *timedOut)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << msecs;
Q_D(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
QMutexLocker locker(&d->worker->mutex);
if (!d->worker->pendingData.isEmpty())
return true;
// Nothing to do, wait for more events
d->eventLoop.processEvents();
}
d->setError(QAbstractSocket::SocketTimeoutError,
WinRTSocketEngine::TimeOutErrorString);
if (timedOut)
*timedOut = true;
return false;
}
bool QNativeSocketEngine::waitForWrite(int msecs, bool *timedOut)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << msecs;
Q_UNUSED(timedOut);
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForWrite(), false);
Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForWrite(),
QAbstractSocket::UnconnectedState, false);
if (d->socketState == QAbstractSocket::ConnectingState) {
HRESULT hr = QWinRTFunctions::await(d->worker->connectOp, QWinRTFunctions::ProcessMainThreadEvents);
if (SUCCEEDED(hr)) {
handleConnectOpFinished(true, QAbstractSocket::UnknownSocketError, WinRTSocketEngine::UnknownSocketErrorString);
return true;
}
}
return false;
}
bool QNativeSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, bool checkRead, bool checkWrite, int msecs, bool *timedOut)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << checkRead << checkWrite << msecs;
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForReadOrWrite(), false);
Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForReadOrWrite(),
QAbstractSocket::UnconnectedState, false);
Q_UNUSED(readyToRead);
Q_UNUSED(readyToWrite);
Q_UNUSED(timedOut);
return false;
}
bool QNativeSocketEngine::isReadNotificationEnabled() const
{
Q_D(const QNativeSocketEngine);
return d->notifyOnRead;
}
void QNativeSocketEngine::setReadNotificationEnabled(bool enable)
{
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO << enable;
Q_D(QNativeSocketEngine);
d->notifyOnRead = enable;
}
bool QNativeSocketEngine::isWriteNotificationEnabled() const
{
Q_D(const QNativeSocketEngine);
return d->notifyOnWrite;
}
void QNativeSocketEngine::setWriteNotificationEnabled(bool enable)
{
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO << 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();
}
}
bool QNativeSocketEngine::isExceptionNotificationEnabled() const
{
Q_D(const QNativeSocketEngine);
return d->notifyOnException;
}
void QNativeSocketEngine::setExceptionNotificationEnabled(bool enable)
{
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO << enable;
Q_D(QNativeSocketEngine);
d->notifyOnException = enable;
}
void QNativeSocketEngine::establishRead()
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
Q_D(QNativeSocketEngine);
HRESULT hr;
hr = QEventDispatcherWinRT::runOnXamlThread([d]() {
d->worker->setTcpSocket(d->tcpSocket());
d->worker->startReading();
return S_OK;
});
Q_ASSERT_SUCCEEDED(hr);
}
void QNativeSocketEngine::handleConnectOpFinished(bool success, QAbstractSocket::SocketError error, WinRTSocketEngine::ErrorString errorString)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << success << error << errorString;
Q_D(QNativeSocketEngine);
disconnect(d->worker, &SocketEngineWorker::connectOpFinished,
this, &QNativeSocketEngine::handleConnectOpFinished);
if (!success) {
d->setError(error, errorString);
d->socketState = QAbstractSocket::UnconnectedState;
close();
return;
}
d->socketState = QAbstractSocket::ConnectedState;
d->fetchConnectionParameters();
emit connectionReady();
if (d->socketType != QAbstractSocket::TcpSocket)
return;
#ifndef QT_NO_SSL
// Delay the reader so that the SSL socket can upgrade
if (d->sslSocket)
QObject::connect(qobject_cast<QSslSocket *>(d->sslSocket), &QSslSocket::encrypted, this, &QNativeSocketEngine::establishRead);
else
#endif
establishRead();
}
void QNativeSocketEngine::handleNewData()
{
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO;
Q_D(QNativeSocketEngine);
if (d->notifyOnRead && d->emitReadReady) {
if (d->socketType == QAbstractSocket::UdpSocket && !d->worker->emitDataReceived)
return;
qCDebug(lcNetworkSocketVerbose) << this << Q_FUNC_INFO << "Emitting readReady";
d->pendingReadNotification = true;
emit readReady();
d->worker->emitDataReceived = false;
d->emitReadReady = false;
}
}
void QNativeSocketEngine::handleTcpError(QAbstractSocket::SocketError error)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << error;
Q_D(QNativeSocketEngine);
WinRTSocketEngine::ErrorString errorString;
switch (error) {
case QAbstractSocket::RemoteHostClosedError:
errorString = WinRTSocketEngine::RemoteHostClosedErrorString;
break;
default:
errorString = WinRTSocketEngine::UnknownSocketErrorString;
}
d->setError(error, errorString);
close();
}
void QNativeSocketEngine::processReadReady()
{
Q_D(QNativeSocketEngine);
if (d->closingDown)
return;
d->pendingReadNotification = false;
readNotification();
}
bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol &socketProtocol)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << socketType << socketProtocol;
Q_UNUSED(socketProtocol);
HRESULT hr;
switch (socketType) {
case QAbstractSocket::TcpSocket: {
ComPtr<IStreamSocket> socket;
hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_StreamSocket).Get(), &socket);
RETURN_FALSE_IF_FAILED("createNewSocket: Could not create socket instance");
socketDescriptor = qintptr(socket.Detach());
break;
}
case QAbstractSocket::UdpSocket: {
ComPtr<IDatagramSocket> socket;
hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_DatagramSocket).Get(), &socket);
RETURN_FALSE_IF_FAILED("createNewSocket: Could not create socket instance");
socketDescriptor = qintptr(socket.Detach());
QEventDispatcherWinRT::runOnXamlThread([&hr, this]() {
hr = udpSocket()->add_MessageReceived(Callback<DatagramReceivedHandler>(worker, &SocketEngineWorker::OnNewDatagramReceived).Get(), &connectionToken);
if (FAILED(hr)) {
qErrnoWarning(hr, "createNewSocket: Could not add \"message received\" callback");
return hr;
}
return S_OK;
});
if (FAILED(hr))
return false;
break;
}
default:
qWarning("Invalid socket type");
return false;
}
this->socketType = socketType;
// Make the socket nonblocking.
if (!setOption(QAbstractSocketEngine::NonBlockingSocketOption, 1)) {
setError(QAbstractSocket::UnsupportedSocketOperationError, WinRTSocketEngine::NonBlockingInitFailedErrorString);
q_func()->close();
return false;
}
return true;
}
QNativeSocketEnginePrivate::QNativeSocketEnginePrivate()
: QAbstractSocketEnginePrivate()
, notifyOnRead(true)
, notifyOnWrite(true)
, notifyOnException(false)
, closingDown(false)
, socketDescriptor(-1)
, worker(new SocketEngineWorker(this))
, sslSocket(nullptr)
, connectionToken( { -1 } )
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
}
QNativeSocketEnginePrivate::~QNativeSocketEnginePrivate()
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
if (socketDescriptor == -1 || connectionToken.value == -1)
return;
HRESULT hr;
if (socketType == QAbstractSocket::UdpSocket)
hr = udpSocket()->remove_MessageReceived(connectionToken);
else if (socketType == QAbstractSocket::TcpSocket)
hr = tcpListener->remove_ConnectionReceived(connectionToken);
Q_ASSERT_SUCCEEDED(hr);
worker->deleteLater();
}
void QNativeSocketEnginePrivate::setError(QAbstractSocket::SocketError error, WinRTSocketEngine::ErrorString errorString) const
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << error << errorString;
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 WinRTSocketEngine::NonBlockingInitFailedErrorString:
socketErrorString = QNativeSocketEngine::tr("Unable to initialize non-blocking socket");
break;
case WinRTSocketEngine::BroadcastingInitFailedErrorString:
socketErrorString = QNativeSocketEngine::tr("Unable to initialize broadcast socket");
break;
// should not happen anymore
case WinRTSocketEngine::NoIpV6ErrorString:
socketErrorString = QNativeSocketEngine::tr("Attempt to use IPv6 socket on a platform with no IPv6 support");
break;
case WinRTSocketEngine::RemoteHostClosedErrorString:
socketErrorString = QNativeSocketEngine::tr("The remote host closed the connection");
break;
case WinRTSocketEngine::TimeOutErrorString:
socketErrorString = QNativeSocketEngine::tr("Network operation timed out");
break;
case WinRTSocketEngine::ResourceErrorString:
socketErrorString = QNativeSocketEngine::tr("Out of resources");
break;
case WinRTSocketEngine::OperationUnsupportedErrorString:
socketErrorString = QNativeSocketEngine::tr("Unsupported socket operation");
break;
case WinRTSocketEngine::ProtocolUnsupportedErrorString:
socketErrorString = QNativeSocketEngine::tr("Protocol type not supported");
break;
case WinRTSocketEngine::InvalidSocketErrorString:
socketErrorString = QNativeSocketEngine::tr("Invalid socket descriptor");
break;
case WinRTSocketEngine::HostUnreachableErrorString:
socketErrorString = QNativeSocketEngine::tr("Host unreachable");
break;
case WinRTSocketEngine::NetworkUnreachableErrorString:
socketErrorString = QNativeSocketEngine::tr("Network unreachable");
break;
case WinRTSocketEngine::AccessErrorString:
socketErrorString = QNativeSocketEngine::tr("Permission denied");
break;
case WinRTSocketEngine::ConnectionTimeOutErrorString:
socketErrorString = QNativeSocketEngine::tr("Connection timed out");
break;
case WinRTSocketEngine::ConnectionRefusedErrorString:
socketErrorString = QNativeSocketEngine::tr("Connection refused");
break;
case WinRTSocketEngine::AddressInuseErrorString:
socketErrorString = QNativeSocketEngine::tr("The bound address is already in use");
break;
case WinRTSocketEngine::AddressNotAvailableErrorString:
socketErrorString = QNativeSocketEngine::tr("The address is not available");
break;
case WinRTSocketEngine::AddressProtectedErrorString:
socketErrorString = QNativeSocketEngine::tr("The address is protected");
break;
case WinRTSocketEngine::DatagramTooLargeErrorString:
socketErrorString = QNativeSocketEngine::tr("Datagram was too large to send");
break;
case WinRTSocketEngine::SendDatagramErrorString:
socketErrorString = QNativeSocketEngine::tr("Unable to send a message");
break;
case WinRTSocketEngine::ReceiveDatagramErrorString:
socketErrorString = QNativeSocketEngine::tr("Unable to receive a message");
break;
case WinRTSocketEngine::WriteErrorString:
socketErrorString = QNativeSocketEngine::tr("Unable to write");
break;
case WinRTSocketEngine::ReadErrorString:
socketErrorString = QNativeSocketEngine::tr("Network error");
break;
case WinRTSocketEngine::PortInuseErrorString:
socketErrorString = QNativeSocketEngine::tr("Another socket is already listening on the same port");
break;
case WinRTSocketEngine::NotSocketErrorString:
socketErrorString = QNativeSocketEngine::tr("Operation on non-socket");
break;
case WinRTSocketEngine::InvalidProxyTypeString:
socketErrorString = QNativeSocketEngine::tr("The proxy type is invalid for this operation");
break;
case WinRTSocketEngine::TemporaryErrorString:
socketErrorString = QNativeSocketEngine::tr("Temporary error");
break;
case WinRTSocketEngine::UnknownSocketErrorString:
socketErrorString = QNativeSocketEngine::tr("Unknown error");
break;
}
}
int QNativeSocketEnginePrivate::option(QAbstractSocketEngine::SocketOption opt) const
{
ComPtr<IStreamSocketControl> control;
if (socketType == QAbstractSocket::TcpSocket) {
if (FAILED(tcpSocket()->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:
case QAbstractSocketEngine::MaxStreamsSocketOption:
case QAbstractSocketEngine::PathMtuInformation:
default:
return -1;
}
return -1;
}
bool QNativeSocketEnginePrivate::setOption(QAbstractSocketEngine::SocketOption opt, int v)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO << opt << v;
ComPtr<IStreamSocketControl> control;
if (socketType == QAbstractSocket::TcpSocket) {
if (FAILED(tcpSocket()->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
|| socketState != QAbstractSocket::UnconnectedState)
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:
case QAbstractSocketEngine::MaxStreamsSocketOption:
case QAbstractSocketEngine::PathMtuInformation:
default:
return false;
}
return false;
}
bool QNativeSocketEnginePrivate::fetchConnectionParameters()
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
localPort = 0;
localAddress.clear();
peerPort = 0;
peerAddress.clear();
inboundStreamCount = outboundStreamCount = 0;
HRESULT hr;
if (socketType == QAbstractSocket::TcpSocket) {
ComPtr<IHostName> hostName;
HString tmpHString;
ComPtr<IStreamSocketInformation> info;
hr = tcpSocket()->get_Information(&info);
Q_ASSERT_SUCCEEDED(hr);
hr = info->get_LocalAddress(&hostName);
Q_ASSERT_SUCCEEDED(hr);
if (hostName) {
hr = hostName->get_CanonicalName(tmpHString.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
localAddress.setAddress(qt_QStringFromHString(tmpHString));
hr = info->get_LocalPort(tmpHString.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
localPort = qt_QStringFromHString(tmpHString).toInt();
}
if (!localPort && tcpListener) {
ComPtr<IStreamSocketListenerInformation> listenerInfo = 0;
hr = tcpListener->get_Information(&listenerInfo);
Q_ASSERT_SUCCEEDED(hr);
hr = listenerInfo->get_LocalPort(tmpHString.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
localPort = qt_QStringFromHString(tmpHString).toInt();
localAddress = QHostAddress::Any;
}
info->get_RemoteAddress(&hostName);
if (hostName) {
hr = hostName->get_CanonicalName(tmpHString.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
peerAddress.setAddress(qt_QStringFromHString(tmpHString));
hr = info->get_RemotePort(tmpHString.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
peerPort = qt_QStringFromHString(tmpHString).toInt();
inboundStreamCount = outboundStreamCount = 1;
}
} else if (socketType == QAbstractSocket::UdpSocket) {
ComPtr<IHostName> hostName;
HString tmpHString;
ComPtr<IDatagramSocketInformation> info;
hr = udpSocket()->get_Information(&info);
Q_ASSERT_SUCCEEDED(hr);
hr = info->get_LocalAddress(&hostName);
Q_ASSERT_SUCCEEDED(hr);
if (hostName) {
hr = hostName->get_CanonicalName(tmpHString.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
localAddress.setAddress(qt_QStringFromHString(tmpHString));
hr = info->get_LocalPort(tmpHString.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
localPort = qt_QStringFromHString(tmpHString).toInt();
}
hr = info->get_RemoteAddress(&hostName);
Q_ASSERT_SUCCEEDED(hr);
if (hostName) {
hr = hostName->get_CanonicalName(tmpHString.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
peerAddress.setAddress(qt_QStringFromHString(tmpHString));
hr = info->get_RemotePort(tmpHString.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
peerPort = qt_QStringFromHString(tmpHString).toInt();
inboundStreamCount = outboundStreamCount = 1;
}
}
return true;
}
HRESULT QNativeSocketEnginePrivate::handleClientConnection(IStreamSocketListener *listener, IStreamSocketListenerConnectionReceivedEventArgs *args)
{
qCDebug(lcNetworkSocket) << this << Q_FUNC_INFO;
Q_Q(QNativeSocketEngine);
Q_UNUSED(listener)
IStreamSocket *socket;
args->get_Socket(&socket);
pendingConnections.append(socket);
emit q->connectionReady();
if (notifyOnRead)
emit q->readReady();
return S_OK;
}
QT_END_NAMESPACE
#include "qnativesocketengine_winrt.moc"