Revision control

Copy as Markdown

// Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QString>
#include <QtTest>
#include <QNetworkProxy>
#include <QTcpSocket>
#include <QTcpServer>
#include <QtCore/QScopedPointer>
#ifndef QT_NO_SSL
#include <QtNetwork/qsslpresharedkeyauthenticator.h>
#include <QtNetwork/qsslcipher.h>
#include <QtNetwork/qsslkey.h>
#include <QtNetwork/qsslsocket.h>
#endif
#include <QtWebSockets/QWebSocketHandshakeOptions>
#include <QtWebSockets/QWebSocketServer>
#include <QtWebSockets/QWebSocket>
#include <QtWebSockets/QWebSocketCorsAuthenticator>
#include <QtWebSockets/qwebsocketprotocol.h>
#include <memory>
using namespace Qt::StringLiterals;
QT_USE_NAMESPACE
Q_DECLARE_METATYPE(QWebSocketProtocol::Version)
Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode)
Q_DECLARE_METATYPE(QWebSocketServer::SslMode)
Q_DECLARE_METATYPE(QWebSocketCorsAuthenticator *)
#ifndef QT_NO_SSL
Q_DECLARE_METATYPE(QSslError)
#endif
// Use this cipher to force PSK key sharing.
static const QString PSK_CIPHER_WITHOUT_AUTH = QStringLiteral("PSK-AES256-CBC-SHA");
static const QByteArray PSK_CLIENT_PRESHAREDKEY = QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f");
static const QByteArray PSK_SERVER_IDENTITY_HINT = QByteArrayLiteral("QtTestServerHint");
static const QByteArray PSK_CLIENT_IDENTITY = QByteArrayLiteral("Client_identity");
class PskProvider : public QObject
{
Q_OBJECT
public:
bool m_server = false;
QByteArray m_identity;
QByteArray m_psk;
public slots:
#if QT_CONFIG(ssl)
void providePsk(QSslPreSharedKeyAuthenticator *authenticator)
{
QVERIFY(authenticator);
QCOMPARE(authenticator->identityHint(), PSK_SERVER_IDENTITY_HINT);
if (m_server)
QCOMPARE(authenticator->maximumIdentityLength(), 0);
else
QVERIFY(authenticator->maximumIdentityLength() > 0);
QVERIFY(authenticator->maximumPreSharedKeyLength() > 0);
if (!m_identity.isEmpty()) {
authenticator->setIdentity(m_identity);
QCOMPARE(authenticator->identity(), m_identity);
}
if (!m_psk.isEmpty()) {
authenticator->setPreSharedKey(m_psk);
QCOMPARE(authenticator->preSharedKey(), m_psk);
}
}
#endif
};
class tst_QWebSocketServer : public QObject
{
Q_OBJECT
public:
tst_QWebSocketServer();
private Q_SLOTS:
void init();
void initTestCase();
void cleanupTestCase();
void tst_initialisation();
void tst_settersAndGetters();
void tst_listening();
void tst_connectivity();
void tst_protocols_data();
void tst_protocols();
#if QT_CONFIG(ssl)
void tst_preSharedKey();
#endif
void tst_maxPendingConnections();
void tst_serverDestroyedWhileSocketConnected();
void tst_scheme(); // qtbug-55927
void tst_handleConnection();
void tst_handshakeTimeout(); // qtbug-63312, qtbug-57026
void fragmentedHandshakeOnHandedOverSocket_data();
void fragmentedHandshakeOnHandedOverSocket();
void oversizedHandshakeOnHandedOverSocket_data();
void oversizedHandshakeOnHandedOverSocket();
void disconnectDuringHandshakeOnHandedOverSocket_data();
void disconnectDuringHandshakeOnHandedOverSocket();
void handshakeFollowedByFrame();
void gentleClose();
void multipleFrames();
private:
bool m_shouldSkipUnsupportedIpv6Test;
#ifdef SHOULD_CHECK_SYSCALL_SUPPORT
bool ipv6GetsockoptionMissing(int level, int optname);
#endif
};
tst_QWebSocketServer::tst_QWebSocketServer() : m_shouldSkipUnsupportedIpv6Test(false)
{
}
void tst_QWebSocketServer::init()
{
qRegisterMetaType<QWebSocketProtocol::Version>("QWebSocketProtocol::Version");
qRegisterMetaType<QWebSocketProtocol::CloseCode>("QWebSocketProtocol::CloseCode");
qRegisterMetaType<QWebSocketServer::SslMode>("QWebSocketServer::SslMode");
qRegisterMetaType<QWebSocketCorsAuthenticator *>("QWebSocketCorsAuthenticator *");
#ifndef QT_NO_SSL
qRegisterMetaType<QSslError>("QSslError");
qRegisterMetaType<QSslPreSharedKeyAuthenticator *>();
#endif
}
#ifdef SHOULD_CHECK_SYSCALL_SUPPORT
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
bool tst_QWebSocketServer::ipv6GetsockoptionMissing(int level, int optname)
{
int testSocket;
testSocket = socket(PF_INET6, SOCK_STREAM, 0);
// If we can't test here, assume it's not missing
if (testSocket == -1)
return false;
bool result = false;
if (getsockopt(testSocket, level, optname, nullptr, 0) == -1) {
if (errno == EOPNOTSUPP) {
result = true;
}
}
close(testSocket);
return result;
}
#endif //SHOULD_CHECK_SYSCALL_SUPPORT
void tst_QWebSocketServer::initTestCase()
{
#ifdef SHOULD_CHECK_SYSCALL_SUPPORT
// Qemu does not have required support for IPV6 socket options.
// If this is detected, skip the test
m_shouldSkipUnsupportedIpv6Test = ipv6GetsockoptionMissing(SOL_IPV6, IPV6_V6ONLY);
#endif
}
void tst_QWebSocketServer::cleanupTestCase()
{
}
void tst_QWebSocketServer::tst_initialisation()
{
{
QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode);
QVERIFY(server.serverName().isEmpty());
QCOMPARE(server.secureMode(), QWebSocketServer::NonSecureMode);
QVERIFY(!server.isListening());
QCOMPARE(server.maxPendingConnections(), 30);
QCOMPARE(server.serverPort(), quint16(0));
QCOMPARE(server.serverAddress(), QHostAddress());
QCOMPARE(server.socketDescriptor(), -1);
QVERIFY(!server.hasPendingConnections());
QVERIFY(!server.nextPendingConnection());
QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeNormal);
QVERIFY(server.errorString().isEmpty());
#ifndef QT_NO_NETWORKPROXY
QCOMPARE(server.proxy().type(), QNetworkProxy::DefaultProxy);
#endif
#ifndef QT_NO_SSL
QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration());
#endif
QCOMPARE(server.supportedVersions().size(), 1);
QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::VersionLatest);
QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::Version13);
server.close();
//closing a server should not affect any of the parameters
//certainly if the server was not opened before
QVERIFY(server.serverName().isEmpty());
QCOMPARE(server.secureMode(), QWebSocketServer::NonSecureMode);
QVERIFY(!server.isListening());
QCOMPARE(server.maxPendingConnections(), 30);
QCOMPARE(server.serverPort(), quint16(0));
QCOMPARE(server.serverAddress(), QHostAddress());
QCOMPARE(server.socketDescriptor(), -1);
QVERIFY(!server.hasPendingConnections());
QVERIFY(!server.nextPendingConnection());
QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeNormal);
QVERIFY(server.errorString().isEmpty());
#ifndef QT_NO_NETWORKPROXY
QCOMPARE(server.proxy().type(), QNetworkProxy::DefaultProxy);
#endif
#ifndef QT_NO_SSL
QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration());
#endif
QCOMPARE(server.supportedVersions().size(), 1);
QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::VersionLatest);
QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::Version13);
QCOMPARE(server.serverUrl(), QUrl());
}
{
#ifndef QT_NO_SSL
QWebSocketServer sslServer(QString(), QWebSocketServer::SecureMode);
QCOMPARE(sslServer.secureMode(), QWebSocketServer::SecureMode);
#endif
}
}
void tst_QWebSocketServer::tst_settersAndGetters()
{
QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode);
server.setMaxPendingConnections(23);
QCOMPARE(server.maxPendingConnections(), 23);
server.setMaxPendingConnections(INT_MIN);
QCOMPARE(server.maxPendingConnections(), INT_MIN);
server.setMaxPendingConnections(INT_MAX);
QCOMPARE(server.maxPendingConnections(), INT_MAX);
QVERIFY(!server.setSocketDescriptor(-2));
QCOMPARE(server.socketDescriptor(), -1);
server.setServerName(QStringLiteral("Qt WebSocketServer"));
QCOMPARE(server.serverName(), QStringLiteral("Qt WebSocketServer"));
#ifndef QT_NO_NETWORKPROXY
QNetworkProxy proxy(QNetworkProxy::Socks5Proxy);
server.setProxy(proxy);
QCOMPARE(server.proxy(), proxy);
#endif
#ifndef QT_NO_SSL
//cannot set an ssl configuration on a non secure server
QSslConfiguration sslConfiguration = QSslConfiguration::defaultConfiguration();
sslConfiguration.setPeerVerifyDepth(sslConfiguration.peerVerifyDepth() + 1);
server.setSslConfiguration(sslConfiguration);
QVERIFY(server.sslConfiguration() != sslConfiguration);
QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration());
QWebSocketServer sslServer(QString(), QWebSocketServer::SecureMode);
sslServer.setSslConfiguration(sslConfiguration);
QCOMPARE(sslServer.sslConfiguration(), sslConfiguration);
QVERIFY(sslServer.sslConfiguration() != QSslConfiguration::defaultConfiguration());
#endif
server.setHandshakeTimeout(64);
QCOMPARE(server.handshakeTimeoutMS(), 64);
#if __has_include(<chrono>)
auto expected = std::chrono::milliseconds(64);
QCOMPARE(server.handshakeTimeout(), expected);
expected = std::chrono::milliseconds(242);
server.setHandshakeTimeout(expected);
QCOMPARE(server.handshakeTimeoutMS(), 242);
QCOMPARE(server.handshakeTimeout(), expected);
#endif
}
void tst_QWebSocketServer::tst_listening()
{
//These listening tests are not too extensive, as the implementation of QWebSocketServer
//relies on QTcpServer
QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode);
QSignalSpy serverAcceptErrorSpy(&server, SIGNAL(acceptError(QAbstractSocket::SocketError)));
QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection()));
QSignalSpy serverErrorSpy(&server,
SIGNAL(serverError(QWebSocketProtocol::CloseCode)));
QSignalSpy corsAuthenticationSpy(&server,
SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*)));
QSignalSpy serverClosedSpy(&server, SIGNAL(closed()));
#ifndef QT_NO_SSL
QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError)));
QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList<QSslError>)));
QSignalSpy sslErrorsOccurredSpy(&server, SIGNAL(sslErrorsOccurred(QSslSocket*, QList<QSslError>)));
#endif
QVERIFY(server.listen()); //listen on all network interface, choose an appropriate port
QVERIFY(server.isListening());
QCOMPARE(serverClosedSpy.size(), 0);
server.close();
QTRY_COMPARE(serverClosedSpy.size(), 1);
QVERIFY(!server.isListening());
QCOMPARE(serverErrorSpy.size(), 0);
QVERIFY(!server.listen(QHostAddress(QStringLiteral("1.2.3.4")), 0));
QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeAbnormalDisconnection);
QCOMPARE(server.errorString().toLatin1().constData(), "The address is not available");
QVERIFY(!server.isListening());
QCOMPARE(serverAcceptErrorSpy.size(), 0);
QCOMPARE(serverConnectionSpy.size(), 0);
QCOMPARE(corsAuthenticationSpy.size(), 0);
#ifndef QT_NO_SSL
QCOMPARE(peerVerifyErrorSpy.size(), 0);
QCOMPARE(sslErrorsSpy.size(), 0);
QCOMPARE(sslErrorsOccurredSpy.size(), 0);
#endif
QCOMPARE(serverErrorSpy.size(), 1);
QCOMPARE(serverClosedSpy.size(), 1);
}
void tst_QWebSocketServer::tst_connectivity()
{
if (m_shouldSkipUnsupportedIpv6Test)
QSKIP("Syscalls needed for ipv6 sockoptions missing functionality");
QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode);
QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection()));
QSignalSpy serverErrorSpy(&server,
SIGNAL(serverError(QWebSocketProtocol::CloseCode)));
QSignalSpy corsAuthenticationSpy(&server,
SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*)));
QSignalSpy serverClosedSpy(&server, SIGNAL(closed()));
#ifndef QT_NO_SSL
QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError)));
QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList<QSslError>)));
QSignalSpy sslErrorsOccurredSpy(&server, SIGNAL(sslErrorsOccurred(QSslSocket*, QList<QSslError>)));
#endif
QWebSocket socket;
QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected()));
QVERIFY(server.listen());
QCOMPARE(server.serverAddress(), QHostAddress(QHostAddress::Any));
QCOMPARE(server.serverUrl(), QUrl(QStringLiteral("ws://") + QHostAddress(QHostAddress::LocalHost).toString() +
QStringLiteral(":").append(QString::number(server.serverPort()))));
socket.open(server.serverUrl());
QTRY_COMPARE(socketConnectedSpy.size(), 1);
QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
QCOMPARE(serverConnectionSpy.size(), 1);
QCOMPARE(corsAuthenticationSpy.size(), 1);
QCOMPARE(serverClosedSpy.size(), 0);
server.close();
QTRY_COMPARE(serverClosedSpy.size(), 1);
#ifndef QT_NO_SSL
QCOMPARE(peerVerifyErrorSpy.size(), 0);
QCOMPARE(sslErrorsSpy.size(), 0);
QCOMPARE(sslErrorsOccurredSpy.size(), 0);
#endif
QCOMPARE(serverErrorSpy.size(), 0);
}
void tst_QWebSocketServer::tst_protocols_data()
{
QTest::addColumn<QStringList>("clientProtocols");
QTest::addColumn<QStringList>("headerProtocols");
QTest::addColumn<QString>("expectedProtocol");
QTest::addRow("none") << QStringList{} << QStringList{} << QString{};
QTest::addRow("same order as server")
<< QStringList{ "chat", "superchat" } << QStringList{} << QStringLiteral("chat");
QTest::addRow("different order from server")
<< QStringList{ "superchat", "chat" } << QStringList{} << QStringLiteral("superchat");
QTest::addRow("unsupported protocol") << QStringList{ "foo" } << QStringList{} << QString{};
QTest::addRow("mixed supported/unsupported protocol")
<< QStringList{ "foo", "chat" } << QStringList{} << QStringLiteral("chat");
QTest::addRow("same order as server, in header")
<< QStringList{} << QStringList{ "chat", "superchat" } << QStringLiteral("chat");
QTest::addRow("specified in options and in header")
<< QStringList{ "superchat" } << QStringList{ "chat" } << QStringLiteral("superchat");
}
void tst_QWebSocketServer::tst_protocols()
{
QFETCH(QStringList, clientProtocols);
QFETCH(QStringList, headerProtocols);
QFETCH(QString, expectedProtocol);
QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode);
server.setSupportedSubprotocols({ QStringLiteral("chat"), QStringLiteral("superchat") });
QSignalSpy newConnectionSpy(&server, &QWebSocketServer::newConnection);
QVERIFY(server.listen());
QWebSocketHandshakeOptions opt;
QNetworkRequest request(server.serverUrl());
if (!headerProtocols.isEmpty()) {
QString protocols = headerProtocols.join(u',');
request.setRawHeader("Sec-WebSocket-Protocol", protocols.toUtf8());
}
opt.setSubprotocols(clientProtocols);
QWebSocket client;
client.open(request, opt);
QTRY_COMPARE(client.state(), QAbstractSocket::ConnectedState);
QTRY_COMPARE(newConnectionSpy.size(), 1);
auto *serverSocket = server.nextPendingConnection();
QVERIFY(serverSocket);
QCOMPARE(client.subprotocol(), expectedProtocol);
QCOMPARE(serverSocket->subprotocol(), expectedProtocol);
QCOMPARE(serverSocket->handshakeOptions().subprotocols(), clientProtocols + headerProtocols);
}
#if QT_CONFIG(ssl)
void tst_QWebSocketServer::tst_preSharedKey()
{
if (m_shouldSkipUnsupportedIpv6Test)
QSKIP("Syscalls needed for ipv6 sockoptions missing functionality");
if (QSslSocket::activeBackend() != u"openssl")
QSKIP("OpenSSL required");
QWebSocketServer server(QString(), QWebSocketServer::SecureMode);
bool cipherFound = false;
const QList<QSslCipher> supportedCiphers = QSslConfiguration::supportedCiphers();
for (const QSslCipher &cipher : supportedCiphers) {
if (cipher.name() == PSK_CIPHER_WITHOUT_AUTH) {
cipherFound = true;
break;
}
}
if (!cipherFound)
QSKIP("SSL implementation does not support the necessary cipher");
QSslCipher cipher(PSK_CIPHER_WITHOUT_AUTH);
QList<QSslCipher> list;
list << cipher;
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setProtocol(QSsl::TlsV1_2); // With TLS 1.3 there are some issues with PSK, force 1.2
config.setCiphers(list);
config.setPeerVerifyMode(QSslSocket::VerifyNone);
config.setPreSharedKeyIdentityHint(PSK_SERVER_IDENTITY_HINT);
server.setSslConfiguration(config);
PskProvider providerServer;
providerServer.m_server = true;
providerServer.m_identity = PSK_CLIENT_IDENTITY;
providerServer.m_psk = PSK_CLIENT_PRESHAREDKEY;
connect(&server, &QWebSocketServer::preSharedKeyAuthenticationRequired, &providerServer, &PskProvider::providePsk);
QSignalSpy serverPskRequiredSpy(&server, &QWebSocketServer::preSharedKeyAuthenticationRequired);
QSignalSpy serverConnectionSpy(&server, &QWebSocketServer::newConnection);
QSignalSpy serverErrorSpy(&server,
SIGNAL(serverError(QWebSocketProtocol::CloseCode)));
QSignalSpy serverClosedSpy(&server, &QWebSocketServer::closed);
QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList<QSslError>)));
QSignalSpy sslErrorsOccurredSpy(&server, SIGNAL(sslErrorsOccurred(QSslSocket*, QList<QSslError>)));
QWebSocket socket;
QSslConfiguration socketConfig = QSslConfiguration::defaultConfiguration();
if (QSslSocket::sslLibraryVersionNumber() >= 0x10101000L)
socketConfig.setProtocol(QSsl::TlsV1_2); // With TLS 1.3 there are some issues with PSK, force 1.2
socketConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
socketConfig.setCiphers(list);
socket.setSslConfiguration(socketConfig);
PskProvider providerClient;
providerClient.m_identity = PSK_CLIENT_IDENTITY;
providerClient.m_psk = PSK_CLIENT_PRESHAREDKEY;
connect(&socket, &QWebSocket::preSharedKeyAuthenticationRequired, &providerClient, &PskProvider::providePsk);
QSignalSpy socketPskRequiredSpy(&socket, &QWebSocket::preSharedKeyAuthenticationRequired);
QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected);
QVERIFY(server.listen());
QCOMPARE(server.serverAddress(), QHostAddress(QHostAddress::Any));
QCOMPARE(server.serverUrl(), QUrl(QString::asprintf("wss://%ls:%d",
qUtf16Printable(QHostAddress(QHostAddress::LocalHost).toString()), server.serverPort())));
socket.open(server.serverUrl());
QTRY_COMPARE(socketConnectedSpy.size(), 1);
QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
QCOMPARE(serverConnectionSpy.size(), 1);
QCOMPARE(serverPskRequiredSpy.size(), 1);
QCOMPARE(socketPskRequiredSpy.size(), 1);
QCOMPARE(serverClosedSpy.size(), 0);
server.close();
QTRY_COMPARE(serverClosedSpy.size(), 1);
QCOMPARE(sslErrorsSpy.size(), 0);
QCOMPARE(sslErrorsOccurredSpy.size(), 0);
QCOMPARE(serverErrorSpy.size(), 0);
}
#endif
void tst_QWebSocketServer::tst_maxPendingConnections()
{
if (m_shouldSkipUnsupportedIpv6Test)
QSKIP("Syscalls needed for ipv6 sockoptions missing functionality");
//tests if maximum connections are respected
//also checks if there are no side-effects like signals that are unexpectedly thrown
QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode);
server.setMaxPendingConnections(2);
QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection()));
QSignalSpy serverErrorSpy(&server,
SIGNAL(serverError(QWebSocketProtocol::CloseCode)));
QSignalSpy corsAuthenticationSpy(&server,
SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*)));
QSignalSpy serverClosedSpy(&server, SIGNAL(closed()));
#ifndef QT_NO_SSL
QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError)));
QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList<QSslError>)));
QSignalSpy sslErrorsOccurredSpy(&server, SIGNAL(sslErrorsOccurred(QSslSocket*, QList<QSslError>)));
#endif
QSignalSpy serverAcceptErrorSpy(&server, SIGNAL(acceptError(QAbstractSocket::SocketError)));
QWebSocket socket1;
QWebSocket socket2;
QWebSocket socket3;
QSignalSpy socket1ConnectedSpy(&socket1, SIGNAL(connected()));
QSignalSpy socket2ConnectedSpy(&socket2, SIGNAL(connected()));
QSignalSpy socket3ConnectedSpy(&socket3, SIGNAL(connected()));
QVERIFY(server.listen());
socket1.open(server.serverUrl());
QTRY_COMPARE(socket1ConnectedSpy.size(), 1);
QCOMPARE(socket1.state(), QAbstractSocket::ConnectedState);
QCOMPARE(serverConnectionSpy.size(), 1);
QCOMPARE(corsAuthenticationSpy.size(), 1);
socket2.open(server.serverUrl());
QTRY_COMPARE(socket2ConnectedSpy.size(), 1);
QCOMPARE(socket2.state(), QAbstractSocket::ConnectedState);
QCOMPARE(serverConnectionSpy.size(), 2);
QCOMPARE(corsAuthenticationSpy.size(), 2);
socket3.open(server.serverUrl());
QVERIFY(!socket3ConnectedSpy.wait(250));
QCOMPARE(socket3ConnectedSpy.size(), 0);
QCOMPARE(socket3.state(), QAbstractSocket::UnconnectedState);
QCOMPARE(serverConnectionSpy.size(), 2);
QCOMPARE(corsAuthenticationSpy.size(), 2);
QVERIFY(server.hasPendingConnections());
QWebSocket *pSocket = server.nextPendingConnection();
QVERIFY(pSocket);
delete pSocket;
QVERIFY(server.hasPendingConnections());
pSocket = server.nextPendingConnection();
QVERIFY(pSocket);
delete pSocket;
QVERIFY(!server.hasPendingConnections());
QVERIFY(!server.nextPendingConnection());
//will resolve in another commit
#ifndef Q_OS_WIN
QCOMPARE(serverErrorSpy.size(), 1);
QCOMPARE(serverErrorSpy.at(0).at(0).value<QWebSocketProtocol::CloseCode>(),
QWebSocketProtocol::CloseCodeAbnormalDisconnection);
#endif
QCOMPARE(serverClosedSpy.size(), 0);
server.close();
QTRY_COMPARE(serverClosedSpy.size(), 1);
#ifndef QT_NO_SSL
QCOMPARE(peerVerifyErrorSpy.size(), 0);
QCOMPARE(sslErrorsSpy.size(), 0);
QCOMPARE(sslErrorsOccurredSpy.size(), 0);
#endif
QCOMPARE(serverAcceptErrorSpy.size(), 0);
}
void tst_QWebSocketServer::tst_serverDestroyedWhileSocketConnected()
{
if (m_shouldSkipUnsupportedIpv6Test)
QSKIP("Syscalls needed for ipv6 sockoptions missing functionality");
QWebSocketServer * server = new QWebSocketServer(QString(), QWebSocketServer::NonSecureMode);
QSignalSpy serverConnectionSpy(server, SIGNAL(newConnection()));
QSignalSpy corsAuthenticationSpy(server,
SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*)));
QSignalSpy serverClosedSpy(server, SIGNAL(closed()));
QWebSocket socket;
QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected()));
QSignalSpy socketDisconnectedSpy(&socket, SIGNAL(disconnected()));
QVERIFY(server->listen());
QCOMPARE(server->serverAddress(), QHostAddress(QHostAddress::Any));
QCOMPARE(server->serverUrl(), QUrl(QStringLiteral("ws://") + QHostAddress(QHostAddress::LocalHost).toString() +
QStringLiteral(":").append(QString::number(server->serverPort()))));
socket.open(server->serverUrl());
QTRY_COMPARE(socketConnectedSpy.size(), 1);
QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
QCOMPARE(serverConnectionSpy.size(), 1);
QCOMPARE(corsAuthenticationSpy.size(), 1);
QCOMPARE(serverClosedSpy.size(), 0);
delete server;
QTRY_COMPARE(socketDisconnectedSpy.size(), 1);
}
#ifndef QT_NO_SSL
static void setupSecureServer(QWebSocketServer *secureServer)
{
QSslConfiguration sslConfiguration;
QFile certFile(QStringLiteral(":/localhost.cert"));
QFile keyFile(QStringLiteral(":/localhost.key"));
QVERIFY(certFile.open(QIODevice::ReadOnly));
QVERIFY(keyFile.open(QIODevice::ReadOnly));
QSslCertificate certificate(&certFile, QSsl::Pem);
QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem);
certFile.close();
keyFile.close();
sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
sslConfiguration.setLocalCertificate(certificate);
sslConfiguration.setPrivateKey(sslKey);
secureServer->setSslConfiguration(sslConfiguration);
}
#endif
void tst_QWebSocketServer::tst_scheme()
{
if (m_shouldSkipUnsupportedIpv6Test)
QSKIP("Syscalls needed for ipv6 sockoptions missing functionality");
QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode);
QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection()));
QVERIFY(plainServer.listen());
QWebSocket plainSocket;
plainSocket.open(plainServer.serverUrl());
QTRY_COMPARE(plainServerConnectionSpy.size(), 1);
QScopedPointer<QWebSocket> plainServerSocket(plainServer.nextPendingConnection());
QVERIFY(!plainServerSocket.isNull());
QCOMPARE(plainServerSocket->requestUrl().scheme(), QStringLiteral("ws"));
plainServer.close();
#ifndef QT_NO_SSL
QWebSocketServer secureServer(QString(), QWebSocketServer::SecureMode);
setupSecureServer(&secureServer);
if (QTest::currentTestFailed())
return;
QSignalSpy secureServerConnectionSpy(&secureServer, SIGNAL(newConnection()));
QVERIFY(secureServer.listen());
QSslCipher sessionCipher;
QWebSocket secureSocket;
connect(&secureSocket, &QWebSocket::sslErrors,
&secureSocket, [&] {
secureSocket.ignoreSslErrors();
sessionCipher = secureSocket.sslConfiguration().sessionCipher();
});
secureSocket.open(secureServer.serverUrl());
QTRY_COMPARE(secureServerConnectionSpy.size(), 1);
QScopedPointer<QWebSocket> secureServerSocket(secureServer.nextPendingConnection());
QVERIFY(!secureServerSocket.isNull());
QCOMPARE(secureServerSocket->requestUrl().scheme(), QStringLiteral("wss"));
secureServer.close();
QVERIFY(!sessionCipher.isNull());
QWebSocketServer selfSignedServer(QString(), QWebSocketServer::SecureMode);
QSslConfiguration sslConfiguration;
QFile certFile(QStringLiteral(":/selfsigned.cert"));
QFile keyFile(QStringLiteral(":/selfsigned.key"));
QVERIFY(certFile.open(QIODevice::ReadOnly));
QVERIFY(keyFile.open(QIODevice::ReadOnly));
QSslCertificate certificate(&certFile, QSsl::Pem);
QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem);
certFile.close();
keyFile.close();
sslConfiguration.setLocalCertificate(certificate);
sslConfiguration.setPrivateKey(sslKey);
sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer);
selfSignedServer.setSslConfiguration(sslConfiguration);
QSignalSpy selfSignedServerConnectionSpy(&selfSignedServer, SIGNAL(newConnection()));
QSignalSpy selfSignedServerSslErrorsOccurredSpy(
&selfSignedServer, SIGNAL(sslErrorsOccurred(QSslSocket*, QList<QSslError>)));
QVERIFY(selfSignedServer.listen());
QWebSocket selfSignedSocket;
sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
selfSignedSocket.setSslConfiguration(sslConfiguration);
QObject::connect(&selfSignedSocket, &QWebSocket::sslErrors,
&selfSignedSocket, qOverload<>(&QWebSocket::ignoreSslErrors));
selfSignedSocket.open(selfSignedServer.serverUrl());
QTRY_COMPARE(selfSignedServerSslErrorsOccurredSpy.size(), 1);
#endif
}
void tst_QWebSocketServer::tst_handleConnection()
{
QWebSocketServer wsServer(QString(), QWebSocketServer::NonSecureMode);
QSignalSpy wsServerConnectionSpy(&wsServer, &QWebSocketServer::newConnection);
QTcpServer tcpServer;
connect(&tcpServer, &QTcpServer::newConnection,
&wsServer, [&tcpServer, &wsServer]() {
wsServer.handleConnection(tcpServer.nextPendingConnection());
});
QVERIFY(tcpServer.listen());
QWebSocket webSocket;
QSignalSpy wsConnectedSpy(&webSocket, &QWebSocket::connected);
webSocket.open(QUrl{u"ws://localhost:%1"_s.arg(tcpServer.serverPort())});
QTRY_COMPARE(wsConnectedSpy.size(), 1);
QTRY_COMPARE(wsServerConnectionSpy.size(), 1);
QScopedPointer<QWebSocket> webServerSocket(wsServer.nextPendingConnection());
QVERIFY(!webServerSocket.isNull());
QSignalSpy wsMessageReceivedSpy(webServerSocket.data(), &QWebSocket::textMessageReceived);
webSocket.sendTextMessage("dummy");
QTRY_COMPARE(wsMessageReceivedSpy.size(), 1);
QList<QVariant> arguments = wsMessageReceivedSpy.takeFirst();
QCOMPARE(arguments.first().toString(), QString("dummy"));
QSignalSpy clientMessageReceivedSpy(&webSocket, &QWebSocket::textMessageReceived);
webServerSocket->sendTextMessage("hello");
QVERIFY(webServerSocket->bytesToWrite() > 5); // 5 + a few extra bytes for header
QTRY_COMPARE(webServerSocket->bytesToWrite(), 0);
QTRY_COMPARE(clientMessageReceivedSpy.size(), 1);
arguments = clientMessageReceivedSpy.takeFirst();
QCOMPARE(arguments.first().toString(), QString("hello"));
}
struct SocketSpy {
QTcpSocket *socket;
QSignalSpy *disconnectSpy;
~SocketSpy() {
delete socket;
delete disconnectSpy;
}
};
static void openManyConnections(QList<SocketSpy *> *sockets, quint16 port, int numConnections)
{
for (int i = 0; i < numConnections; i++) {
QTcpSocket *c = new QTcpSocket;
QSignalSpy *spy = new QSignalSpy(c, &QTcpSocket::disconnected);
c->connectToHost("127.0.0.1", port);
sockets->append(new SocketSpy{c, spy});
}
}
// Sum the counts together, for better output on failure (e.g. "FAIL: Actual: 49, Expected: 50")
static int sumSocketSpyCount(const QList<SocketSpy *> &sockets)
{
return std::accumulate(sockets.cbegin(), sockets.cend(), 0, [](int c, SocketSpy *s) {
return c + s->disconnectSpy->size();
});
}
void tst_QWebSocketServer::tst_handshakeTimeout()
{
{ // No Timeout
QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode);
plainServer.setHandshakeTimeout(-1);
QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection()));
QVERIFY(plainServer.listen());
QWebSocket socket;
socket.open(plainServer.serverUrl());
QTRY_COMPARE(plainServerConnectionSpy.size(), 1);
QScopedPointer<QWebSocket> plainServerSocket(plainServer.nextPendingConnection());
QVERIFY(!plainServerSocket.isNull());
plainServer.close();
}
{ // Unencrypted
QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode);
plainServer.setHandshakeTimeout(500);
QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection()));
QVERIFY(plainServer.listen());
/* QTcpServer has a default of 30 pending connections. The test checks
* whether, when that list is full, the connections are dropped after
* a timeout and later pending connections are processed. */
const int numConnections = 50;
QList<SocketSpy *> sockets;
auto cleaner = qScopeGuard([&sockets]() { qDeleteAll(sockets); });
openManyConnections(&sockets, plainServer.serverPort(), numConnections);
QCoreApplication::processEvents();
/* We have 50 plain TCP connections open, that are not proper websockets. */
QCOMPARE(plainServerConnectionSpy.size(), 0);
QWebSocket socket;
socket.open(plainServer.serverUrl());
/* Check that a real websocket will be processed after some non-websocket
* TCP connections timeout. */
QTRY_COMPARE(plainServerConnectionSpy.size(), 1);
QScopedPointer<QWebSocket> plainServerSocket(plainServer.nextPendingConnection());
QVERIFY(!plainServerSocket.isNull());
/* Check that all non websocket connections eventually timeout. */
QTRY_COMPARE(sumSocketSpyCount(sockets), numConnections);
plainServer.close();
}
#if QT_CONFIG(ssl)
{ // Encrypted
QWebSocketServer secureServer(QString(), QWebSocketServer::SecureMode);
setupSecureServer(&secureServer);
if (QTest::currentTestFailed())
return;
secureServer.setHandshakeTimeout(2000);
QSignalSpy secureServerConnectionSpy(&secureServer, SIGNAL(newConnection()));
QVERIFY(secureServer.listen());
const int numConnections = 50;
QList<SocketSpy *> sockets;
auto cleaner = qScopeGuard([&sockets]() { qDeleteAll(sockets); });
openManyConnections(&sockets, secureServer.serverPort(), numConnections);
QCoreApplication::processEvents();
QCOMPARE(secureServerConnectionSpy.size(), 0);
QWebSocket secureSocket;
connect(&secureSocket, &QWebSocket::errorOccurred,
this, [](QAbstractSocket::SocketError error) {
// This shouldn't print but it's useful for debugging when/if it does.
qDebug() << "Error occurred in the client:" << error;
});
QSslConfiguration config = secureSocket.sslConfiguration();
config.setPeerVerifyMode(QSslSocket::VerifyNone);
secureSocket.setSslConfiguration(config);
secureSocket.open(secureServer.serverUrl());
QTRY_COMPARE(secureServerConnectionSpy.size(), 1);
QScopedPointer<QWebSocket> serverSocket(secureServer.nextPendingConnection());
QVERIFY(!serverSocket.isNull());
QTRY_COMPARE(sumSocketSpyCount(sockets), numConnections);
secureServer.close();
}
#endif
{ // Ensure properly handshaked connections are not timed out
QWebSocketServer plainServer(QString(), QWebSocketServer::NonSecureMode);
plainServer.setHandshakeTimeout(250);
QSignalSpy plainServerConnectionSpy(&plainServer, SIGNAL(newConnection()));
QVERIFY(plainServer.listen());
QWebSocket socket;
QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected);
QSignalSpy socketDisconnectedSpy(&socket, &QWebSocket::disconnected);
socket.open(plainServer.serverUrl());
QTRY_COMPARE(plainServerConnectionSpy.size(), 1);
QTRY_COMPARE(socketConnectedSpy.size(), 1);
QEventLoop loop;
QTimer::singleShot(500, &loop, &QEventLoop::quit);
loop.exec();
QCOMPARE(socketDisconnectedSpy.size(), 0);
}
}
static QByteArray handshakeRequest()
{
return "GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n"_ba;
}
// A server and its signal spies, which is used in handshake tests below.
class HandshakeTestServer
{
public:
HandshakeTestServer()
: connectionSpy(&server, &QWebSocketServer::newConnection),
errorSpy(&server, &QWebSocketServer::serverError)
{
server.setHandshakeTimeout(-1);
}
// Let the server accept the client itself.
std::unique_ptr<QTcpSocket> acceptedByServer()
{
if (!server.listen(QHostAddress::LocalHost))
return {};
auto client = std::make_unique<QTcpSocket>();
client->connectToHost(QHostAddress::LocalHost, server.serverPort());
if (!client->waitForConnected())
return {};
return client;
}
// Accepts the client here and hands it over with handleConnection(). Keeping the
// accepted socket is what lets a test deliver one readyRead at a time.
std::unique_ptr<QTcpSocket> handedOverToServer()
{
if (!tcpServer.listen(QHostAddress::LocalHost))
return {};
auto client = std::make_unique<QTcpSocket>();
client->connectToHost(QHostAddress::LocalHost, tcpServer.serverPort());
if (!client->waitForConnected() || !tcpServer.waitForNewConnection(5000))
return {};
handedOverSocket = tcpServer.nextPendingConnection();
if (!handedOverSocket)
return {};
server.handleConnection(handedOverSocket);
return client;
}
// Writes one fragment and lets the server run its queued reader exactly once, so
// the test can check what the server did with that fragment alone.
bool writeFragment(QTcpSocket *client, QByteArrayView fragment)
{
if (client->write(fragment.data(), fragment.size()) != fragment.size())
return false;
qint64 previouslyReceived = handedOverSocket->bytesAvailable();
while (client->bytesToWrite() != 0) {
if (!client->waitForBytesWritten() || !handedOverSocket->waitForReadyRead())
return false;
}
while (handedOverSocket->bytesAvailable() != fragment.size() + previouslyReceived) {
if (!handedOverSocket->waitForReadyRead())
return false;
}
deliverQueuedReads();
return true;
}
void deliverQueuedReads()
{
QCoreApplication::processEvents();
}
QWebSocketServer server{ QString(), QWebSocketServer::NonSecureMode };
QSignalSpy connectionSpy;
QSignalSpy errorSpy;
// handedOverToServer() accepts the client here rather than in the QWebSocketServer,
// so that the test owns the connection it then hands over.
QTcpServer tcpServer;
QPointer<QTcpSocket> handedOverSocket;
};
void tst_QWebSocketServer::fragmentedHandshakeOnHandedOverSocket_data()
{
QTest::addColumn<int>("splitPosition");
QTest::newRow("one-byte-at-a-time") << 0;
for (int i = 1; i < 4; ++i)
QTest::addRow("terminator-split-%d", i) << int(handshakeRequest().size() - 4 + i);
}
void tst_QWebSocketServer::fragmentedHandshakeOnHandedOverSocket()
{
QFETCH(const int, splitPosition);
HandshakeTestServer test;
const std::unique_ptr<QTcpSocket> client = test.handedOverToServer();
QVERIFY(client);
const QByteArray request = handshakeRequest();
QList<qsizetype> fragmentSizes;
// Send one byte at a time
if (splitPosition == 0)
fragmentSizes.resize(request.size(), 1);
else
fragmentSizes = { splitPosition, request.size() - splitPosition };
// Nothing may be upgraded or rejected before the terminator arrives, wherever
// the fragment boundaries happen to fall.
qsizetype sent = 0;
for (qsizetype size : fragmentSizes) {
QVERIFY(test.writeFragment(client.get(), QByteArrayView(request).sliced(sent, size)));
sent += size;
QCOMPARE(test.connectionSpy.size(), sent == request.size() ? 1 : 0);
QCOMPARE(test.errorSpy.size(), 0);
}
const std::unique_ptr<QWebSocket> socket(test.server.nextPendingConnection());
QVERIFY(socket);
QCOMPARE(socket->requestUrl().path(), "/"_L1);
}
void tst_QWebSocketServer::oversizedHandshakeOnHandedOverSocket_data()
{
QTest::addColumn<bool>("complete");
QTest::addColumn<bool>("fragmented");
QTest::newRow("incomplete-at-limit") << false << false;
QTest::newRow("complete-at-limit") << true << false;
QTest::newRow("incomplete-crossing-limit") << false << true;
QTest::newRow("complete-crossing-limit") << true << true;
}
void tst_QWebSocketServer::oversizedHandshakeOnHandedOverSocket()
{
QFETCH(const bool, complete);
QFETCH(const bool, fragmented);
HandshakeTestServer test;
const std::unique_ptr<QTcpSocket> client = test.handedOverToServer();
QVERIFY(client);
// Pad the request out to exactly the header ceiling: 100 lines of 8 KiB plus the
// terminator. The limit itself has to be rejected, not just sizes past it.
constexpr qsizetype limit = 100 * 8 * 1024 + 4;
constexpr qsizetype terminatorSize = 4;
QByteArray request = handshakeRequest();
request.chop(2);
request += "X-Padding: ";
const qsizetype paddingSize = limit - request.size() - (complete ? terminatorSize : 0);
request += QByteArray(paddingSize, 'x');
if (complete)
request += "\r\n\r\n";
QCOMPARE(request.size(), limit);
if (fragmented) {
// Everything except the final byte stays one below the ceiling, so it must
// all be accepted.
constexpr qsizetype fragmentSize = 4096;
qsizetype sent = 0;
while (sent < limit - 1) {
const qsizetype remainingBelowLimit = limit - 1 - sent;
const qsizetype size = qMin(fragmentSize, remainingBelowLimit);
QVERIFY(test.writeFragment(client.get(),
QByteArrayView(request).sliced(sent, size)));
sent += size;
QCOMPARE(test.errorSpy.size(), 0);
QCOMPARE(test.connectionSpy.size(), 0);
}
// The byte that reaches the ceiling is the one that must be refused.
QCOMPARE(client->write(request.last(1)), 1);
client->flush();
} else {
QCOMPARE(client->write(request), limit);
}
QTRY_COMPARE(test.errorSpy.size(), 1);
QCOMPARE(test.errorSpy.first().first().value<QWebSocketProtocol::CloseCode>(),
QWebSocketProtocol::CloseCodeTooMuchData);
QTRY_COMPARE(client->state(), QAbstractSocket::UnconnectedState);
QCOMPARE(test.connectionSpy.size(), 0);
}
void tst_QWebSocketServer::disconnectDuringHandshakeOnHandedOverSocket_data()
{
QTest::addColumn<bool>("completeRequest");
QTest::addColumn<bool>("queuedRead");
QTest::newRow("partial, after read") << false << false;
QTest::newRow("partial, before queued read") << false << true;
// A request that is complete has to be dropped just the same: the peer is gone,
// so there is nobody left to send the response to.
QTest::newRow("complete, before queued read") << true << true;
}
void tst_QWebSocketServer::disconnectDuringHandshakeOnHandedOverSocket()
{
// queuedRead leaves a readyRead queued when the peer goes away, so the server
// reads the request only after it has handled disconnected(). Which of the two
// orders occurs is platform dependent, so both are exercised everywhere.
QFETCH(const bool, completeRequest);
QFETCH(const bool, queuedRead);
HandshakeTestServer test;
const std::unique_ptr<QTcpSocket> client = test.handedOverToServer();
QVERIFY(client);
// Send the request, optionally without letting the server read it yet.
const QByteArray request = completeRequest ? handshakeRequest()
: handshakeRequest().first(40);
QCOMPARE(client->write(request), request.size());
while (client->bytesToWrite())
QVERIFY(client->waitForBytesWritten());
while (test.handedOverSocket->bytesAvailable() < request.size())
QVERIFY(test.handedOverSocket->waitForReadyRead());
if (!queuedRead)
test.deliverQueuedReads();
// Drop the peer mid-handshake and let anything still queued be delivered.
test.handedOverSocket->disconnectFromHost();
QCOMPARE(test.handedOverSocket->state(), QAbstractSocket::UnconnectedState);
test.deliverQueuedReads();
QTRY_VERIFY(test.handedOverSocket.isNull());
QCOMPARE(test.connectionSpy.size(), 0);
QCOMPARE(test.errorSpy.size(), 0);
// The abandoned handshake must not affect the next one.
QVERIFY(test.server.listen(QHostAddress::LocalHost));
QWebSocket nextClient;
nextClient.open(test.server.serverUrl());
QTRY_COMPARE(test.connectionSpy.size(), 1);
const std::unique_ptr<QWebSocket> socket(test.server.nextPendingConnection());
QVERIFY(socket);
QCOMPARE(test.errorSpy.size(), 0);
}
void tst_QWebSocketServer::handshakeFollowedByFrame()
{
HandshakeTestServer test;
const std::unique_ptr<QTcpSocket> client = test.acceptedByServer();
QVERIFY(client);
// Start a masked text frame in the same write as the handshake, so reading the
// request must not consume the frame bytes that follow the terminator. The frame
// is left one byte short until the message spy exists, so it cannot be missed.
const QByteArray request = handshakeRequest() + QByteArray::fromHex("81820000000068");
QCOMPARE(client->write(request), request.size());
QTRY_COMPARE(test.connectionSpy.size(), 1);
const std::unique_ptr<QWebSocket> socket(test.server.nextPendingConnection());
QVERIFY(socket);
QSignalSpy messageSpy(socket.get(), &QWebSocket::textMessageReceived);
QCOMPARE(client->write("i", 1), 1);
QTRY_COMPARE(messageSpy.size(), 1);
QCOMPARE(messageSpy.first().first().toString(), "hi"_L1);
QCOMPARE(test.errorSpy.size(), 0);
}
void tst_QWebSocketServer::gentleClose()
{
QWebSocketServer server("gentleClose", QWebSocketServer::NonSecureMode);
QSignalSpy serverConnectionSpy(&server, &QWebSocketServer::newConnection);
QVERIFY(server.listen());
QWebSocket client;
QSignalSpy connectedSpy(&client, &QWebSocket::connected);
QSignalSpy clientDisconnectedSpy(&client, &QWebSocket::disconnected);
QSignalSpy clientErrorSpy(&client, &QWebSocket::errorOccurred);
client.open(server.serverUrl());
QVERIFY(serverConnectionSpy.wait());
QVERIFY(connectedSpy.wait());
QWebSocket *serverSocket = server.nextPendingConnection();
QSignalSpy disconnectedSpy(serverSocket, &QWebSocket::disconnected);
QSignalSpy serverErrorSpy(serverSocket, &QWebSocket::errorOccurred);
client.close(QWebSocketProtocol::CloseCodeNormal);
QVERIFY(disconnectedSpy.wait());
QVERIFY(clientDisconnectedSpy.wait());
QVERIFY(client.closeCode() != QWebSocketProtocol::CloseCodeGoingAway);
QVERIFY(serverSocket->closeCode() != QWebSocketProtocol::CloseCodeGoingAway);
QVERIFY(client.error() != QAbstractSocket::RemoteHostClosedError);
QVERIFY(serverSocket->error() != QAbstractSocket::RemoteHostClosedError);
QCOMPARE(clientErrorSpy.count(), 0);
QCOMPARE(serverErrorSpy.count(), 0);
}
void tst_QWebSocketServer::multipleFrames()
{
QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode);
QSignalSpy serverConnectionSpy(&server, &QWebSocketServer::newConnection);
QVERIFY(server.listen());
QWebSocket socket;
QSignalSpy socketConnectedSpy(&socket, &QWebSocket::connected);
QSignalSpy messageReceivedSpy(&socket, &QWebSocket::binaryMessageReceived);
socket.open(server.serverUrl());
QVERIFY(serverConnectionSpy.wait());
QVERIFY(socketConnectedSpy.wait());
auto serverSocket = std::unique_ptr<QWebSocket>(server.nextPendingConnection());
QVERIFY(serverSocket);
for (int i = 0; i < 10; i++)
serverSocket->sendBinaryMessage(QByteArray("abc"));
if (serverSocket->bytesToWrite())
QVERIFY(serverSocket->flush());
QVERIFY(messageReceivedSpy.wait());
// Since there's no guarantee the operating system will fit all 10 websocket frames into 1 tcp
// frame, let's just assume it will do at least 2. EXCEPT_FAIL any which doesn't merge any.
QVERIFY2(messageReceivedSpy.size() > 1, "Received only 1 message in the TCP frame!");
}
QTEST_MAIN(tst_QWebSocketServer)
#include "tst_qwebsocketserver.moc"