Revision control
++ // Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ #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.size();
++
++ //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().size(), 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().size(), payload.size());
++ 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"
++