/*! \internal */ void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString reason) { if (Q_UNLIKELY(!m_pSocket)) return; if (!m_isClosingHandshakeSent) { Q_Q(QWebSocket); const quint16 code = qToBigEndian<quint16>(closeCode); QByteArray payload; payload.append(static_cast<const char *>(static_cast<const void *>(&code)), 2); if (!reason.isEmpty()) payload.append(reason.toUtf8()); quint32 maskingKey = 0; if (m_mustMask) { maskingKey = generateMaskingKey(); QWebSocketProtocol::mask(payload.data(), payload.size(), maskingKey); } QByteArray frame = getFrameHeader(QWebSocketProtocol::OpCodeClose, payload.size(), maskingKey, true); frame.append(payload); m_pSocket->write(frame); m_pSocket->flush(); m_isClosingHandshakeSent = true; Q_EMIT q->aboutToClose(); } m_pSocket->close(); }
/*! * \brief Gracefully closes the socket with the given \a closeCode and \a reason. Any data in the write buffer is flushed before the socket is closed. * \param closeCode The WebSocketProtocol::CloseCode indicating the reason to close. * \param reason A string describing the error more in detail */ void WebSocket::close(WebSocketProtocol::CloseCode closeCode, QString reason) { if (!m_isClosingHandshakeSent) { quint32 maskingKey = 0; if (m_mustMask) { maskingKey = generateMaskingKey(); } quint16 code = qToBigEndian<quint16>(closeCode); QByteArray payload; payload.append(static_cast<const char *>(static_cast<const void *>(&code)), 2); if (!reason.isEmpty()) { payload.append(reason.toUtf8()); } if (m_mustMask) { WebSocketProtocol::mask(payload.data(), payload.size(), maskingKey); } QByteArray frame = getFrameHeader(WebSocketProtocol::OC_CLOSE, payload.size(), maskingKey, true); frame.append(payload); m_pSocket->write(frame); m_pSocket->flush(); m_isClosingHandshakeSent = true; Q_EMIT aboutToClose(); } m_pSocket->close(); }
struct wsFrame* createWSFrame(char *data, int len, unsigned char opcode){ struct wsFrame* frame = (struct wsFrame*)malloc(sizeof(struct wsFrame)); frame->fin = 1; frame->opcode = opcode; frame->mask = 1; frame->len = len; frame->maskingKey = generateMaskingKey(); frame->data = (char*)malloc(frame->len+1); strcpy(frame->data, data); return frame; }
/*! \internal */ void QWebSocketPrivate::processPing(const QByteArray &data) { Q_ASSERT(m_pSocket); quint32 maskingKey = 0; if (m_mustMask) maskingKey = generateMaskingKey(); m_pSocket->write(getFrameHeader(QWebSocketProtocol::OpCodePong, data.size(), maskingKey, true)); if (data.size() > 0) { QByteArray maskedData = data; if (m_mustMask) QWebSocketProtocol::mask(&maskedData, maskingKey); m_pSocket->write(maskedData); } }
/*! \internal */ void QWebSocketPrivate::ping(const QByteArray &payload) { QByteArray payloadTruncated = payload.left(125); m_pingTimer.restart(); quint32 maskingKey = 0; if (m_mustMask) maskingKey = generateMaskingKey(); QByteArray pingFrame = getFrameHeader(QWebSocketProtocol::OpCodePing, payloadTruncated.size(), maskingKey, true); if (m_mustMask) QWebSocketProtocol::mask(&payloadTruncated, maskingKey); pingFrame.append(payloadTruncated); qint64 ret = writeFrame(pingFrame); Q_UNUSED(ret); }
void EnginioBackendConnection::ping() { if (_sentCloseFrame) return; // The WebSocket server should accept ping frames without payload according to // the specification, but ours does not, so let's add a dummy payload. QByteArray dummy; dummy.append(QStringLiteral("Ping.").toUtf8()); QByteArray maskingKey = generateMaskingKey(); QByteArray message = constructFrameHeader(/*isFinalFragment*/ true, PingOp, dummy.size(), maskingKey); Q_ASSERT(!message.isEmpty()); maskData(dummy, maskingKey); message.append(dummy); _tcpSocket->write(message); }
void EnginioBackendConnection::close(WebSocketCloseStatus closeStatus) { if (_sentCloseFrame) return; _sentCloseFrame = true; _keepAliveTimer.stop(); QByteArray payload; quint16 closeStatusBigEndian = qToBigEndian<quint16>(closeStatus); payload.append(reinterpret_cast<char*>(&closeStatusBigEndian), DefaultHeaderLength); QByteArray maskingKey = generateMaskingKey(); QByteArray message = constructFrameHeader(/*isFinalFragment*/ true, ConnectionCloseOp, payload.size(), maskingKey); Q_ASSERT(!message.isEmpty()); maskData(payload, maskingKey); message.append(payload); _tcpSocket->write(message); }
/*! * \internal */ qint64 QWebSocketPrivate::doWriteFrames(const QByteArray &data, bool isBinary) { qint64 payloadWritten = 0; if (Q_UNLIKELY(!m_pSocket) || (state() != QAbstractSocket::ConnectedState)) return payloadWritten; Q_Q(QWebSocket); const QWebSocketProtocol::OpCode firstOpCode = isBinary ? QWebSocketProtocol::OpCodeBinary : QWebSocketProtocol::OpCodeText; int numFrames = data.size() / FRAME_SIZE_IN_BYTES; QByteArray tmpData(data); tmpData.detach(); char *payload = tmpData.data(); quint64 sizeLeft = quint64(data.size()) % FRAME_SIZE_IN_BYTES; if (Q_LIKELY(sizeLeft)) ++numFrames; //catch the case where the payload is zero bytes; //in this case, we still need to send a frame if (Q_UNLIKELY(numFrames == 0)) numFrames = 1; quint64 currentPosition = 0; qint64 bytesWritten = 0; quint64 bytesLeft = data.size(); for (int i = 0; i < numFrames; ++i) { quint32 maskingKey = 0; if (m_mustMask) maskingKey = generateMaskingKey(); const bool isLastFrame = (i == (numFrames - 1)); const bool isFirstFrame = (i == 0); const quint64 size = qMin(bytesLeft, FRAME_SIZE_IN_BYTES); const QWebSocketProtocol::OpCode opcode = isFirstFrame ? firstOpCode : QWebSocketProtocol::OpCodeContinue; //write header bytesWritten += m_pSocket->write(getFrameHeader(opcode, size, maskingKey, isLastFrame)); //write payload if (Q_LIKELY(size > 0)) { char *currentData = payload + currentPosition; if (m_mustMask) QWebSocketProtocol::mask(currentData, size, maskingKey); qint64 written = m_pSocket->write(currentData, static_cast<qint64>(size)); if (Q_LIKELY(written > 0)) { bytesWritten += written; payloadWritten += written; } else { m_pSocket->flush(); setErrorString(QWebSocket::tr("Error writing bytes to socket: %1.") .arg(m_pSocket->errorString())); Q_EMIT q->error(QAbstractSocket::NetworkError); break; } } currentPosition += size; bytesLeft -= size; } if (Q_UNLIKELY(payloadWritten != data.size())) { setErrorString(QWebSocket::tr("Bytes written %1 != %2.") .arg(payloadWritten).arg(data.size())); Q_EMIT q->error(QAbstractSocket::NetworkError); } return payloadWritten; }
void EnginioBackendConnection::onSocketReadyRead() { // WebSocket Protocol (RFC6455) // Base Framing Protocol // http://tools.ietf.org/html/rfc6455#section-5.2 // // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-------+-+-------------+-------------------------------+ // |F|R|R|R| opcode|M| Payload len | Extended payload length | // |I|S|S|S| (4) |A| (7) | (16/64) | // |N|V|V|V| |S| | (if payload len==126/127) | // | |1|2|3| |K| | | // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + // | Extended payload length continued, if payload len == 127 | // + - - - - - - - - - - - - - - - +-------------------------------+ // | |Masking-key, if MASK set to 1 | // +-------------------------------+-------------------------------+ // | Masking-key (continued) | Payload Data | // +-------------------------------- - - - - - - - - - - - - - - - + // : Payload Data continued ... : // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // | Payload Data continued ... | // +---------------------------------------------------------------+ while (_tcpSocket->bytesAvailable()) { switch (_protocolDecodeState) { case HandshakePending: { // The response is closed by a CRLF line on its own (e.g. ends with two newlines). while (_handshakeReply.isEmpty() || (!_handshakeReply.endsWith(QString(CRLF % CRLF).toUtf8()) // According to documentation QIODevice::readLine replaces newline characters on // Windows with '\n', so just to be on the safe side: && !_handshakeReply.endsWith(QByteArrayLiteral("\n\n")))) { if (!_tcpSocket->bytesAvailable()) return; _handshakeReply.append(_tcpSocket->readLine()); } QString response = QString::fromUtf8(_handshakeReply); _handshakeReply.clear(); int statusCode = extractResponseStatus(response); QString secWebSocketAccept = extractResponseHeader(SecWebSocketAcceptHeader, response, /* ignoreCase */ false); bool hasValidKey = secWebSocketAccept == gBase64EncodedSha1VerificationKey; if (statusCode != 101 || !hasValidKey || extractResponseHeader(UpgradeHeader, response) != QStringLiteral("websocket") || extractResponseHeader(ConnectionHeader, response) != QStringLiteral("upgrade") ) return protocolError("Handshake failed!"); _keepAliveTimer.start(TwoMinutes, this); _protocolDecodeState = FrameHeaderPending; emit stateChanged(ConnectedState); } // Fall-through. case FrameHeaderPending: { if (quint64(_tcpSocket->bytesAvailable()) < DefaultHeaderLength) return; // Large payload. if (_payloadLength == LargePayloadMarker) { if (quint64(_tcpSocket->bytesAvailable()) < LargePayloadHeaderLength) return; char data[LargePayloadHeaderLength]; if (quint64(_tcpSocket->read(data, LargePayloadHeaderLength)) != LargePayloadHeaderLength) return protocolError("Reading large payload length failed!"); if (data[0] & MSB) return protocolError("The most significant bit of a large payload length must be 0!", MessageTooBigCloseStatus); // 8 bytes interpreted as a 64-bit unsigned integer _payloadLength = qFromBigEndian<quint64>(reinterpret_cast<uchar*>(data)); _protocolDecodeState = PayloadDataPending; break; } char data[DefaultHeaderLength]; if (quint64(_tcpSocket->read(data, DefaultHeaderLength)) != DefaultHeaderLength) return protocolError("Reading header failed!"); if (!_payloadLength) { // This is the initial frame header data. _isFinalFragment = (data[0] & FIN); _protocolOpcode = static_cast<WebSocketOpcode>(data[0] & OPC); _isPayloadMasked = (data[1] & MSK); _payloadLength = (data[1] & LEN); if (_isPayloadMasked) return protocolError("Invalid masked frame received from server."); // For data length 0-125 LEN is the payload length. if (_payloadLength < NormalPayloadMarker) _protocolDecodeState = PayloadDataPending; } else { Q_ASSERT(_payloadLength == NormalPayloadMarker); // Normal sized payload: 2 bytes interpreted as the payload // length expressed in network byte order (e.g. big endian). _payloadLength = qFromBigEndian<quint16>(reinterpret_cast<uchar*>(data)); _protocolDecodeState = PayloadDataPending; } break; } case PayloadDataPending: { if (static_cast<quint64>(_tcpSocket->bytesAvailable()) < _payloadLength) return; if (_protocolOpcode == ConnectionCloseOp) { WebSocketCloseStatus closeStatus = UnknownCloseStatus; if (_payloadLength >= DefaultHeaderLength) { char data[DefaultHeaderLength]; if (quint64(_tcpSocket->read(data, DefaultHeaderLength)) != DefaultHeaderLength) return protocolError("Reading connection close status failed!"); closeStatus = static_cast<WebSocketCloseStatus>(qFromBigEndian<quint16>(reinterpret_cast<uchar*>(data))); // The body may contain UTF-8-encoded data with value /reason/, // the interpretation of this data is however not defined by the // specification. Further more the data is not guaranteed to be // human readable, thus it is safe for us to just discard the rest // of the message at this point. } qDebug() << "Connection closed by the server with status:" << closeStatus; QJsonObject data; data[EnginioString::messageType] = QStringLiteral("close"); data[EnginioString::status] = closeStatus; emit dataReceived(data); close(closeStatus); _tcpSocket->close(); return; } // We received data from the server so restart the timer. _keepAliveTimer.start(TwoMinutes, this); _applicationData.append(_tcpSocket->read(_payloadLength)); _protocolDecodeState = FrameHeaderPending; _payloadLength = 0; if (!_isFinalFragment) break; switch (_protocolOpcode) { case TextFrameOp: { QJsonObject data = QJsonDocument::fromJson(_applicationData).object(); data[EnginioString::messageType] = QStringLiteral("data"); emit dataReceived(data); break; } case PingOp:{ // We must send back identical application data as found in the message. QByteArray payload = _applicationData; QByteArray maskingKey = generateMaskingKey(); QByteArray message = constructFrameHeader(/*isFinalFragment*/ true, PongOp, payload.size(), maskingKey); Q_ASSERT(!message.isEmpty()); maskData(payload, maskingKey); message.append(payload); _tcpSocket->write(message); break; } case PongOp: _pingTimeoutTimer.stop(); emit pong(); break; default: protocolError("WebSocketOpcode not yet supported.", UnsupportedDataTypeCloseStatus); qWarning() << "\t\t->" << _protocolOpcode; } _applicationData.clear(); break; } } } }
/*! \internal */ void WebSocket::processControlFrame(WebSocketProtocol::OpCode opCode, QByteArray frame) { switch (opCode) { case WebSocketProtocol::OC_PING: { quint32 maskingKey = 0; if (m_mustMask) { maskingKey = generateMaskingKey(); } m_pSocket->write(getFrameHeader(WebSocketProtocol::OC_PONG, frame.size(), maskingKey, true)); if (frame.size() > 0) { if (m_mustMask) { WebSocketProtocol::mask(&frame, maskingKey); } m_pSocket->write(frame); } break; } case WebSocketProtocol::OC_PONG: { Q_EMIT pong(static_cast<quint64>(m_pingTimer.elapsed())); break; } case WebSocketProtocol::OC_CLOSE: { quint16 closeCode = WebSocketProtocol::CC_NORMAL; QString closeReason; if (frame.size() > 0) //close frame can have a close code and reason { closeCode = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(frame.constData())); if (!WebSocketProtocol::isCloseCodeValid(closeCode)) { closeCode = WebSocketProtocol::CC_PROTOCOL_ERROR; closeReason = QString("Invalid close code %1 detected").arg(closeCode); } else { if (frame.size() > 2) { QTextCodec *tc = QTextCodec::codecForName("UTF-8"); QTextCodec::ConverterState state(QTextCodec::ConvertInvalidToNull); closeReason = tc->toUnicode(frame.constData() + 2, frame.size() - 2, &state); bool failed = (state.invalidChars != 0) || (state.remainingChars != 0); if (failed) { closeCode = WebSocketProtocol::CC_WRONG_DATATYPE; closeReason = "Invalid UTF-8 code encountered."; } } } } m_isClosingHandshakeReceived = true; close(static_cast<WebSocketProtocol::CloseCode>(closeCode), closeReason); break; } case WebSocketProtocol::OC_CONTINUE: case WebSocketProtocol::OC_BINARY: case WebSocketProtocol::OC_TEXT: case WebSocketProtocol::OC_RESERVED_3: case WebSocketProtocol::OC_RESERVED_4: case WebSocketProtocol::OC_RESERVED_5: case WebSocketProtocol::OC_RESERVED_6: case WebSocketProtocol::OC_RESERVED_7: case WebSocketProtocol::OC_RESERVED_B: case WebSocketProtocol::OC_RESERVED_D: case WebSocketProtocol::OC_RESERVED_E: case WebSocketProtocol::OC_RESERVED_F: case WebSocketProtocol::OC_RESERVED_V: { //do nothing //case added to make C++ compiler happy break; } default: { qDebug() << "WebSocket::processData: Invalid opcode detected:" << static_cast<int>(opCode); //Do nothing break; } } }
/*! * \internal */ qint64 WebSocket::doWriteFrames(const QByteArray &data, bool isBinary) { const WebSocketProtocol::OpCode firstOpCode = isBinary ? WebSocketProtocol::OC_BINARY : WebSocketProtocol::OC_TEXT; int numFrames = data.size() / FRAME_SIZE_IN_BYTES; QByteArray tmpData(data); tmpData.detach(); char *payload = tmpData.data(); quint64 sizeLeft = static_cast<quint64>(data.size()) % FRAME_SIZE_IN_BYTES; if (sizeLeft) { ++numFrames; } if (numFrames == 0) //catch the case where the payload is zero bytes; in that case, we still need to send a frame { numFrames = 1; } quint64 currentPosition = 0; qint64 bytesWritten = 0; qint64 payloadWritten = 0; quint64 bytesLeft = data.size(); for (int i = 0; i < numFrames; ++i) { quint32 maskingKey = 0; if (m_mustMask) { maskingKey = generateMaskingKey(); } bool isLastFrame = (i == (numFrames - 1)); bool isFirstFrame = (i == 0); quint64 size = qMin(bytesLeft, FRAME_SIZE_IN_BYTES); WebSocketProtocol::OpCode opcode = isFirstFrame ? firstOpCode : WebSocketProtocol::OC_CONTINUE; //write header bytesWritten += m_pSocket->write(getFrameHeader(opcode, size, maskingKey, isLastFrame)); //write payload if (size > 0) { char *currentData = payload + currentPosition; if (m_mustMask) { WebSocketProtocol::mask(currentData, size, maskingKey); } qint64 written = m_pSocket->write(currentData, static_cast<qint64>(size)); if (written > 0) { bytesWritten += written; payloadWritten += written; } else { setErrorString("WebSocket::doWriteFrames: Error writing bytes to socket: " + m_pSocket->errorString()); qDebug() << errorString(); m_pSocket->flush(); Q_EMIT error(QAbstractSocket::NetworkError); break; } } currentPosition += size; bytesLeft -= size; } if (payloadWritten != data.size()) { setErrorString("Bytes written " + QString::number(payloadWritten) + " != " + QString::number(data.size())); qDebug() << errorString(); Q_EMIT error(QAbstractSocket::NetworkError); } return payloadWritten; }