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 = QWebSocketFrame::readFrame(&buffer); buffer.close(); QVERIFY(!frame.isValid()); QCOMPARE(frame.closeCode(), expectedError); }
void tst_WebSocketFrame::tst_malformedFrames() { QFETCH(QByteArray, payload); QFETCH(QWebSocketProtocol::CloseCode, expectedError); QBuffer buffer; buffer.setData(payload); buffer.open(QIODevice::ReadOnly); QWebSocketFrame frame = QWebSocketFrame::readFrame(&buffer); buffer.close(); QVERIFY(!frame.isValid()); QCOMPARE(frame.closeCode(), expectedError); }
/*! \internal */ QWebSocketFrame QWebSocketFrame::readFrame(QIODevice *pIoDevice) { bool isDone = false; qint64 bytesRead = 0; QWebSocketFrame frame; quint64 dataWaitSize = 0; Q_UNUSED(dataWaitSize); // value is used in MACRO, Q_UNUSED to avoid compiler warnings ProcessingState processingState = PS_READ_HEADER; ProcessingState returnState = PS_READ_HEADER; bool hasMask = false; quint64 payloadLength = 0; while (!isDone) { switch (processingState) { case PS_WAIT_FOR_MORE_DATA: //TODO: waitForReadyRead should really be changed //now, when a websocket is used in a GUI thread //the GUI will hang for at most 5 seconds //maybe, a QStateMachine should be used if (!pIoDevice->waitForReadyRead(5000)) { frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Timeout when reading data from socket.")); processingState = PS_DISPATCH_RESULT; } else { processingState = returnState; } break; case PS_READ_HEADER: if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) { //FIN, RSV1-3, Opcode char header[2] = {0}; bytesRead = pIoDevice->read(header, 2); frame.m_isFinalFrame = (header[0] & 0x80) != 0; frame.m_rsv1 = (header[0] & 0x40); frame.m_rsv2 = (header[0] & 0x20); frame.m_rsv3 = (header[0] & 0x10); frame.m_opCode = static_cast<QWebSocketProtocol::OpCode>(header[0] & 0x0F); //Mask, PayloadLength hasMask = (header[1] & 0x80) != 0; frame.m_length = (header[1] & 0x7F); switch (frame.m_length) { case 126: { processingState = PS_READ_PAYLOAD_LENGTH; break; } case 127: { processingState = PS_READ_BIG_PAYLOAD_LENGTH; break; } default: { payloadLength = frame.m_length; processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; break; } } if (!frame.checkValidity()) processingState = PS_DISPATCH_RESULT; } else { WAIT_FOR_MORE_DATA(2); } break; case PS_READ_PAYLOAD_LENGTH: if (Q_LIKELY(pIoDevice->bytesAvailable() >= 2)) { uchar length[2] = {0}; bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 2); if (Q_UNLIKELY(bytesRead == -1)) { frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Error occurred while reading from the network: %1") .arg(pIoDevice->errorString())); processingState = PS_DISPATCH_RESULT; } else { payloadLength = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(length)); if (Q_UNLIKELY(payloadLength < 126)) { //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2 //"in all cases, the minimal number of bytes MUST be used to encode //the length, for example, the length of a 124-byte-long string //can't be encoded as the sequence 126, 0, 124" frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Lengths smaller than 126 " \ "must be expressed as one byte.")); processingState = PS_DISPATCH_RESULT; } else { processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; } } } else { WAIT_FOR_MORE_DATA(2); } break; case PS_READ_BIG_PAYLOAD_LENGTH: if (Q_LIKELY(pIoDevice->bytesAvailable() >= 8)) { uchar length[8] = {0}; bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 8); if (Q_UNLIKELY(bytesRead < 8)) { frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION, QObject::tr("Something went wrong during "\ "reading from the network.")); processingState = PS_DISPATCH_RESULT; } else { //Most significant bit must be set to 0 as //per http://tools.ietf.org/html/rfc6455#section-5.2 payloadLength = qFromBigEndian<quint64>(length); if (Q_UNLIKELY(payloadLength & (quint64(1) << 63))) { frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Highest bit of payload length is not 0.")); processingState = PS_DISPATCH_RESULT; } else if (Q_UNLIKELY(payloadLength <= 0xFFFFu)) { //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2 //"in all cases, the minimal number of bytes MUST be used to encode //the length, for example, the length of a 124-byte-long string //can't be encoded as the sequence 126, 0, 124" frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Lengths smaller than 65536 (2^16) " \ "must be expressed as 2 bytes.")); processingState = PS_DISPATCH_RESULT; } else { processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; } } } else { WAIT_FOR_MORE_DATA(8); } break; case PS_READ_MASK: if (Q_LIKELY(pIoDevice->bytesAvailable() >= 4)) { bytesRead = pIoDevice->read(reinterpret_cast<char *>(&frame.m_mask), sizeof(frame.m_mask)); if (bytesRead == -1) { frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Error while reading from the network: %1.") .arg(pIoDevice->errorString())); processingState = PS_DISPATCH_RESULT; } else { frame.m_mask = qFromBigEndian(frame.m_mask); processingState = PS_READ_PAYLOAD; } } else { WAIT_FOR_MORE_DATA(4); } break; case PS_READ_PAYLOAD: if (!payloadLength) { processingState = PS_DISPATCH_RESULT; } else if (Q_UNLIKELY(payloadLength > MAX_FRAME_SIZE_IN_BYTES)) { frame.setError(QWebSocketProtocol::CC_TOO_MUCH_DATA, QObject::tr("Maximum framesize exceeded.")); processingState = PS_DISPATCH_RESULT; } else { quint64 bytesAvailable = quint64(pIoDevice->bytesAvailable()); if (bytesAvailable >= payloadLength) { frame.m_payload = pIoDevice->read(payloadLength); //payloadLength can be safely cast to an integer, //because MAX_FRAME_SIZE_IN_BYTES = MAX_INT if (Q_UNLIKELY(frame.m_payload.length() != int(payloadLength))) { //some error occurred; refer to the Qt documentation of QIODevice::read() frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION, QObject::tr("Some serious error occurred " \ "while reading from the network.")); processingState = PS_DISPATCH_RESULT; } else { if (hasMask) QWebSocketProtocol::mask(&frame.m_payload, frame.m_mask); processingState = PS_DISPATCH_RESULT; } } else { //if payload is too big, then this will timeout WAIT_FOR_MORE_DATA(payloadLength); } } break; case PS_DISPATCH_RESULT: processingState = PS_READ_HEADER; isDone = true; break; default: //should not come here qWarning() << "DataProcessor::process: Found invalid state. This should not happen!"; frame.clear(); isDone = true; break; } //end switch } return frame; }
/*! \internal */ bool QWebSocketDataProcessor::processControlFrame(const QWebSocketFrame &frame) { bool mustStopProcessing = true; //control frames never expect additional frames to be processed switch (frame.opCode()) { case QWebSocketProtocol::OpCodePing: Q_EMIT pingReceived(frame.payload()); break; case QWebSocketProtocol::OpCodePong: Q_EMIT pongReceived(frame.payload()); break; case QWebSocketProtocol::OpCodeClose: { quint16 closeCode = QWebSocketProtocol::CloseCodeNormal; QString closeReason; QByteArray payload = frame.payload(); if (Q_UNLIKELY(payload.size() == 1)) { //size is either 0 (no close code and no reason) //or >= 2 (at least a close code of 2 bytes) closeCode = QWebSocketProtocol::CloseCodeProtocolError; closeReason = tr("Payload of close frame is too small."); } else if (Q_LIKELY(payload.size() > 1)) { //close frame can have a close code and reason closeCode = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(payload.constData())); if (Q_UNLIKELY(!QWebSocketProtocol::isCloseCodeValid(closeCode))) { closeCode = QWebSocketProtocol::CloseCodeProtocolError; closeReason = tr("Invalid close code %1 detected.").arg(closeCode); } else { if (payload.size() > 2) { QTextCodec *tc = QTextCodec::codecForName(QByteArrayLiteral("UTF-8")); QTextCodec::ConverterState state(QTextCodec::ConvertInvalidToNull); closeReason = tc->toUnicode(payload.constData() + 2, payload.size() - 2, &state); const bool failed = (state.invalidChars != 0) || (state.remainingChars != 0); if (Q_UNLIKELY(failed)) { closeCode = QWebSocketProtocol::CloseCodeWrongDatatype; closeReason = tr("Invalid UTF-8 code encountered."); } } } } Q_EMIT closeReceived(static_cast<QWebSocketProtocol::CloseCode>(closeCode), closeReason); break; } case QWebSocketProtocol::OpCodeContinue: case QWebSocketProtocol::OpCodeBinary: case QWebSocketProtocol::OpCodeText: case QWebSocketProtocol::OpCodeReserved3: case QWebSocketProtocol::OpCodeReserved4: case QWebSocketProtocol::OpCodeReserved5: case QWebSocketProtocol::OpCodeReserved6: case QWebSocketProtocol::OpCodeReserved7: case QWebSocketProtocol::OpCodeReservedC: case QWebSocketProtocol::OpCodeReservedB: case QWebSocketProtocol::OpCodeReservedD: case QWebSocketProtocol::OpCodeReservedE: case QWebSocketProtocol::OpCodeReservedF: //do nothing //case statements added to make C++ compiler happy break; default: Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError, tr("Invalid opcode detected: %1").arg(int(frame.opCode()))); //do nothing break; } return mustStopProcessing; }
/*! \internal */ void QWebSocketDataProcessor::process(QIODevice *pIoDevice) { bool isDone = false; while (!isDone) { QWebSocketFrame frame = QWebSocketFrame::readFrame(pIoDevice); if (Q_LIKELY(frame.isValid())) { if (frame.isControlFrame()) { isDone = processControlFrame(frame); } else { //we have a dataframe; opcode can be OC_CONTINUE, OC_TEXT or OC_BINARY if (Q_UNLIKELY(!m_isFragmented && frame.isContinuationFrame())) { clear(); Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError, tr("Received Continuation frame, while there is " \ "nothing to continue.")); return; } if (Q_UNLIKELY(m_isFragmented && frame.isDataFrame() && !frame.isContinuationFrame())) { clear(); Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeProtocolError, tr("All data frames after the initial data frame " \ "must have opcode 0 (continuation).")); return; } if (!frame.isContinuationFrame()) { m_opCode = frame.opCode(); m_isFragmented = !frame.isFinalFrame(); } quint64 messageLength = (quint64)(m_opCode == QWebSocketProtocol::OpCodeText) ? m_textMessage.length() : m_binaryMessage.length(); if (Q_UNLIKELY((messageLength + quint64(frame.payload().length())) > MAX_MESSAGE_SIZE_IN_BYTES)) { clear(); Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeTooMuchData, tr("Received message is too big.")); return; } if (m_opCode == QWebSocketProtocol::OpCodeText) { QString frameTxt = m_pTextCodec->toUnicode(frame.payload().constData(), frame.payload().size(), m_pConverterState); bool failed = (m_pConverterState->invalidChars != 0) || (frame.isFinalFrame() && (m_pConverterState->remainingChars != 0)); if (Q_UNLIKELY(failed)) { clear(); Q_EMIT errorEncountered(QWebSocketProtocol::CloseCodeWrongDatatype, tr("Invalid UTF-8 code encountered.")); return; } else { m_textMessage.append(frameTxt); Q_EMIT textFrameReceived(frameTxt, frame.isFinalFrame()); } } else { m_binaryMessage.append(frame.payload()); Q_EMIT binaryFrameReceived(frame.payload(), frame.isFinalFrame()); } if (frame.isFinalFrame()) { if (m_opCode == QWebSocketProtocol::OpCodeText) Q_EMIT textMessageReceived(m_textMessage); else Q_EMIT binaryMessageReceived(m_binaryMessage); clear(); isDone = true; } } } else { Q_EMIT errorEncountered(frame.closeCode(), frame.closeReason()); clear(); isDone = true; } } }
void tst_WebSocketFrame::tst_goodFrames() { QFETCH(int, rsv1); QFETCH(int, rsv2); QFETCH(int, 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 = QWebSocketFrame::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_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 = QWebSocketFrame::readFrame(&buffer); buffer.close(); { QWebSocketFrame other(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; other = 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()); } }
void tst_WebSocketFrame::tst_initialization() { QWebSocketFrame frame; QVERIFY(!frame.isValid()); QCOMPARE(frame.payload().length(), 0); }