blob: 70466579a8c7f9ca26dd02cce705f7bc549f57b8 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QtTest/qtestcase.h>
#include <QDebug>
#include <QByteArray>
#include <QtEndian>
#include "private/qwebsocketframe_p.h"
#include "private/qwebsocketprotocol_p.h"
#include "qwebsocketprotocol.h"
QT_USE_NAMESPACE
Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode)
Q_DECLARE_METATYPE(QWebSocketProtocol::OpCode)
/*!
* \brief class FrameHelper is used to encode a single frame.
*
* \internal
*/
class FrameHelper
{
public:
FrameHelper();
QByteArray wireRepresentation();
void setRsv1(int value) { m_rsv1 = value; }
void setRsv2(int value) { m_rsv2 = value; }
void setRsv3(int value) { m_rsv3 = value; }
void setMask(quint32 mask) { m_mask = mask; }
void setOpCode(QWebSocketProtocol::OpCode opCode) { m_opCode = opCode; }
void setPayload(const QByteArray &payload) { m_payload = payload; }
void setFinalFrame(bool isFinal) { m_isFinalFrame = isFinal; }
private:
int m_rsv1;
int m_rsv2;
int m_rsv3;
quint32 m_mask;
QWebSocketProtocol::OpCode m_opCode;
QByteArray m_payload;
bool m_isFinalFrame;
};
FrameHelper::FrameHelper() :
m_rsv1(0), m_rsv2(0), m_rsv3(0),
m_mask(0), m_opCode(QWebSocketProtocol::OpCodeReserved3),
m_payload(), m_isFinalFrame(false)
{}
QByteArray FrameHelper::wireRepresentation()
{
quint8 byte = 0x00;
QByteArray wireRep;
quint64 payloadLength = m_payload.length();
//FIN, opcode
byte = static_cast<quint8>((m_opCode & 0x0F) | (m_isFinalFrame ? 0x80 : 0x00)); //FIN, opcode
//RSV1-3
byte |= static_cast<quint8>(((m_rsv1 & 0x01) << 6) | ((m_rsv2 & 0x01) << 5) |
((m_rsv3 & 0x01) << 4));
wireRep.append(static_cast<char>(byte));
byte = 0x00;
if (m_mask != 0)
{
byte |= 0x80;
}
if (payloadLength <= 125)
{
byte |= static_cast<quint8>(payloadLength);
wireRep.append(static_cast<char>(byte));
}
else if (payloadLength <= 0xFFFFU)
{
byte |= 126;
wireRep.append(static_cast<char>(byte));
quint16 swapped = qToBigEndian<quint16>(static_cast<quint16>(payloadLength));
wireRep.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 2);
}
else
{
byte |= 127;
wireRep.append(static_cast<char>(byte));
quint64 swapped = qToBigEndian<quint64>(payloadLength);
wireRep.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 8);
}
//Write mask
if (m_mask != 0)
{
wireRep.append(static_cast<const char *>(static_cast<const void *>(&m_mask)),
sizeof(quint32));
}
QByteArray tmpData = m_payload;
if (m_mask)
{
tmpData.detach();
QWebSocketProtocol::mask(&tmpData, m_mask);
}
wireRep.append(tmpData);
return wireRep;
}
class tst_WebSocketFrame : public QObject
{
Q_OBJECT
public:
tst_WebSocketFrame();
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void init();
void cleanup();
void tst_initialization();
void tst_copyConstructorAndAssignment();
void tst_goodFrames_data();
void tst_goodFrames();
void tst_invalidFrames_data();
void tst_invalidFrames();
void tst_malformedFrames_data();
void tst_malformedFrames();
};
tst_WebSocketFrame::tst_WebSocketFrame()
{}
void tst_WebSocketFrame::initTestCase()
{
}
void tst_WebSocketFrame::cleanupTestCase()
{}
void tst_WebSocketFrame::init()
{
qRegisterMetaType<QWebSocketProtocol::OpCode>("QWebSocketProtocol::OpCode");
qRegisterMetaType<QWebSocketProtocol::CloseCode>("QWebSocketProtocol::CloseCode");
}
void tst_WebSocketFrame::cleanup()
{
}
void tst_WebSocketFrame::tst_initialization()
{
QWebSocketFrame frame;
QVERIFY(!frame.isValid());
QCOMPARE(frame.payload().length(), 0);
}
void tst_WebSocketFrame::tst_copyConstructorAndAssignment()
{
FrameHelper frameHelper;
frameHelper.setRsv1(0);
frameHelper.setRsv2(0);
frameHelper.setRsv3(0);
frameHelper.setFinalFrame(true);
frameHelper.setMask(1234u);
frameHelper.setOpCode(QWebSocketProtocol::OpCodeBinary);
frameHelper.setPayload(QByteArrayLiteral("12345"));
QByteArray payload = frameHelper.wireRepresentation();
QBuffer buffer(&payload);
buffer.open(QIODevice::ReadOnly);
QWebSocketFrame frame;
frame.readFrame(&buffer);
buffer.close();
auto compareFrames = [](const QWebSocketFrame &other, const QWebSocketFrame &frame)
{
QCOMPARE(other.closeCode(), frame.closeCode());
QCOMPARE(other.closeReason(), frame.closeReason());
QCOMPARE(other.hasMask(), frame.hasMask());
QCOMPARE(other.isContinuationFrame(), frame.isContinuationFrame());
QCOMPARE(other.isControlFrame(), frame.isControlFrame());
QCOMPARE(other.isDataFrame(), frame.isDataFrame());
QCOMPARE(other.isFinalFrame(), frame.isFinalFrame());
QCOMPARE(other.isValid(), frame.isValid());
QCOMPARE(other.mask(), frame.mask());
QCOMPARE(other.opCode(), frame.opCode());
QCOMPARE(other.payload(), frame.payload());
QCOMPARE(other.rsv1(), frame.rsv1());
QCOMPARE(other.rsv2(), frame.rsv2());
QCOMPARE(other.rsv3(), frame.rsv3());
};
{
QWebSocketFrame other(frame);
compareFrames(other, frame);
}
{
QWebSocketFrame other;
other = frame;
compareFrames(other, frame);
QWebSocketFrame other2 = std::move(other);
compareFrames(other2, frame);
QWebSocketFrame other3(std::move(other2));
compareFrames(other3, frame);
}
}
void tst_WebSocketFrame::tst_goodFrames_data()
{
QTest::addColumn<bool>("rsv1");
QTest::addColumn<bool>("rsv2");
QTest::addColumn<bool>("rsv3");
QTest::addColumn<quint32>("mask");
QTest::addColumn<QWebSocketProtocol::OpCode>("opCode");
QTest::addColumn<bool>("isFinal");
QTest::addColumn<QByteArray>("payload");
QTest::addColumn<bool>("isControlFrame");
QTest::addColumn<bool>("isDataFrame");
QTest::addColumn<bool>("isContinuationFrame");
QTest::newRow("Non masked final text frame with small payload")
<< false << false << false
<< 0U << QWebSocketProtocol::OpCodeText
<< true << QStringLiteral("Hello world!").toUtf8()
<< false << true << false;
QTest::newRow("Non masked final binary frame with small payload")
<< false << false << false
<< 0U << QWebSocketProtocol::OpCodeBinary
<< true << QByteArrayLiteral("\x00\x01\x02\x03\x04")
<< false << true << false;
QTest::newRow("Non masked final text frame with no payload")
<< false << false << false
<< 0U << QWebSocketProtocol::OpCodeText
<< true << QByteArray()
<< false << true << false;
QTest::newRow("Non masked final binary frame with no payload")
<< false << false << false
<< 0U << QWebSocketProtocol::OpCodeBinary
<< true << QByteArray()
<< false << true << false;
QTest::newRow("Non masked final close frame with small payload")
<< false << false << false
<< 0U << QWebSocketProtocol::OpCodeClose
<< true << QStringLiteral("Hello world!").toUtf8()
<< true << false << false;
QTest::newRow("Non masked final close frame with no payload")
<< false << false << false
<< 0U << QWebSocketProtocol::OpCodeClose
<< true << QByteArray()
<< true << false << false;
QTest::newRow("Non masked final ping frame with small payload")
<< false << false << false
<< 0U << QWebSocketProtocol::OpCodePing
<< true << QStringLiteral("Hello world!").toUtf8()
<< true << false << false;
QTest::newRow("Non masked final pong frame with no payload")
<< false << false << false
<< 0U << QWebSocketProtocol::OpCodePong
<< true << QByteArray()
<< true << false << false;
QTest::newRow("Non masked final continuation frame with small payload")
<< false << false << false
<< 0U << QWebSocketProtocol::OpCodeContinue
<< true << QStringLiteral("Hello world!").toUtf8()
<< false << true << true;
QTest::newRow("Non masked non-final continuation frame with small payload")
<< false << false << false
<< 0U << QWebSocketProtocol::OpCodeContinue
<< false << QStringLiteral("Hello world!").toUtf8()
<< false << true << true;
}
void tst_WebSocketFrame::tst_goodFrames()
{
QFETCH(bool, rsv1);
QFETCH(bool, rsv2);
QFETCH(bool, rsv3);
QFETCH(quint32, mask);
QFETCH(QWebSocketProtocol::OpCode, opCode);
QFETCH(bool, isFinal);
QFETCH(QByteArray, payload);
QFETCH(bool, isControlFrame);
QFETCH(bool, isDataFrame);
QFETCH(bool, isContinuationFrame);
FrameHelper helper;
helper.setRsv1(rsv1);
helper.setRsv2(rsv2);
helper.setRsv3(rsv3);
helper.setMask(mask);
helper.setOpCode(opCode);
helper.setFinalFrame(isFinal);
helper.setPayload(payload);
QByteArray wireRepresentation = helper.wireRepresentation();
QBuffer buffer;
buffer.setData(wireRepresentation);
buffer.open(QIODevice::ReadOnly);
QWebSocketFrame frame;
frame.readFrame(&buffer);
buffer.close();
QVERIFY(frame.isValid());
QCOMPARE(frame.rsv1(), rsv1);
QCOMPARE(frame.rsv2(), rsv2);
QCOMPARE(frame.rsv3(), rsv3);
QCOMPARE(frame.hasMask(), (mask != 0));
QCOMPARE(frame.opCode(), opCode);
QCOMPARE(frame.isFinalFrame(), isFinal);
QCOMPARE(frame.isControlFrame(), isControlFrame);
QCOMPARE(frame.isDataFrame(), isDataFrame);
QCOMPARE(frame.isContinuationFrame(), isContinuationFrame);
QCOMPARE(frame.payload().length(), payload.length());
QCOMPARE(frame.payload(), payload);
}
void tst_WebSocketFrame::tst_invalidFrames_data()
{
QTest::addColumn<int>("rsv1");
QTest::addColumn<int>("rsv2");
QTest::addColumn<int>("rsv3");
QTest::addColumn<quint32>("mask");
QTest::addColumn<QWebSocketProtocol::OpCode>("opCode");
QTest::addColumn<bool>("isFinal");
QTest::addColumn<QByteArray>("payload");
QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedError");
QTest::newRow("RSV1 != 0")
<< 1 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeText
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("RSV2 != 0")
<< 0 << 1 << 0
<< 0U << QWebSocketProtocol::OpCodeText
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("RSV3 != 0")
<< 0 << 0 << 1
<< 0U << QWebSocketProtocol::OpCodeText
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("RSV1 != 0 and RSV2 != 0")
<< 1 << 1 << 0
<< 0U << QWebSocketProtocol::OpCodeText
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("RSV1 != 0 and RSV3 != 0")
<< 1 << 0 << 1
<< 0U << QWebSocketProtocol::OpCodeText
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("RSV2 != 0 and RSV3 != 0")
<< 0 << 1 << 1
<< 0U << QWebSocketProtocol::OpCodeText
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Reserved OpCode 3")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeReserved3
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Reserved OpCode 4")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeReserved4
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Reserved OpCode 5")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeReserved5
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Reserved OpCode 6")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeReserved6
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Reserved OpCode 7")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeReserved7
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Reserved OpCode B")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeReservedB
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Reserved OpCode C")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeReservedC
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Reserved OpCode D")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeReservedD
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Reserved OpCode E")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeReservedE
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Reserved OpCode F")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeReservedF
<< true << QStringLiteral("Hello world!").toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Close Frame with payload > 125 bytes")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeClose
<< true << QString(126, 'a').toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Non-final Close Frame")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeClose
<< false << QString(126, 'a').toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Ping Frame with payload > 125 bytes")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodePing
<< true << QString(126, 'a').toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Non-final Ping Frame")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodePing
<< false << QString(126, 'a').toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Pong Frame with payload > 125 bytes")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodePong
<< true << QString(126, 'a').toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
QTest::newRow("Non-final Pong Frame")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodePong
<< false << QString(126, 'a').toUtf8()
<< QWebSocketProtocol::CloseCodeProtocolError;
}
void tst_WebSocketFrame::tst_invalidFrames()
{
QFETCH(int, rsv1);
QFETCH(int, rsv2);
QFETCH(int, rsv3);
QFETCH(quint32, mask);
QFETCH(QWebSocketProtocol::OpCode, opCode);
QFETCH(bool, isFinal);
QFETCH(QByteArray, payload);
QFETCH(QWebSocketProtocol::CloseCode, expectedError);
FrameHelper helper;
helper.setRsv1(rsv1);
helper.setRsv2(rsv2);
helper.setRsv3(rsv3);
helper.setMask(mask);
helper.setOpCode(opCode);
helper.setFinalFrame(isFinal);
helper.setPayload(payload);
QByteArray wireRepresentation = helper.wireRepresentation();
QBuffer buffer;
buffer.setData(wireRepresentation);
buffer.open(QIODevice::ReadOnly);
QWebSocketFrame frame;
frame.readFrame(&buffer);
buffer.close();
QVERIFY(!frame.isValid());
QCOMPARE(frame.closeCode(), expectedError);
}
/*
* Incomplete or overly large frames
* Payload must be crafted manually
*
QTest::newRow("Frame Too Big")
<< 0 << 0 << 0
<< 0U << QWebSocketProtocol::OpCodeText
<< true << QString(MAX_FRAME_SIZE_IN_BYTES + 1, 'a').toUtf8()
<< QWebSocketProtocol::CloseCodeTooMuchData;
*/
void tst_WebSocketFrame::tst_malformedFrames_data()
{
QTest::addColumn<QByteArray>("payload");
QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedError");
//too little data
QTest::newRow("No data") << QByteArray() << QWebSocketProtocol::CloseCodeGoingAway;
FrameHelper helper;
helper.setRsv1(0);
helper.setRsv2(0);
helper.setRsv3(0);
helper.setMask(0U);
helper.setOpCode(QWebSocketProtocol::OpCodeText);
helper.setFinalFrame(true);
helper.setPayload(QString(10, 'a').toUtf8());
QByteArray wireRep = helper.wireRepresentation();
//too little data
//header + payload should be 12 bytes for non-masked payloads < 126 bytes
for (int i = 1; i < 12; ++i)
{
QTest::newRow(QStringLiteral("Header too small - %1 byte(s)").arg(i).toLatin1().constData())
<< wireRep.left(i)
<< QWebSocketProtocol::CloseCodeGoingAway;
}
//too much data
{
const char bigpayloadIndicator = char(127);
const quint64 payloadSize = QWebSocketFrame::maxFrameSize() + 1;
uchar swapped[8] = {0};
qToBigEndian<quint64>(payloadSize, swapped);
QTest::newRow("Frame too big")
<< wireRep.left(1).append(bigpayloadIndicator)
.append(reinterpret_cast<char *>(swapped), 8)
<< QWebSocketProtocol::CloseCodeTooMuchData;
}
//invalid size field
{
const char bigpayloadIndicator = char(127);
quint64 payloadSize = quint64(1) << 63;
uchar swapped[8] = {0};
qToBigEndian<quint64>(payloadSize, swapped);
QTest::newRow("Highest bit of payload length is set")
<< wireRep.left(1).append(bigpayloadIndicator)
.append(reinterpret_cast<char *>(swapped), 8)
<< QWebSocketProtocol::CloseCodeProtocolError;
payloadSize = 256;
qToBigEndian<quint64>(payloadSize, swapped);
QTest::newRow("Overlong 64-bit size field; should be 16-bit")
<< wireRep.left(1).append(bigpayloadIndicator)
.append(reinterpret_cast<char *>(swapped), 8)
<< QWebSocketProtocol::CloseCodeProtocolError;
}
//overlong size field
{
const char largepayloadIndicator = char(126);
const quint16 payloadSize = 120;
uchar swapped[2] = {0};
qToBigEndian<quint16>(payloadSize, swapped);
QTest::newRow("Overlong 16-bit size field")
<< wireRep.left(1).append(largepayloadIndicator)
.append(reinterpret_cast<char *>(swapped), 2)
<< QWebSocketProtocol::CloseCodeProtocolError;
}
{
const char bigpayloadIndicator = char(127);
quint64 payloadSize = 120;
uchar swapped[8] = {0};
qToBigEndian<quint64>(payloadSize, swapped);
QTest::newRow("Overlong 64-bit size field; should be 7-bit")
<< wireRep.left(1).append(bigpayloadIndicator)
.append(reinterpret_cast<char *>(swapped), 8)
<< QWebSocketProtocol::CloseCodeProtocolError;
payloadSize = 256;
qToBigEndian<quint64>(payloadSize, swapped);
QTest::newRow("Overlong 64-bit size field; should be 16-bit")
<< wireRep.left(1).append(bigpayloadIndicator)
.append(reinterpret_cast<char *>(swapped), 8)
<< QWebSocketProtocol::CloseCodeProtocolError;
}
}
void tst_WebSocketFrame::tst_malformedFrames()
{
QFETCH(QByteArray, payload);
QFETCH(QWebSocketProtocol::CloseCode, expectedError);
QBuffer buffer;
buffer.setData(payload);
buffer.open(QIODevice::ReadOnly);
QWebSocketFrame frame;
frame.readFrame(&buffer);
buffer.close();
QVERIFY(!frame.isValid());
QCOMPARE(frame.closeCode(), expectedError);
}
QTEST_MAIN(tst_WebSocketFrame)
#include "tst_websocketframe.moc"