2017-02-23 10:57:59 -06:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
|
|
/*
|
|
|
|
* 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/.
|
|
|
|
*/
|
|
|
|
|
2020-04-18 03:39:50 -05:00
|
|
|
#pragma once
|
2017-02-23 10:57:59 -06:00
|
|
|
|
2017-05-20 12:28:43 -05:00
|
|
|
#include <chrono>
|
|
|
|
#include <memory>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include "common/Common.hpp"
|
|
|
|
#include "common/Log.hpp"
|
2017-10-26 03:38:57 -05:00
|
|
|
#include "common/Unit.hpp"
|
2017-02-23 10:57:59 -06:00
|
|
|
#include "Socket.hpp"
|
|
|
|
|
2018-05-01 11:50:13 -05:00
|
|
|
#include <Poco/MemoryStream.h>
|
2017-03-02 03:38:49 -06:00
|
|
|
#include <Poco/Net/HTTPRequest.h>
|
2018-05-03 10:32:31 -05:00
|
|
|
#include <Poco/Net/HTTPResponse.h>
|
2017-03-02 03:38:49 -06:00
|
|
|
#include <Poco/Net/WebSocket.h>
|
|
|
|
|
2020-03-06 11:43:46 -06:00
|
|
|
class WebSocketHandler : public ProtocolHandlerInterface
|
2017-02-23 10:57:59 -06:00
|
|
|
{
|
2018-10-24 02:35:46 -05:00
|
|
|
private:
|
2018-02-11 18:14:21 -06:00
|
|
|
/// The socket that owns us (we can't own it).
|
2017-02-26 12:37:15 -06:00
|
|
|
std::weak_ptr<StreamSocket> _socket;
|
2017-03-02 03:38:49 -06:00
|
|
|
|
2020-06-24 05:50:08 -05:00
|
|
|
#if !MOBILEAPP
|
2018-02-11 18:14:21 -06:00
|
|
|
std::chrono::steady_clock::time_point _lastPingSentTime;
|
2017-03-17 17:59:03 -05:00
|
|
|
int _pingTimeUs;
|
2020-06-24 05:50:08 -05:00
|
|
|
bool _isMasking;
|
|
|
|
bool _inFragmentBlock;
|
|
|
|
#endif
|
2017-03-17 17:59:03 -05:00
|
|
|
|
2017-02-23 10:57:59 -06:00
|
|
|
std::vector<char> _wsPayload;
|
2018-02-11 18:14:21 -06:00
|
|
|
std::atomic<bool> _shuttingDown;
|
2018-05-01 11:50:13 -05:00
|
|
|
bool _isClient;
|
2017-02-23 10:57:59 -06:00
|
|
|
|
2018-10-24 02:35:46 -05:00
|
|
|
protected:
|
2018-02-11 18:14:21 -06:00
|
|
|
struct WSFrameMask
|
2017-02-28 16:31:27 -06:00
|
|
|
{
|
2020-11-15 08:01:55 -06:00
|
|
|
static constexpr unsigned char Fin = 0x80;
|
|
|
|
static constexpr unsigned char Mask = 0x80;
|
2017-02-28 16:31:27 -06:00
|
|
|
};
|
|
|
|
|
2020-04-09 08:43:51 -05:00
|
|
|
static const int InitialPingDelayMicroS;
|
|
|
|
static const int PingFrequencyMicroS;
|
2018-02-11 18:14:21 -06:00
|
|
|
|
2017-02-23 10:57:59 -06:00
|
|
|
public:
|
2018-05-01 11:50:13 -05:00
|
|
|
/// Perform upgrade ourselves, or select a client web socket.
|
2019-03-08 02:21:17 -06:00
|
|
|
/// Parameters:
|
|
|
|
/// isClient: the instance should behave like a client (true) or like a server (false)
|
|
|
|
/// (from websocket perspective)
|
|
|
|
/// isMasking: a client should mask (true) or not (false) outgoing frames
|
|
|
|
/// isManualDefrag: the message handler should be called for every fragment of a message and
|
|
|
|
/// defragmentation should be handled inside message handler (true) or the message handler
|
|
|
|
/// should be called after all fragments of a message were received and the message
|
|
|
|
/// was defragmented (false).
|
2020-06-24 05:50:08 -05:00
|
|
|
WebSocketHandler(bool isClient = false, bool isMasking = true) :
|
2019-04-19 04:10:51 -05:00
|
|
|
#if !MOBILEAPP
|
2020-06-24 05:50:08 -05:00
|
|
|
_lastPingSentTime(std::chrono::steady_clock::now()),
|
|
|
|
_pingTimeUs(0),
|
|
|
|
_isMasking(isClient && isMasking),
|
|
|
|
_inFragmentBlock(false),
|
2019-04-19 04:10:51 -05:00
|
|
|
#endif
|
2020-06-24 05:50:08 -05:00
|
|
|
_shuttingDown(false),
|
|
|
|
_isClient(isClient)
|
2017-02-23 10:57:59 -06:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-03-02 03:38:49 -06:00
|
|
|
/// Upgrades itself to a websocket directly.
|
2019-03-08 02:21:17 -06:00
|
|
|
/// Parameters:
|
|
|
|
/// socket: the TCP socket which received the upgrade request
|
|
|
|
/// request: the HTTP upgrade request to WebSocket
|
2017-03-06 13:50:06 -06:00
|
|
|
WebSocketHandler(const std::weak_ptr<StreamSocket>& socket,
|
2019-04-19 04:10:51 -05:00
|
|
|
const Poco::Net::HTTPRequest& request)
|
|
|
|
: _socket(socket)
|
2020-06-24 05:50:08 -05:00
|
|
|
#if !MOBILEAPP
|
2019-04-19 04:10:51 -05:00
|
|
|
, _lastPingSentTime(std::chrono::steady_clock::now() -
|
2020-04-09 08:43:51 -05:00
|
|
|
std::chrono::microseconds(PingFrequencyMicroS) -
|
|
|
|
std::chrono::microseconds(InitialPingDelayMicroS))
|
2019-04-19 04:10:51 -05:00
|
|
|
, _pingTimeUs(0)
|
|
|
|
, _isMasking(false)
|
|
|
|
, _inFragmentBlock(false)
|
|
|
|
#endif
|
2020-06-24 05:50:08 -05:00
|
|
|
, _shuttingDown(false)
|
|
|
|
, _isClient(false)
|
2017-03-01 12:49:29 -06:00
|
|
|
{
|
2017-03-02 03:38:49 -06:00
|
|
|
upgradeToWebSocket(request);
|
2017-03-01 12:49:29 -06:00
|
|
|
}
|
|
|
|
|
2020-03-06 11:43:46 -06:00
|
|
|
/// Implementation of the ProtocolHandlerInterface.
|
2017-03-26 22:06:44 -05:00
|
|
|
void onConnect(const std::shared_ptr<StreamSocket>& socket) override
|
2017-02-23 10:57:59 -06:00
|
|
|
{
|
2017-02-25 18:07:22 -06:00
|
|
|
_socket = socket;
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << " Connected to WS Handler " << this);
|
2017-02-23 10:57:59 -06:00
|
|
|
}
|
|
|
|
|
2017-02-28 20:06:29 -06:00
|
|
|
/// Status codes sent to peer on shutdown.
|
|
|
|
enum class StatusCodes : unsigned short
|
|
|
|
{
|
|
|
|
NORMAL_CLOSE = 1000,
|
|
|
|
ENDPOINT_GOING_AWAY = 1001,
|
|
|
|
PROTOCOL_ERROR = 1002,
|
|
|
|
PAYLOAD_NOT_ACCEPTABLE = 1003,
|
|
|
|
RESERVED = 1004,
|
|
|
|
RESERVED_NO_STATUS_CODE = 1005,
|
|
|
|
RESERVED_ABNORMAL_CLOSE = 1006,
|
|
|
|
MALFORMED_PAYLOAD = 1007,
|
|
|
|
POLICY_VIOLATION = 1008,
|
|
|
|
PAYLOAD_TOO_BIG = 1009,
|
|
|
|
EXTENSION_REQUIRED = 1010,
|
|
|
|
UNEXPECTED_CONDITION = 1011,
|
|
|
|
RESERVED_TLS_FAILURE = 1015
|
|
|
|
};
|
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
/// Sends WS Close frame to the peer.
|
|
|
|
void sendCloseFrame(const StatusCodes statusCode = StatusCodes::NORMAL_CLOSE, const std::string& statusMessage = "")
|
2017-02-28 20:06:29 -06:00
|
|
|
{
|
2018-02-07 03:17:35 -06:00
|
|
|
std::shared_ptr<StreamSocket> socket = _socket.lock();
|
2020-04-19 13:46:01 -05:00
|
|
|
if (!socket)
|
2017-03-26 22:06:44 -05:00
|
|
|
{
|
2018-09-19 02:30:19 -05:00
|
|
|
LOG_ERR("No socket associated with WebSocketHandler " << this);
|
2017-02-28 20:06:29 -06:00
|
|
|
return;
|
2017-03-26 22:06:44 -05:00
|
|
|
}
|
2017-02-28 20:06:29 -06:00
|
|
|
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Shutdown websocket, code: " <<
|
2017-03-25 20:50:24 -05:00
|
|
|
static_cast<unsigned>(statusCode) << ", message: " << statusMessage);
|
|
|
|
_shuttingDown = true;
|
2017-03-10 11:58:51 -06:00
|
|
|
|
2019-02-12 05:16:40 -06:00
|
|
|
#if !MOBILEAPP
|
2017-02-28 20:06:29 -06:00
|
|
|
const size_t len = statusMessage.size();
|
|
|
|
std::vector<char> buf(2 + len);
|
2017-02-28 21:33:39 -06:00
|
|
|
buf[0] = ((((int)statusCode) >> 8) & 0xff);
|
|
|
|
buf[1] = ((((int)statusCode) >> 0) & 0xff);
|
2017-03-20 21:55:52 -05:00
|
|
|
std::copy(statusMessage.begin(), statusMessage.end(), buf.begin() + 2);
|
2018-02-11 18:14:21 -06:00
|
|
|
const unsigned char flags = WSFrameMask::Fin
|
2017-03-20 21:55:52 -05:00
|
|
|
| static_cast<char>(WSOpCode::Close);
|
2017-02-28 20:06:29 -06:00
|
|
|
|
|
|
|
sendFrame(socket, buf.data(), buf.size(), flags);
|
2018-09-13 11:16:00 -05:00
|
|
|
#endif
|
2017-02-28 20:06:29 -06:00
|
|
|
}
|
|
|
|
|
2020-03-06 11:43:46 -06:00
|
|
|
void shutdown(bool goingAway, const std::string &statusMessage) override
|
|
|
|
{
|
|
|
|
shutdown(goingAway ? WebSocketHandler::StatusCodes::ENDPOINT_GOING_AWAY :
|
|
|
|
WebSocketHandler::StatusCodes::NORMAL_CLOSE, statusMessage);
|
|
|
|
}
|
|
|
|
|
|
|
|
void getIOStats(uint64_t &sent, uint64_t &recv) override
|
|
|
|
{
|
|
|
|
std::shared_ptr<StreamSocket> socket = getSocket().lock();
|
|
|
|
if (socket)
|
|
|
|
socket->getIOStats(sent, recv);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sent = 0;
|
|
|
|
recv = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
void shutdown(const StatusCodes statusCode = StatusCodes::NORMAL_CLOSE, const std::string& statusMessage = "")
|
|
|
|
{
|
|
|
|
if (!_shuttingDown)
|
|
|
|
sendCloseFrame(statusCode, statusMessage);
|
|
|
|
std::shared_ptr<StreamSocket> socket = _socket.lock();
|
|
|
|
if (socket)
|
|
|
|
{
|
|
|
|
socket->closeConnection();
|
|
|
|
socket->getInBuffer().clear();
|
|
|
|
}
|
|
|
|
_wsPayload.clear();
|
2019-04-19 04:10:51 -05:00
|
|
|
#if !MOBILEAPP
|
2019-03-08 02:21:17 -06:00
|
|
|
_inFragmentBlock = false;
|
2019-04-19 04:10:51 -05:00
|
|
|
#endif
|
2019-03-08 02:21:17 -06:00
|
|
|
_shuttingDown = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool handleTCPStream(const std::shared_ptr<StreamSocket>& socket)
|
2017-02-23 10:57:59 -06:00
|
|
|
{
|
2017-04-02 22:27:06 -05:00
|
|
|
assert(socket && "Expected a valid socket instance.");
|
2017-02-26 12:37:15 -06:00
|
|
|
|
2017-02-23 10:57:59 -06:00
|
|
|
// websocket fun !
|
2018-10-25 09:38:54 -05:00
|
|
|
const size_t len = socket->getInBuffer().size();
|
2017-03-10 08:49:19 -06:00
|
|
|
|
|
|
|
if (len == 0)
|
|
|
|
return false; // avoid logging.
|
|
|
|
|
2019-02-12 05:16:40 -06:00
|
|
|
#if !MOBILEAPP
|
2017-02-23 10:57:59 -06:00
|
|
|
if (len < 2) // partial read
|
2018-07-19 03:35:48 -05:00
|
|
|
{
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Still incomplete WebSocket message, have " << len << " bytes");
|
2017-03-10 08:36:21 -06:00
|
|
|
return false;
|
2018-07-19 03:35:48 -05:00
|
|
|
}
|
2017-02-23 10:57:59 -06:00
|
|
|
|
2018-10-25 09:38:54 -05:00
|
|
|
unsigned char *p = reinterpret_cast<unsigned char*>(&socket->getInBuffer()[0]);
|
2017-04-02 22:27:06 -05:00
|
|
|
const bool fin = p[0] & 0x80;
|
|
|
|
const WSOpCode code = static_cast<WSOpCode>(p[0] & 0x0f);
|
|
|
|
const bool hasMask = p[1] & 0x80;
|
2017-02-23 10:57:59 -06:00
|
|
|
size_t payloadLen = p[1] & 0x7f;
|
|
|
|
size_t headerLen = 2;
|
|
|
|
|
|
|
|
// normally - 7 bit length.
|
|
|
|
if (payloadLen == 126) // 2 byte length
|
|
|
|
{
|
|
|
|
if (len < 2 + 2)
|
2018-07-19 03:35:48 -05:00
|
|
|
{
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Still incomplete WebSocket message, have " << len << " bytes");
|
2017-03-10 08:36:21 -06:00
|
|
|
return false;
|
2018-07-19 03:35:48 -05:00
|
|
|
}
|
2017-02-23 10:57:59 -06:00
|
|
|
|
|
|
|
payloadLen = (((unsigned)p[2]) << 8) | ((unsigned)p[3]);
|
|
|
|
headerLen += 2;
|
|
|
|
}
|
|
|
|
else if (payloadLen == 127) // 8 byte length
|
|
|
|
{
|
|
|
|
if (len < 2 + 8)
|
2018-07-19 03:35:48 -05:00
|
|
|
{
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Still incomplete WebSocket message, have " << len << " bytes");
|
2017-03-10 08:36:21 -06:00
|
|
|
return false;
|
2018-07-19 03:35:48 -05:00
|
|
|
}
|
2017-02-28 21:33:39 -06:00
|
|
|
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));
|
2017-02-23 10:57:59 -06:00
|
|
|
// FIXME: crop read length to remove top / sign bits.
|
|
|
|
headerLen += 8;
|
|
|
|
}
|
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
unsigned char *data, *mask = nullptr;
|
2017-02-23 10:57:59 -06:00
|
|
|
|
|
|
|
if (hasMask)
|
|
|
|
{
|
|
|
|
mask = p + headerLen;
|
|
|
|
headerLen += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (payloadLen + headerLen > len)
|
|
|
|
{ // partial read wait for more data.
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Still incomplete WebSocket frame, have " << len
|
2019-03-31 22:30:05 -05:00
|
|
|
<< " bytes, frame is " << payloadLen + headerLen << " bytes");
|
2017-03-10 08:36:21 -06:00
|
|
|
return false;
|
2017-02-23 10:57:59 -06:00
|
|
|
}
|
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
if (hasMask && _isClient)
|
|
|
|
{
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_ERR('#' << socket->getFD() << ": Servers should not send masked frames. Only clients.");
|
2019-03-08 02:21:17 -06:00
|
|
|
shutdown(StatusCodes::PROTOCOL_ERROR);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Incoming WebSocket data of " << len << " bytes: "
|
2019-03-31 22:30:05 -05:00
|
|
|
<< Util::stringifyHexLine(socket->getInBuffer(), 0, std::min((size_t)32, len)));
|
2018-07-19 03:35:48 -05:00
|
|
|
|
2017-02-23 10:57:59 -06:00
|
|
|
data = p + headerLen;
|
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
if (isControlFrame(code))
|
2017-02-23 10:57:59 -06:00
|
|
|
{
|
2019-03-08 02:21:17 -06:00
|
|
|
//Process control frames
|
2017-02-23 10:57:59 -06:00
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
std::vector<char> ctrlPayload;
|
2018-09-11 01:30:55 -05:00
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
readPayload(data, payloadLen, mask, ctrlPayload);
|
|
|
|
socket->getInBuffer().erase(socket->getInBuffer().begin(), socket->getInBuffer().begin() + headerLen + payloadLen);
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Incoming WebSocket frame code " << static_cast<unsigned>(code) <<
|
2019-03-08 02:21:17 -06:00
|
|
|
", fin? " << fin << ", mask? " << hasMask << ", payload length: " << payloadLen <<
|
2018-10-25 09:38:54 -05:00
|
|
|
", residual socket data: " << socket->getInBuffer().size() << " bytes.");
|
2017-02-27 08:34:48 -06:00
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
// All control frames MUST NOT be fragmented and MUST have a payload length of 125 bytes or less
|
|
|
|
if (!fin)
|
2018-05-02 09:40:16 -05:00
|
|
|
{
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_ERR('#' << socket->getFD() << ": A control frame cannot be fragmented.");
|
2019-03-08 02:21:17 -06:00
|
|
|
shutdown(StatusCodes::PROTOCOL_ERROR);
|
|
|
|
return true;
|
2018-05-02 09:40:16 -05:00
|
|
|
}
|
2019-03-08 02:21:17 -06:00
|
|
|
if (payloadLen > 125)
|
2018-05-02 09:40:16 -05:00
|
|
|
{
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_ERR('#' << socket->getFD() << ": The payload length of a control frame must not exceed 125 bytes.");
|
2019-03-08 02:21:17 -06:00
|
|
|
shutdown(StatusCodes::PROTOCOL_ERROR);
|
|
|
|
return true;
|
2018-05-02 09:40:16 -05:00
|
|
|
}
|
2019-03-08 02:21:17 -06:00
|
|
|
|
|
|
|
switch (code)
|
2018-05-02 09:40:16 -05:00
|
|
|
{
|
2019-03-08 02:21:17 -06:00
|
|
|
case WSOpCode::Pong:
|
|
|
|
{
|
2020-09-17 06:54:00 -05:00
|
|
|
if (_isClient)
|
|
|
|
LOG_WRN('#' << socket->getFD() << ": Servers should not send pongs, only clients");
|
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
_pingTimeUs = std::chrono::duration_cast<std::chrono::microseconds>
|
|
|
|
(std::chrono::steady_clock::now() - _lastPingSentTime).count();
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Pong received: " << _pingTimeUs << " microseconds");
|
2019-03-08 02:21:17 -06:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case WSOpCode::Ping:
|
|
|
|
{
|
2020-09-17 06:54:00 -05:00
|
|
|
if (!_isClient)
|
|
|
|
LOG_ERR('#' << socket->getFD() << ": Clients should not send pings, only servers");
|
|
|
|
|
|
|
|
const auto now = std::chrono::steady_clock::now();
|
2019-03-08 02:21:17 -06:00
|
|
|
_pingTimeUs = std::chrono::duration_cast<std::chrono::microseconds>
|
|
|
|
(now - _lastPingSentTime).count();
|
|
|
|
sendPong(now, &ctrlPayload[0], payloadLen, socket);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case WSOpCode::Close:
|
|
|
|
{
|
|
|
|
std::string message;
|
|
|
|
StatusCodes statusCode = StatusCodes::NORMAL_CLOSE;
|
|
|
|
if (!_shuttingDown)
|
|
|
|
{
|
|
|
|
// Peer-initiated shutdown must be echoed.
|
|
|
|
// Otherwise, this is the echo to _our_ shutdown message, which we should ignore.
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Peer initiated socket shutdown. Code: " << static_cast<int>(statusCode));
|
2019-03-08 02:21:17 -06:00
|
|
|
if (ctrlPayload.size())
|
|
|
|
{
|
|
|
|
statusCode = static_cast<StatusCodes>((((uint64_t)(unsigned char)ctrlPayload[0]) << 8) +
|
|
|
|
(((uint64_t)(unsigned char)ctrlPayload[1]) << 0));
|
|
|
|
if (ctrlPayload.size() > 2)
|
|
|
|
message.assign(&ctrlPayload[2], &ctrlPayload[2] + ctrlPayload.size() - 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
shutdown(statusCode, message);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
default:
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_ERR('#' << socket->getFD() << ": Received unknown control code");
|
2019-03-08 02:21:17 -06:00
|
|
|
shutdown(StatusCodes::PROTOCOL_ERROR);
|
2018-05-02 09:40:16 -05:00
|
|
|
break;
|
|
|
|
}
|
2019-03-08 02:21:17 -06:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check data frames for errors
|
|
|
|
if (_inFragmentBlock)
|
|
|
|
{
|
|
|
|
if (code != WSOpCode::Continuation)
|
2018-05-02 09:40:16 -05:00
|
|
|
{
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_ERR('#' << socket->getFD() << ": A fragment that is not the first fragment of a message must have the opcode equal to 0.");
|
2019-03-08 02:21:17 -06:00
|
|
|
shutdown(StatusCodes::PROTOCOL_ERROR);
|
|
|
|
return true;
|
2018-05-02 09:40:16 -05:00
|
|
|
}
|
2019-03-08 02:21:17 -06:00
|
|
|
}
|
|
|
|
else if (code == WSOpCode::Continuation)
|
|
|
|
{
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_ERR('#' << socket->getFD() << ": An unfragmented message or the first fragment of a fragmented message must have the opcode different than 0.");
|
2019-03-08 02:21:17 -06:00
|
|
|
shutdown(StatusCodes::PROTOCOL_ERROR);
|
|
|
|
return true;
|
2018-05-02 09:40:16 -05:00
|
|
|
}
|
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
//Process data frame
|
|
|
|
readPayload(data, payloadLen, mask, _wsPayload);
|
2018-09-11 01:30:55 -05:00
|
|
|
#else
|
2019-03-08 02:21:17 -06:00
|
|
|
unsigned char * const p = reinterpret_cast<unsigned char*>(&socket->getInBuffer()[0]);
|
|
|
|
_wsPayload.insert(_wsPayload.end(), p, p + len);
|
|
|
|
const size_t headerLen = 0;
|
|
|
|
const size_t payloadLen = len;
|
2018-09-11 01:30:55 -05:00
|
|
|
#endif
|
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
socket->getInBuffer().erase(socket->getInBuffer().begin(), socket->getInBuffer().begin() + headerLen + payloadLen);
|
|
|
|
|
2019-02-12 05:16:40 -06:00
|
|
|
#if !MOBILEAPP
|
2019-03-08 02:21:17 -06:00
|
|
|
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Incoming WebSocket frame code " << static_cast<unsigned>(code) <<
|
2019-03-08 02:21:17 -06:00
|
|
|
", fin? " << fin << ", mask? " << hasMask << ", payload length: " << payloadLen <<
|
2019-05-29 07:10:44 -05:00
|
|
|
", residual socket data: " << socket->getInBuffer().size() << " bytes, unmasked data: "+
|
|
|
|
Util::stringifyHexLine(_wsPayload, 0, std::min((size_t)32, _wsPayload.size())));
|
2019-03-08 02:21:17 -06:00
|
|
|
|
|
|
|
if (fin)
|
2018-05-02 09:40:16 -05:00
|
|
|
{
|
2020-03-05 12:55:00 -06:00
|
|
|
// If is final fragment then process the accumulated message.
|
|
|
|
handleMessage(_wsPayload);
|
2019-03-08 02:21:17 -06:00
|
|
|
_inFragmentBlock = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-05 12:55:00 -06:00
|
|
|
_inFragmentBlock = true;
|
|
|
|
// If is not final fragment then wait for next fragment.
|
2020-07-14 04:44:21 -05:00
|
|
|
return true;
|
2017-03-01 00:12:12 -06:00
|
|
|
}
|
2019-03-08 02:21:17 -06:00
|
|
|
#else
|
2020-03-05 12:55:00 -06:00
|
|
|
handleMessage(_wsPayload);
|
2019-03-08 02:21:17 -06:00
|
|
|
|
2018-09-11 01:30:55 -05:00
|
|
|
#endif
|
2017-03-01 00:12:12 -06:00
|
|
|
|
2017-02-23 10:57:59 -06:00
|
|
|
_wsPayload.clear();
|
2017-03-10 08:36:21 -06:00
|
|
|
|
|
|
|
return true;
|
2017-02-23 10:57:59 -06:00
|
|
|
}
|
|
|
|
|
2020-03-06 11:43:46 -06:00
|
|
|
/// Implementation of the ProtocolHandlerInterface.
|
2017-05-05 05:51:43 -05:00
|
|
|
virtual void handleIncomingMessage(SocketDisposition&) override
|
2017-03-10 08:36:21 -06:00
|
|
|
{
|
2018-02-07 03:17:35 -06:00
|
|
|
std::shared_ptr<StreamSocket> socket = _socket.lock();
|
2018-09-13 11:16:00 -05:00
|
|
|
|
2019-02-12 05:16:40 -06:00
|
|
|
#if MOBILEAPP
|
2018-09-13 11:16:00 -05:00
|
|
|
// No separate "upgrade" is going on
|
2020-04-19 13:46:01 -05:00
|
|
|
if (socket && !socket->isWebSocket())
|
2018-09-13 11:16:00 -05:00
|
|
|
socket->setWebSocket();
|
|
|
|
#endif
|
|
|
|
|
2020-04-19 13:46:01 -05:00
|
|
|
if (!socket)
|
2017-04-02 22:27:06 -05:00
|
|
|
{
|
2018-09-19 02:30:19 -05:00
|
|
|
LOG_ERR("No socket associated with WebSocketHandler " << this);
|
2017-04-02 22:27:06 -05:00
|
|
|
}
|
2019-02-12 05:16:40 -06:00
|
|
|
#if !MOBILEAPP
|
2018-05-01 11:50:13 -05:00
|
|
|
else if (_isClient && !socket->isWebSocket())
|
2020-04-19 13:46:01 -05:00
|
|
|
handleClientUpgrade(socket);
|
2018-09-13 11:16:00 -05:00
|
|
|
#endif
|
2017-04-02 22:27:06 -05:00
|
|
|
else
|
|
|
|
{
|
2020-07-24 11:50:43 -05:00
|
|
|
while (socket->processInputEnabled() && handleTCPStream(socket))
|
2018-07-19 03:39:32 -05:00
|
|
|
; // might have multiple messages in the accumulated buffer.
|
2017-04-02 22:27:06 -05:00
|
|
|
}
|
2017-03-10 08:36:21 -06:00
|
|
|
}
|
|
|
|
|
2020-04-10 06:34:15 -05:00
|
|
|
int getPollEvents(std::chrono::steady_clock::time_point now,
|
2020-04-09 08:43:51 -05:00
|
|
|
int64_t & timeoutMaxMicroS) override
|
2017-03-06 10:26:52 -06:00
|
|
|
{
|
2020-06-24 05:50:08 -05:00
|
|
|
#if !MOBILEAPP
|
2018-05-02 09:40:16 -05:00
|
|
|
if (!_isClient)
|
|
|
|
{
|
2020-04-09 08:43:51 -05:00
|
|
|
const int64_t timeSincePingMicroS =
|
|
|
|
std::chrono::duration_cast<std::chrono::microseconds>(now - _lastPingSentTime).count();
|
|
|
|
timeoutMaxMicroS = std::min(timeoutMaxMicroS, PingFrequencyMicroS - timeSincePingMicroS);
|
2018-05-02 09:40:16 -05:00
|
|
|
}
|
2020-06-24 05:50:08 -05:00
|
|
|
#endif
|
2020-03-06 11:43:46 -06:00
|
|
|
int events = POLLIN;
|
|
|
|
if (_msgHandler && _msgHandler->hasQueuedMessages())
|
|
|
|
events |= POLLOUT;
|
|
|
|
return events;
|
2017-03-06 10:26:52 -06:00
|
|
|
}
|
|
|
|
|
2019-02-12 05:16:40 -06:00
|
|
|
#if !MOBILEAPP
|
2020-03-05 12:42:00 -06:00
|
|
|
private:
|
2017-03-18 09:39:49 -05:00
|
|
|
/// Send a ping message
|
2018-05-02 09:40:16 -05:00
|
|
|
void sendPingOrPong(std::chrono::steady_clock::time_point now,
|
|
|
|
const char* data, const size_t len,
|
|
|
|
const WSOpCode code,
|
|
|
|
const std::shared_ptr<StreamSocket>& socket)
|
2017-03-18 09:39:49 -05:00
|
|
|
{
|
2017-04-09 17:20:52 -05:00
|
|
|
assert(socket && "Expected a valid socket instance.");
|
|
|
|
|
2017-03-18 09:39:49 -05:00
|
|
|
// Must not send this before we're upgraded.
|
2018-02-11 18:14:21 -06:00
|
|
|
if (!socket->isWebSocket())
|
2017-03-18 09:39:49 -05:00
|
|
|
{
|
2018-02-11 18:14:21 -06:00
|
|
|
LOG_WRN("Attempted ping on non-upgraded websocket! #" << socket->getFD());
|
|
|
|
_lastPingSentTime = now; // Pretend we sent it to avoid timing out immediately.
|
2017-03-18 09:39:49 -05:00
|
|
|
return;
|
|
|
|
}
|
2017-04-09 17:20:52 -05:00
|
|
|
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Sending " <<
|
2018-05-02 09:40:16 -05:00
|
|
|
(const char *)(code == WSOpCode::Ping ? " ping." : "pong."));
|
2017-03-18 09:39:49 -05:00
|
|
|
// FIXME: allow an empty payload.
|
2018-05-02 09:40:16 -05:00
|
|
|
sendMessage(data, len, code, false);
|
2018-02-11 18:14:21 -06:00
|
|
|
_lastPingSentTime = now;
|
2017-03-18 09:39:49 -05:00
|
|
|
}
|
|
|
|
|
2018-05-02 09:40:16 -05:00
|
|
|
void sendPing(std::chrono::steady_clock::time_point now,
|
|
|
|
const std::shared_ptr<StreamSocket>& socket)
|
|
|
|
{
|
|
|
|
assert(!_isClient);
|
|
|
|
sendPingOrPong(now, "", 1, WSOpCode::Ping, socket);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sendPong(std::chrono::steady_clock::time_point now,
|
|
|
|
const char* data, const size_t len,
|
|
|
|
const std::shared_ptr<StreamSocket>& socket)
|
|
|
|
{
|
|
|
|
assert(_isClient);
|
|
|
|
sendPingOrPong(now, data, len, WSOpCode::Pong, socket);
|
|
|
|
}
|
2018-09-13 11:16:00 -05:00
|
|
|
#endif
|
2018-05-02 09:40:16 -05:00
|
|
|
|
2017-03-17 17:59:03 -05:00
|
|
|
/// Do we need to handle a timeout ?
|
|
|
|
void checkTimeout(std::chrono::steady_clock::time_point now) override
|
|
|
|
{
|
2019-02-12 05:16:40 -06:00
|
|
|
#if !MOBILEAPP
|
2018-05-02 09:40:16 -05:00
|
|
|
if (_isClient)
|
|
|
|
return;
|
|
|
|
|
2020-04-09 08:43:51 -05:00
|
|
|
const int64_t timeSincePingMicroS =
|
|
|
|
std::chrono::duration_cast<std::chrono::microseconds>(now - _lastPingSentTime).count();
|
|
|
|
if (timeSincePingMicroS >= PingFrequencyMicroS)
|
2017-04-09 17:20:52 -05:00
|
|
|
{
|
2018-02-11 18:14:21 -06:00
|
|
|
const std::shared_ptr<StreamSocket> socket = _socket.lock();
|
2017-04-09 17:20:52 -05:00
|
|
|
if (socket)
|
|
|
|
sendPing(now, socket);
|
|
|
|
}
|
2018-09-13 11:16:00 -05:00
|
|
|
#endif
|
2017-03-17 17:59:03 -05:00
|
|
|
}
|
2020-11-15 08:01:55 -06:00
|
|
|
|
2020-03-05 12:42:00 -06:00
|
|
|
public:
|
2020-03-06 11:43:46 -06:00
|
|
|
void performWrites() override
|
|
|
|
{
|
|
|
|
if (_msgHandler)
|
|
|
|
_msgHandler->writeQueuedMessages();
|
|
|
|
}
|
|
|
|
|
|
|
|
void onDisconnect() override
|
|
|
|
{
|
|
|
|
if (_msgHandler)
|
|
|
|
_msgHandler->onDisconnect();
|
|
|
|
}
|
2017-03-06 10:26:52 -06:00
|
|
|
|
2017-03-14 20:13:36 -05:00
|
|
|
/// Sends a WebSocket Text message.
|
2017-03-29 19:38:41 -05:00
|
|
|
int sendMessage(const std::string& msg) const
|
2017-03-01 12:49:29 -06:00
|
|
|
{
|
2020-03-16 04:34:12 -05:00
|
|
|
return sendTextMessage(msg.c_str(), msg.size());
|
2020-03-06 11:43:46 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Implementation of the ProtocolHandlerInterface.
|
2020-03-16 04:34:12 -05:00
|
|
|
int sendTextMessage(const char* msg, const size_t len, bool flush = false) const override
|
2020-03-06 11:43:46 -06:00
|
|
|
{
|
2020-03-16 04:34:12 -05:00
|
|
|
return sendMessage(msg, len, WSOpCode::Text, flush);
|
2020-03-06 11:43:46 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Implementation of the ProtocolHandlerInterface.
|
|
|
|
int sendBinaryMessage(const char *data, const size_t len, bool flush = false) const override
|
|
|
|
{
|
|
|
|
return sendMessage(data, len, WSOpCode::Binary, flush);
|
2017-03-01 12:49:29 -06:00
|
|
|
}
|
|
|
|
|
2017-02-26 12:37:15 -06:00
|
|
|
/// 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.
|
2017-02-27 00:45:18 -06:00
|
|
|
int sendMessage(const char* data, const size_t len, const WSOpCode code, const bool flush = true) const
|
2017-02-23 10:57:59 -06:00
|
|
|
{
|
2017-10-26 03:38:57 -05:00
|
|
|
int unitReturn = -1;
|
2020-04-01 10:22:23 -05:00
|
|
|
if (!Util::isFuzzing() && UnitBase::get().filterSendMessage(data, len, code, flush, unitReturn))
|
2017-10-26 03:38:57 -05:00
|
|
|
return unitReturn;
|
|
|
|
|
2017-02-28 20:04:44 -06:00
|
|
|
//TODO: Support fragmented messages.
|
2017-02-26 20:32:16 -06:00
|
|
|
|
2018-02-07 03:17:35 -06:00
|
|
|
std::shared_ptr<StreamSocket> socket = _socket.lock();
|
2018-02-11 18:14:21 -06:00
|
|
|
return sendFrame(socket, data, len, WSFrameMask::Fin | static_cast<unsigned char>(code), flush);
|
2017-02-28 20:04:44 -06:00
|
|
|
}
|
2020-04-23 05:35:42 -05:00
|
|
|
|
|
|
|
protected:
|
|
|
|
|
|
|
|
#if !MOBILEAPP
|
|
|
|
/// Builds a websocket frame based on data and flags received as parameters.
|
|
|
|
/// The frame is output in 'out' parameter
|
|
|
|
void buildFrame(const char* data, const uint64_t len, unsigned char flags, std::vector<char>& out) const
|
|
|
|
{
|
2017-02-28 20:04:44 -06:00
|
|
|
out.push_back(flags);
|
2017-02-23 10:57:59 -06:00
|
|
|
|
2018-06-15 08:18:31 -05:00
|
|
|
int maskFlag = _isMasking ? 0x80 : 0;
|
2017-02-23 10:57:59 -06:00
|
|
|
if (len < 126)
|
|
|
|
{
|
2018-06-15 08:18:31 -05:00
|
|
|
out.push_back((char)(len | maskFlag));
|
2017-02-23 10:57:59 -06:00
|
|
|
}
|
|
|
|
else if (len <= 0xffff)
|
|
|
|
{
|
2018-06-15 08:18:31 -05:00
|
|
|
out.push_back((char)(126 | maskFlag));
|
2017-02-28 16:31:27 -06:00
|
|
|
out.push_back(static_cast<char>((len >> 8) & 0xff));
|
|
|
|
out.push_back(static_cast<char>((len >> 0) & 0xff));
|
2017-02-23 10:57:59 -06:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-06-15 08:18:31 -05:00
|
|
|
out.push_back((char)(127 | maskFlag));
|
2017-02-28 16:31:27 -06:00
|
|
|
out.push_back(static_cast<char>((len >> 56) & 0xff));
|
|
|
|
out.push_back(static_cast<char>((len >> 48) & 0xff));
|
|
|
|
out.push_back(static_cast<char>((len >> 40) & 0xff));
|
|
|
|
out.push_back(static_cast<char>((len >> 32) & 0xff));
|
|
|
|
out.push_back(static_cast<char>((len >> 24) & 0xff));
|
|
|
|
out.push_back(static_cast<char>((len >> 16) & 0xff));
|
|
|
|
out.push_back(static_cast<char>((len >> 8) & 0xff));
|
|
|
|
out.push_back(static_cast<char>((len >> 0) & 0xff));
|
2017-02-23 10:57:59 -06:00
|
|
|
}
|
|
|
|
|
2018-06-15 08:18:31 -05:00
|
|
|
if (_isMasking)
|
|
|
|
{ // flip some top bits - perhaps it helps.
|
|
|
|
size_t mask = out.size();
|
|
|
|
|
|
|
|
out.push_back(static_cast<char>(0x81));
|
|
|
|
out.push_back(static_cast<char>(0x76));
|
|
|
|
out.push_back(static_cast<char>(0x81));
|
|
|
|
out.push_back(static_cast<char>(0x76));
|
|
|
|
|
|
|
|
// Copy the data.
|
|
|
|
out.insert(out.end(), data, data + len);
|
|
|
|
|
|
|
|
// Mask it.
|
|
|
|
for (size_t i = 4; i < out.size() - mask; ++i)
|
|
|
|
out[mask + i] = out[mask + i] ^ out[mask + (i%4)];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Copy the data.
|
|
|
|
out.insert(out.end(), data, data + len);
|
|
|
|
}
|
2020-04-23 05:35:42 -05:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/// 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.
|
|
|
|
int sendFrame(const std::shared_ptr<StreamSocket>& socket,
|
|
|
|
const char* data, const uint64_t len,
|
|
|
|
unsigned char flags, const bool flush = true) const
|
|
|
|
{
|
|
|
|
if (!socket || data == nullptr || len == 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (socket->isClosed())
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
socket->assertCorrectThread();
|
|
|
|
std::vector<char>& out = socket->getOutBuffer();
|
|
|
|
|
|
|
|
#if !MOBILEAPP
|
|
|
|
const size_t oldSize = out.size();
|
|
|
|
|
|
|
|
buildFrame(data, len, flags, out);
|
|
|
|
|
2017-04-09 17:24:51 -05:00
|
|
|
const size_t size = out.size() - oldSize;
|
2020-03-12 12:01:39 -05:00
|
|
|
|
|
|
|
if (flush)
|
|
|
|
socket->writeOutgoingData();
|
2018-09-13 11:16:00 -05:00
|
|
|
#else
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC("WebSocketHandle::sendFrame: Writing to #" << socket->getFD() << ' ' << len << " bytes");
|
2017-02-28 20:04:44 -06:00
|
|
|
|
2020-03-12 12:01:39 -05:00
|
|
|
// We ignore the flush parameter and always flush in the MOBILEAPP case because there is no
|
|
|
|
// WebSocket framing, we put the messages as such into the FakeSocket queue.
|
|
|
|
|
|
|
|
(void) flush;
|
2018-09-13 11:16:00 -05:00
|
|
|
out.insert(out.end(), data, data + len);
|
|
|
|
const size_t size = out.size();
|
2020-03-12 12:01:39 -05:00
|
|
|
|
|
|
|
socket->writeOutgoingData();
|
2018-09-13 11:16:00 -05:00
|
|
|
#endif
|
2017-02-26 12:37:15 -06:00
|
|
|
|
2017-04-09 17:24:51 -05:00
|
|
|
return size;
|
2017-02-23 10:57:59 -06:00
|
|
|
}
|
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
bool isControlFrame(WSOpCode code){ return code >= WSOpCode::Close; }
|
|
|
|
|
|
|
|
void readPayload(unsigned char *data, size_t dataLen, unsigned char* mask, std::vector<char>& payload)
|
|
|
|
{
|
2020-01-06 09:10:02 -06:00
|
|
|
if (dataLen == 0)
|
|
|
|
return;
|
|
|
|
|
2019-03-08 02:21:17 -06:00
|
|
|
if (mask)
|
|
|
|
{
|
|
|
|
size_t end = payload.size();
|
|
|
|
payload.resize(end + dataLen);
|
net: avoid UB in WebSocketHandler::readPayload()
Seen when closing a Writer document.
/home/vmiklos/lode/opt_private/gcc-7.3.0/lib/gcc/x86_64-pc-linux-gnu/7.3.0/../../../../include/c++/7.3.0/bits/stl_vector.h:798:9: runtime error: reference binding to null pointer of type 'char'
#0 0x6ff633 in std::vector<char, std::allocator<char> >::operator[](unsigned long) /home/vmiklos/lode/opt_private/gcc-7.3.0/lib/gcc/x86_64-pc-linux-gnu/7.3.0/../../../../include/c++/7.3.0/bits/stl_vector.h:798:2
#1 0x770d0c in WebSocketHandler::readPayload(unsigned char*, unsigned long, unsigned char*, std::vector<char, std::allocator<char> >&) /home/vmiklos/lode/dev/online/./net/WebSocketHandler.hpp:611:29
#2 0x759324 in WebSocketHandler::handleTCPStream(std::shared_ptr<StreamSocket> const&) /home/vmiklos/lode/dev/online/./net/WebSocketHandler.hpp:251:13
#3 0x6f820d in WebSocketHandler::handleIncomingMessage(SocketDisposition&) /home/vmiklos/lode/dev/online/./net/WebSocketHandler.hpp:419:20
#4 0xb2da64 in ClientSession::handleIncomingMessage(SocketDisposition&) /home/vmiklos/lode/dev/online/wsd/ClientSession.cpp:74:14
#5 0xa70a61 in StreamSocket::handlePoll(SocketDisposition&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >, int) /home/vmiklos/lode/dev/online/./net/Socket.hpp:1037:29
#6 0x6ec83d in SocketPoll::poll(int) /home/vmiklos/lode/dev/online/./net/Socket.hpp:570:34
#7 0x830019 in DocumentBroker::pollThread() /home/vmiklos/lode/dev/online/wsd/DocumentBroker.cpp:286:16
#8 0x8fdb38 in DocumentBroker::DocumentBrokerPoll::pollingThread() /home/vmiklos/lode/dev/online/wsd/DocumentBroker.cpp:165:20
#9 0xe00e75 in SocketPoll::pollingThreadEntry() /home/vmiklos/lode/dev/online/net/Socket.cpp:184:9
#10 0xe49cfd in void std::__invoke_impl<void, void (SocketPoll::*)(), SocketPoll*>(std::__invoke_memfun_deref, void (SocketPoll::*&&)(), SocketPoll*&&) /home/vmiklos/lode/opt_private/gcc-7.3.0/lib/gcc/x86_64-pc-linux-gnu/7.3.0/../../../../include/c++/7.3.0/bits/invoke.h:73:14
#11 0xe4980a in std::__invoke_result<void (SocketPoll::*)(), SocketPoll*>::type std::__invoke<void (SocketPoll::*)(), SocketPoll*>(void (SocketPoll::*&&)(), SocketPoll*&&) /home/vmiklos/lode/opt_private/gcc-7.3.0/lib/gcc/x86_64-pc-linux-gnu/7.3.0/../../../../include/c++/7.3.0/bits/invoke.h:95:14
#12 0xe496bd in decltype(std::__invoke(_S_declval<0ul>(), _S_declval<1ul>())) std::thread::_Invoker<std::tuple<void (SocketPoll::*)(), SocketPoll*> >::_M_invoke<0ul, 1ul>(std::_Index_tuple<0ul, 1ul>) /home/vmiklos/lode/opt_private/gcc-7.3.0/lib/gcc/x86_64-pc-linux-gnu/7.3.0/../../../../include/c++/7.3.0/thread:234:13
#13 0xe494c7 in std::thread::_Invoker<std::tuple<void (SocketPoll::*)(), SocketPoll*> >::operator()() /home/vmiklos/lode/opt_private/gcc-7.3.0/lib/gcc/x86_64-pc-linux-gnu/7.3.0/../../../../include/c++/7.3.0/thread:243:11
#14 0xe4888a in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (SocketPoll::*)(), SocketPoll*> > >::_M_run() /home/vmiklos/lode/opt_private/gcc-7.3.0/lib/gcc/x86_64-pc-linux-gnu/7.3.0/../../../../include/c++/7.3.0/thread:186:13
#15 0x7f2c5805fe2e in execute_native_thread_routine /home/vmiklos/lode/packages/gccbuild/x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/../../../../../gcc-7.3.0/libstdc++-v3/src/c++11/thread.cc:83
#16 0x7f2c57a3c558 in start_thread (/lib64/libpthread.so.0+0x7558)
#17 0x7f2c5715082e in clone (/lib64/libc.so.6+0xf882e)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/vmiklos/lode/opt_private/gcc-7.3.0/lib/gcc/x86_64-pc-linux-gnu/7.3.0/../../../../include/c++/7.3.0/bits/stl_vector.h:798:9 in
Change-Id: Ifaf6b193e9bba480587c2e184df55aa0728bb370
2019-05-27 02:06:38 -05:00
|
|
|
if (dataLen > 0)
|
|
|
|
{
|
|
|
|
char* wsData = &payload[end];
|
|
|
|
for (size_t i = 0; i < dataLen; ++i)
|
|
|
|
*wsData++ = data[i] ^ mask[i % 4];
|
|
|
|
}
|
2019-03-08 02:21:17 -06:00
|
|
|
}
|
|
|
|
else
|
|
|
|
payload.insert(payload.end(), data, data + dataLen);
|
|
|
|
}
|
|
|
|
|
2020-10-03 11:38:25 -05:00
|
|
|
/// To be overridden to handle the websocket messages the way you need.
|
2020-03-06 11:43:46 -06:00
|
|
|
virtual void handleMessage(const std::vector<char> &data)
|
2017-02-26 10:31:52 -06:00
|
|
|
{
|
2020-03-06 11:43:46 -06:00
|
|
|
if (_msgHandler)
|
|
|
|
_msgHandler->handleMessage(data);
|
2017-02-26 10:31:52 -06:00
|
|
|
}
|
2017-03-02 03:38:49 -06:00
|
|
|
|
2020-04-23 05:35:42 -05:00
|
|
|
const std::weak_ptr<StreamSocket>& getSocket() const
|
2018-10-24 02:35:46 -05:00
|
|
|
{
|
|
|
|
return _socket;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setSocket(const std::weak_ptr<StreamSocket>& socket)
|
|
|
|
{
|
|
|
|
_socket = socket;
|
|
|
|
}
|
|
|
|
|
2020-03-06 11:43:46 -06:00
|
|
|
/// Implementation of the ProtocolHandlerInterface.
|
2017-03-18 09:59:09 -05:00
|
|
|
void dumpState(std::ostream& os) override;
|
|
|
|
|
2017-03-02 03:38:49 -06:00
|
|
|
private:
|
|
|
|
/// To make the protected 'computeAccept' accessible.
|
2020-11-15 08:01:55 -06:00
|
|
|
class PublicComputeAccept final : public Poco::Net::WebSocket
|
2017-03-02 03:38:49 -06:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
static std::string doComputeAccept(const std::string &key)
|
|
|
|
{
|
|
|
|
return computeAccept(key);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
protected:
|
|
|
|
/// Upgrade the http(s) connection to a websocket.
|
|
|
|
void upgradeToWebSocket(const Poco::Net::HTTPRequest& req)
|
|
|
|
{
|
2018-02-07 03:17:35 -06:00
|
|
|
std::shared_ptr<StreamSocket> socket = _socket.lock();
|
2020-04-19 13:46:01 -05:00
|
|
|
if (!socket)
|
2017-03-02 03:38:49 -06:00
|
|
|
throw std::runtime_error("Invalid socket while upgrading to WebSocket. Request: " + req.getURI());
|
|
|
|
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Upgrading to WebSocket.");
|
2018-02-11 18:14:21 -06:00
|
|
|
assert(!socket->isWebSocket());
|
2017-03-14 21:14:20 -05:00
|
|
|
|
2019-02-12 05:16:40 -06:00
|
|
|
#if !MOBILEAPP
|
2017-03-02 03:38:49 -06:00
|
|
|
// create our websocket goodness ...
|
|
|
|
const int wsVersion = std::stoi(req.get("Sec-WebSocket-Version", "13"));
|
|
|
|
const std::string wsKey = req.get("Sec-WebSocket-Key", "");
|
|
|
|
const std::string wsProtocol = req.get("Sec-WebSocket-Protocol", "chat");
|
|
|
|
// FIXME: other sanity checks ...
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_INF('#' << socket->getFD() << ": WebSocket version: " << wsVersion <<
|
2017-04-09 17:20:52 -05:00
|
|
|
", key: [" << wsKey << "], protocol: [" << wsProtocol << "].");
|
|
|
|
|
2017-04-19 03:58:18 -05:00
|
|
|
#if ENABLE_DEBUG
|
|
|
|
if (std::getenv("LOOL_ZERO_BUFFER_SIZE"))
|
|
|
|
socket->setSocketBufferSize(0);
|
|
|
|
#endif
|
2017-03-02 03:38:49 -06:00
|
|
|
|
|
|
|
std::ostringstream oss;
|
|
|
|
oss << "HTTP/1.1 101 Switching Protocols\r\n"
|
|
|
|
<< "Upgrade: websocket\r\n"
|
|
|
|
<< "Connection: Upgrade\r\n"
|
2017-03-02 03:39:24 -06:00
|
|
|
<< "Sec-WebSocket-Accept: " << PublicComputeAccept::doComputeAccept(wsKey) << "\r\n"
|
2017-03-02 03:38:49 -06:00
|
|
|
<< "\r\n";
|
|
|
|
|
2017-04-09 17:20:52 -05:00
|
|
|
const std::string res = oss.str();
|
2020-05-24 08:10:18 -05:00
|
|
|
LOG_TRC('#' << socket->getFD() << ": Sending WS Upgrade response: " << res);
|
2017-04-09 17:20:52 -05:00
|
|
|
socket->send(res);
|
2018-09-13 11:16:00 -05:00
|
|
|
#endif
|
2018-04-18 13:20:54 -05:00
|
|
|
setWebSocket();
|
|
|
|
}
|
2017-03-10 03:55:28 -06:00
|
|
|
|
2019-02-12 05:16:40 -06:00
|
|
|
#if !MOBILEAPP
|
2018-05-01 11:50:13 -05:00
|
|
|
// Handle incoming upgrade to full socket as client WS.
|
2020-04-19 13:46:01 -05:00
|
|
|
void handleClientUpgrade(const std::shared_ptr<StreamSocket>& socket)
|
2018-05-01 11:50:13 -05:00
|
|
|
{
|
2020-04-19 13:46:01 -05:00
|
|
|
assert(socket && "socket must be valid");
|
2018-05-01 11:50:13 -05:00
|
|
|
|
2019-03-31 22:30:05 -05:00
|
|
|
LOG_TRC("Incoming client websocket upgrade response: "
|
|
|
|
<< std::string(&socket->getInBuffer()[0], socket->getInBuffer().size()));
|
2018-05-01 11:50:13 -05:00
|
|
|
|
|
|
|
bool bOk = false;
|
2019-05-21 21:21:50 -05:00
|
|
|
StreamSocket::MessageMap map;
|
2018-05-03 10:32:31 -05:00
|
|
|
|
|
|
|
try
|
2018-05-01 11:50:13 -05:00
|
|
|
{
|
2018-10-25 09:38:54 -05:00
|
|
|
Poco::MemoryInputStream message(&socket->getInBuffer()[0], socket->getInBuffer().size());;
|
2018-05-03 10:32:31 -05:00
|
|
|
Poco::Net::HTTPResponse response;
|
|
|
|
|
|
|
|
response.read(message);
|
|
|
|
|
2018-05-01 11:50:13 -05:00
|
|
|
{
|
2018-05-03 10:32:31 -05:00
|
|
|
static const std::string marker("\r\n\r\n");
|
2018-10-25 09:38:54 -05:00
|
|
|
auto itBody = std::search(socket->getInBuffer().begin(),
|
|
|
|
socket->getInBuffer().end(),
|
2018-05-03 10:32:31 -05:00
|
|
|
marker.begin(), marker.end());
|
|
|
|
|
2018-10-25 09:38:54 -05:00
|
|
|
if (itBody != socket->getInBuffer().end())
|
2019-05-21 21:21:50 -05:00
|
|
|
map._headerSize = itBody - socket->getInBuffer().begin() + marker.size();
|
2018-05-01 11:50:13 -05:00
|
|
|
}
|
2018-05-03 10:32:31 -05:00
|
|
|
|
2019-03-31 22:30:05 -05:00
|
|
|
if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_SWITCHING_PROTOCOLS
|
|
|
|
&& response.has("Upgrade")
|
|
|
|
&& Poco::icompare(response.get("Upgrade"), "websocket") == 0)
|
2018-05-03 10:32:31 -05:00
|
|
|
{
|
|
|
|
#if 0 // SAL_DEBUG ...
|
|
|
|
const std::string wsKey = response.get("Sec-WebSocket-Accept", "");
|
|
|
|
const std::string wsProtocol = response.get("Sec-WebSocket-Protocol", "");
|
|
|
|
if (Poco::icompare(wsProtocol, "chat") != 0)
|
|
|
|
LOG_ERR("Unknown websocket protocol " << wsProtocol);
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
LOG_TRC("Accepted incoming websocket response");
|
|
|
|
// FIXME: validate Sec-WebSocket-Accept vs. Sec-WebSocket-Key etc.
|
|
|
|
bOk = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const Poco::Exception& exc)
|
|
|
|
{
|
|
|
|
LOG_DBG("handleClientUpgrade exception caught: " << exc.displayText());
|
|
|
|
}
|
|
|
|
catch (const std::exception& exc)
|
|
|
|
{
|
|
|
|
LOG_DBG("handleClientUpgrade exception caught.");
|
2018-05-01 11:50:13 -05:00
|
|
|
}
|
|
|
|
|
2019-05-21 21:21:50 -05:00
|
|
|
if (!bOk || map._headerSize == 0)
|
2018-05-01 11:50:13 -05:00
|
|
|
{
|
2018-05-03 10:32:31 -05:00
|
|
|
LOG_ERR("Bad websocker server response.");
|
|
|
|
|
2018-05-01 11:50:13 -05:00
|
|
|
socket->shutdown();
|
2018-05-03 10:32:31 -05:00
|
|
|
return;
|
2018-05-01 11:50:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
setWebSocket();
|
2019-05-21 21:21:50 -05:00
|
|
|
socket->eraseFirstInputBytes(map);
|
2018-05-01 11:50:13 -05:00
|
|
|
}
|
2018-09-13 11:16:00 -05:00
|
|
|
#endif
|
2018-05-01 11:50:13 -05:00
|
|
|
|
2018-04-18 13:20:54 -05:00
|
|
|
void setWebSocket()
|
|
|
|
{
|
|
|
|
std::shared_ptr<StreamSocket> socket = _socket.lock();
|
2020-04-19 13:46:01 -05:00
|
|
|
if (socket)
|
|
|
|
socket->setWebSocket();
|
2020-06-24 05:50:08 -05:00
|
|
|
#if !MOBILEAPP
|
2017-04-09 17:20:52 -05:00
|
|
|
// No need to ping right upon connection/upgrade,
|
|
|
|
// but do reset the time to avoid pinging immediately after.
|
2018-02-11 18:14:21 -06:00
|
|
|
_lastPingSentTime = std::chrono::steady_clock::now();
|
2020-06-24 05:50:08 -05:00
|
|
|
#endif
|
2017-03-02 03:38:49 -06:00
|
|
|
}
|
2020-07-10 07:22:35 -05:00
|
|
|
|
|
|
|
virtual void enableProcessInput(bool enable = true) override
|
|
|
|
{
|
|
|
|
std::shared_ptr<StreamSocket> socket = _socket.lock();
|
|
|
|
if (socket)
|
|
|
|
socket->enableProcessInput(enable);
|
|
|
|
}
|
|
|
|
|
2020-09-15 02:08:21 -05:00
|
|
|
virtual bool processInputEnabled() const override
|
2020-07-10 07:22:35 -05:00
|
|
|
{
|
|
|
|
std::shared_ptr<StreamSocket> socket = _socket.lock();
|
|
|
|
if (socket)
|
|
|
|
return socket->processInputEnabled();
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2017-02-26 10:31:52 -06:00
|
|
|
};
|
|
|
|
|
2017-02-23 10:57:59 -06:00
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|