2016-11-10 02:47:25 -06:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
|
|
/*
|
2023-11-18 08:13:14 -06:00
|
|
|
* Copyright the Collabora Online contributors.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
|
|
*
|
2016-11-10 02:47:25 -06:00
|
|
|
* 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
|
2016-11-10 02:47:25 -06:00
|
|
|
|
2021-03-08 20:59:33 -06:00
|
|
|
#include <test/testlog.hpp>
|
|
|
|
|
2016-11-21 04:06:05 -06:00
|
|
|
#include <cstdlib>
|
2016-11-10 02:47:25 -06:00
|
|
|
#include <mutex>
|
2016-11-21 04:06:05 -06:00
|
|
|
#include <thread>
|
2016-11-10 02:47:25 -06:00
|
|
|
|
|
|
|
#include <Poco/Net/WebSocket.h>
|
|
|
|
|
|
|
|
#include <Common.hpp>
|
2016-11-24 08:56:06 -06:00
|
|
|
#include <Protocol.hpp>
|
2016-11-10 02:47:25 -06:00
|
|
|
#include <Log.hpp>
|
|
|
|
|
2018-07-26 03:23:05 -05:00
|
|
|
/// Deprecated: do not use ... replaced by net/Socket.hpp
|
|
|
|
///
|
2016-11-10 02:47:25 -06:00
|
|
|
/// WebSocket that is thread safe, and handles large frames transparently.
|
2016-11-12 12:41:54 -06:00
|
|
|
/// Careful - sendFrame and receiveFrame are _not_ virtual,
|
2021-11-18 06:08:14 -06:00
|
|
|
/// we need to make sure that we use COOLWebSocket all over the place.
|
2016-11-12 12:41:54 -06:00
|
|
|
/// It would be a kind of more natural to encapsulate Poco::Net::WebSocket
|
|
|
|
/// instead of inheriting (from that reason,) but that would requite much
|
|
|
|
/// larger code changes.
|
2021-11-18 06:08:14 -06:00
|
|
|
class COOLWebSocket : public Poco::Net::WebSocket
|
2016-11-10 02:47:25 -06:00
|
|
|
{
|
|
|
|
public:
|
2021-11-18 06:08:14 -06:00
|
|
|
COOLWebSocket(const Socket& socket) :
|
2016-11-12 12:41:54 -06:00
|
|
|
Poco::Net::WebSocket(socket)
|
2016-11-10 02:47:25 -06:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-11-18 06:08:14 -06:00
|
|
|
COOLWebSocket(Poco::Net::HTTPClientSession& cs,
|
2016-11-12 12:41:54 -06:00
|
|
|
Poco::Net::HTTPRequest& request,
|
|
|
|
Poco::Net::HTTPResponse& response) :
|
|
|
|
Poco::Net::WebSocket(cs, request, response)
|
2016-11-10 02:47:25 -06:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-11-25 03:46:01 -06:00
|
|
|
/// Wrapper for Poco::Net::WebSocket::receiveFrame() that handles PING frames
|
|
|
|
/// (by replying with a PONG frame) and PONG frames. PONG frames are ignored.
|
2016-12-02 08:43:39 -06:00
|
|
|
|
|
|
|
/// Returns number of bytes received, or 0 if the Poco receiveFrame() returned 0,
|
|
|
|
/// or -1 if no "interesting" (not PING or PONG) frame was actually received).
|
|
|
|
|
2016-11-25 03:46:01 -06:00
|
|
|
/// Should we also factor out the handling of non-final and continuation frames into this?
|
2021-03-08 20:59:33 -06:00
|
|
|
int receiveFrame(char* buffer, const int length, int& flags,
|
|
|
|
const std::string& testname = std::string())
|
2016-11-25 03:46:01 -06:00
|
|
|
{
|
2016-12-12 22:08:01 -06:00
|
|
|
// Timeout is in microseconds. We don't need this, except to yield the cpu.
|
2020-04-09 08:43:51 -05:00
|
|
|
static const Poco::Timespan waitTime(POLL_TIMEOUT_MICRO_S / 10);
|
2016-11-25 03:46:01 -06:00
|
|
|
|
|
|
|
while (poll(waitTime, Poco::Net::Socket::SELECT_READ))
|
|
|
|
{
|
|
|
|
const int n = Poco::Net::WebSocket::receiveFrame(buffer, length, flags);
|
2017-01-18 07:45:33 -06:00
|
|
|
|
|
|
|
if (n <= 0)
|
2021-03-08 20:59:33 -06:00
|
|
|
TST_LOG("Got nothing (" << n << ')');
|
2017-01-18 07:45:33 -06:00
|
|
|
else
|
2021-03-08 20:59:33 -06:00
|
|
|
TST_LOG("Got frame: " << getAbbreviatedFrameDump(buffer, n, flags));
|
2016-12-12 22:08:01 -06:00
|
|
|
|
2016-12-13 16:55:10 -06:00
|
|
|
if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_CLOSE)
|
|
|
|
{
|
|
|
|
// Nothing to do.
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PING)
|
2016-11-25 03:46:01 -06:00
|
|
|
{
|
2016-12-12 22:08:01 -06:00
|
|
|
// Echo back the ping message.
|
2021-03-08 20:59:33 -06:00
|
|
|
if (Poco::Net::WebSocket::sendFrame(buffer, n,
|
|
|
|
static_cast<int>(WebSocket::FRAME_FLAG_FIN)
|
|
|
|
| WebSocket::FRAME_OP_PONG)
|
|
|
|
!= n)
|
2016-12-12 22:08:01 -06:00
|
|
|
{
|
2021-03-08 20:59:33 -06:00
|
|
|
TST_LOG("WARN: Sending Pong failed.");
|
2016-12-12 22:08:01 -06:00
|
|
|
return -1;
|
|
|
|
}
|
2016-11-25 03:46:01 -06:00
|
|
|
}
|
2016-12-13 16:55:10 -06:00
|
|
|
else if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PONG)
|
2016-11-25 03:46:01 -06:00
|
|
|
{
|
2016-12-12 22:08:01 -06:00
|
|
|
// In case we do send pings in the future.
|
2016-11-25 03:46:01 -06:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-12 22:08:01 -06:00
|
|
|
// Not ready for read.
|
2016-11-25 03:46:01 -06:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-11-12 12:41:54 -06:00
|
|
|
/// Wrapper for Poco::Net::WebSocket::sendFrame() that handles large frames.
|
2021-03-08 20:59:33 -06:00
|
|
|
int sendFrame(const char* buffer, const int length, const int flags = FRAME_TEXT,
|
|
|
|
const std::string& testname = std::string())
|
2016-11-10 02:47:25 -06:00
|
|
|
{
|
2017-04-02 23:45:40 -05:00
|
|
|
const int result = Poco::Net::WebSocket::sendFrame(buffer, length, flags);
|
2016-11-12 12:41:16 -06:00
|
|
|
|
|
|
|
if (result != length)
|
|
|
|
{
|
2021-03-08 20:59:33 -06:00
|
|
|
TST_LOG("ERROR: Sent incomplete message, expected "
|
|
|
|
<< length << " bytes but sent " << result
|
|
|
|
<< " for: " << getAbbreviatedFrameDump(buffer, length, flags));
|
2016-11-12 12:41:16 -06:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-03-08 20:59:33 -06:00
|
|
|
TST_LOG("Sent frame: " << getAbbreviatedFrameDump(buffer, length, flags));
|
2016-11-12 12:41:16 -06:00
|
|
|
}
|
2016-11-10 02:47:25 -06:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2017-01-15 11:28:52 -06:00
|
|
|
|
|
|
|
/// Safe shutdown by sending a normal close frame, if socket is not in error,
|
|
|
|
/// or, otherwise, close the socket without sending close frame, if it is.
|
2021-03-08 20:59:33 -06:00
|
|
|
void shutdown(const std::string& testname = std::string())
|
2017-01-15 11:28:52 -06:00
|
|
|
{
|
2021-03-08 20:59:33 -06:00
|
|
|
shutdown(Poco::Net::WebSocket::StatusCodes::WS_NORMAL_CLOSE, testname);
|
2017-01-15 11:28:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Safe shutdown by sending a specific close frame, if socket is not in error,
|
|
|
|
/// or, otherwise, close the socket without sending close frame, if it is.
|
2021-03-08 20:59:33 -06:00
|
|
|
void shutdown(Poco::UInt16 statusCode, const std::string& statusMessage = std::string(),
|
|
|
|
const std::string& testname = std::string())
|
2017-01-15 11:28:52 -06:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// Calling shutdown, in case of error, would try to send a 'close' frame
|
|
|
|
// which won't work in case of broken pipe or timeout from peer. Just close the
|
|
|
|
// socket in that case preventing 'close' frame from being sent.
|
|
|
|
if (Poco::Net::WebSocket::poll(Poco::Timespan(0), Socket::SelectMode::SELECT_ERROR))
|
|
|
|
{
|
|
|
|
Poco::Net::WebSocket::close();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Poco::Net::WebSocket::shutdown(statusCode, statusMessage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const Poco::Exception& exc)
|
|
|
|
{
|
2021-11-18 06:08:14 -06:00
|
|
|
TST_LOG("WARN: COOLWebSocket::shutdown: Exception: "
|
2021-03-08 20:59:33 -06:00
|
|
|
<< exc.displayText()
|
|
|
|
<< (exc.nested() ? " (" + exc.nested()->displayText() + ')' : ""));
|
2017-01-20 15:34:10 -06:00
|
|
|
|
|
|
|
// Just close it.
|
|
|
|
try
|
|
|
|
{
|
|
|
|
Poco::Net::WebSocket::close();
|
|
|
|
}
|
|
|
|
catch (const std::exception&)
|
|
|
|
{
|
|
|
|
// Nothing we can do.
|
|
|
|
}
|
2017-01-15 11:28:52 -06:00
|
|
|
}
|
|
|
|
}
|
2020-04-18 05:55:50 -05:00
|
|
|
|
|
|
|
// Return a string dump of a WebSocket frame: Its opcode, length, first line (if present),
|
|
|
|
// flags. For human-readable logging purposes. Format not guaranteed to be stable. Not to be
|
|
|
|
// inspected programmatically.
|
|
|
|
static inline
|
|
|
|
std::string getAbbreviatedFrameDump(const char *message, const int length, const int flags)
|
|
|
|
{
|
|
|
|
std::ostringstream result;
|
|
|
|
switch (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK)
|
|
|
|
{
|
2020-12-24 07:55:12 -06:00
|
|
|
#define CASE(x) \
|
|
|
|
case Poco::Net::WebSocket::FRAME_OP_##x: \
|
|
|
|
result << #x << " (0x" << std::hex << flags << ')'; \
|
|
|
|
break
|
|
|
|
CASE(CONT);
|
|
|
|
CASE(TEXT);
|
|
|
|
CASE(BINARY);
|
|
|
|
CASE(CLOSE);
|
|
|
|
CASE(PING);
|
|
|
|
CASE(PONG);
|
2020-04-18 05:55:50 -05:00
|
|
|
#undef CASE
|
2020-12-24 07:55:12 -06:00
|
|
|
default:
|
|
|
|
result << "UNKNOWN (0x" << std::hex << flags << ')';
|
|
|
|
break;
|
2020-04-18 05:55:50 -05:00
|
|
|
}
|
2020-12-24 07:55:12 -06:00
|
|
|
|
2021-03-08 17:16:39 -06:00
|
|
|
result << ' ' << std::setw(3) << std::dec << length << " bytes"
|
2020-12-24 07:55:12 -06:00
|
|
|
<< (flags & Poco::Net::WebSocket::FRAME_FLAG_FIN ? " (FIN)" : "");
|
2020-04-18 05:55:50 -05:00
|
|
|
|
|
|
|
if (length > 0 &&
|
|
|
|
((flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) == Poco::Net::WebSocket::FRAME_OP_TEXT ||
|
|
|
|
(flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) == Poco::Net::WebSocket::FRAME_OP_BINARY ||
|
|
|
|
(flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) == Poco::Net::WebSocket::FRAME_OP_PING ||
|
|
|
|
(flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) == Poco::Net::WebSocket::FRAME_OP_PONG))
|
2021-11-18 06:08:14 -06:00
|
|
|
result << ": '" << COOLProtocol::getAbbreviatedMessage(message, length) << '\'';
|
2020-04-18 05:55:50 -05:00
|
|
|
return result.str();
|
|
|
|
}
|
2016-11-10 02:47:25 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|