/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef INCLUDED_WEBSOCKETHANDLER_HPP #define INCLUDED_WEBSOCKETHANDLER_HPP #include "Common.hpp" #include "Log.hpp" #include "Socket.hpp" class WebSocketHandler : public SocketHandlerInterface { // The socket that owns us (we can't own it). std::weak_ptr _socket; std::vector _wsPayload; enum class WSFrameMask : unsigned char { Fin = 0x80, Mask = 0x80 }; public: WebSocketHandler() { } /// Implementation of the SocketHandlerInterface. void onConnect(const std::weak_ptr& socket) override { _socket = socket; } enum WSOpCode { Continuation, // 0x0 Text, // 0x1 Binary, // 0x2 Reserved1, // 0x3 Reserved2, // 0x4 Reserved3, // 0x5 Reserved4, // 0x6 Reserved5, // 0x7 Close, // 0x8 Ping, // 0x9 Pong // 0xa // ... reserved }; /// Implementation of the SocketHandlerInterface. virtual void handleIncomingMessage() override { auto socket = _socket.lock(); if (socket == nullptr) return; // websocket fun ! const size_t len = socket->_inBuffer.size(); LOG_TRC("Incoming WebSocket data of " << len << " bytes to socket #" << socket->getFD()); if (len < 2) // partial read return; unsigned char *p = reinterpret_cast(&socket->_inBuffer[0]); bool fin = p[0] & 0x80; WSOpCode code = static_cast(p[0] & 0x0f); bool hasMask = p[1] & 0x80; size_t payloadLen = p[1] & 0x7f; size_t headerLen = 2; // normally - 7 bit length. if (payloadLen == 126) // 2 byte length { if (len < 2 + 2) return; payloadLen = (((unsigned)p[2]) << 8) | ((unsigned)p[3]); headerLen += 2; } else if (payloadLen == 127) // 8 byte length { if (len < 2 + 8) return; payloadLen = ((((uint64_t)(p[9])) << 0) + (((uint64_t)(p[8])) << 8) + (((uint64_t)(p[7])) << 16) + (((uint64_t)(p[6])) << 24) + (((uint64_t)(p[5])) << 32) + (((uint64_t)(p[4])) << 40) + (((uint64_t)(p[3])) << 48) + (((uint64_t)(p[2])) << 56)); // FIXME: crop read length to remove top / sign bits. headerLen += 8; } unsigned char *data, *mask; if (hasMask) { mask = p + headerLen; headerLen += 4; } if (payloadLen + headerLen > len) { // partial read wait for more data. return; } data = p + headerLen; if (hasMask) { for (size_t i = 0; i < payloadLen; ++i) data[i] = data[i] ^ mask[i % 4]; // FIXME: copy and un-mask at the same time ... _wsPayload.insert(_wsPayload.end(), data, data + payloadLen); } else _wsPayload.insert(_wsPayload.end(), data, data + payloadLen); socket->_inBuffer.erase(socket->_inBuffer.begin(), socket->_inBuffer.begin() + headerLen + payloadLen); // FIXME: fin, aggregating payloads into _wsPayload etc. LOG_TRC("Incoming WebSocket message code " << code << " fin? " << fin << " payload length " << _wsPayload.size()); handleMessage(fin, code, _wsPayload); _wsPayload.clear(); } /// Sends a WebSocket message of WPOpCode type. /// Returns the number of bytes written (including frame overhead) on success, /// 0 for closed/invalid socket, and -1 for other errors. int sendMessage(const char* data, const size_t len, const WSOpCode code, const bool flush = true) const { if (data == nullptr || len == 0) return -1; auto socket = _socket.lock(); if (socket == nullptr) return -1; // no socket == error. auto lock = socket->getWriteLock(); std::vector& out = socket->_outBuffer; //TODO: Support fragmented messages. const unsigned char fin = static_cast(WSFrameMask::Fin); // FIXME: need to support fragmented mesages, but for now send prefix message with size. if (len >= LARGE_MESSAGE_SIZE) { const std::string nextmessage = "nextmessage: size=" + std::to_string(len); const unsigned char size = (nextmessage.size() & 0xff); out.push_back(fin | WSOpCode::Text); out.push_back(size); out.insert(out.end(), nextmessage.data(), nextmessage.data() + size); socket->writeOutgoingData(); } return sendFrame(socket, data, len, static_cast(fin | code), flush); } protected: /// Sends a WebSocket frame given the data, length, and flags. /// Returns the number of bytes written (including frame overhead) on success, /// 0 for closed/invalid socket, and -1 for other errors. static int sendFrame(const std::shared_ptr& socket, const char* data, const size_t len, const unsigned char flags, const bool flush = true) { if (!socket || data == nullptr || len == 0) return -1; std::vector& out = socket->_outBuffer; out.push_back(flags); if (len < 126) { out.push_back((char)len); } else if (len <= 0xffff) { out.push_back((char)126); out.push_back(static_cast((len >> 8) & 0xff)); out.push_back(static_cast((len >> 0) & 0xff)); } else { out.push_back((char)127); out.push_back(static_cast((len >> 56) & 0xff)); out.push_back(static_cast((len >> 48) & 0xff)); out.push_back(static_cast((len >> 40) & 0xff)); out.push_back(static_cast((len >> 32) & 0xff)); out.push_back(static_cast((len >> 24) & 0xff)); out.push_back(static_cast((len >> 16) & 0xff)); out.push_back(static_cast((len >> 8) & 0xff)); out.push_back(static_cast((len >> 0) & 0xff)); } // Copy the data. out.insert(out.end(), data, data + len); if (flush) socket->writeOutgoingData(); // Data + header. return len + 2; } /// To me overriden to handle the websocket messages the way you need. virtual void handleMessage(bool fin, WSOpCode code, std::vector &data) = 0; }; class WebSocketSender : private WebSocketHandler { public: WebSocketSender(const std::weak_ptr& socket) { onConnect(socket); } void sendFrame(const std::string& msg) const { sendMessage(msg.data(), msg.size(), WSOpCode::Text); } private: void handleMessage(bool, WSOpCode, std::vector&) override { // We will not read any. } }; #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */