3#include <xrpl/basics/safe_cast.h>
4#include <xrpl/beast/utility/Journal.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/beast/utility/rngfill.h>
7#include <xrpl/crypto/csprng.h>
8#include <xrpl/protocol/BuildInfo.h>
9#include <xrpl/server/Handoff.h>
10#include <xrpl/server/Port.h>
11#include <xrpl/server/WSSession.h>
12#include <xrpl/server/detail/BasePeer.h>
13#include <xrpl/server/detail/LowestLayer.h>
15#include <boost/asio/error.hpp>
16#include <boost/beast/core/multi_buffer.hpp>
17#include <boost/beast/http/message.hpp>
18#include <boost/beast/websocket.hpp>
19#include <boost/logic/tribool.hpp>
36template <
class Handler,
class Impl>
47 friend class BasePeer<Handler, Impl>;
50 boost::beast::multi_buffer
rb_;
51 boost::beast::multi_buffer
wb_;
59 boost::beast::websocket::close_reason
cr_;
65 std::function<void(boost::beast::websocket::frame_type, boost::beast::string_view)>
69 template <
class Body,
class Headers>
73 boost::asio::executor
const& executor,
76 boost::beast::http::request<Body, Headers>&&
request,
86 [[nodiscard]]
Port const&
95 return this->request_;
98 [[nodiscard]] boost::asio::ip::tcp::endpoint
const&
111 close(boost::beast::websocket::close_reason
const& reason)
override;
120 return *
static_cast<Impl*
>(
this);
154 onPingPong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload);
159 template <
class String>
166template <
class Handler,
class Impl>
167template <
class Body,
class Headers>
171 boost::asio::executor
const& executor,
174 boost::beast::http::request<Body, Headers>&&
request,
176 :
BasePeer<Handler, Impl>(
port, handler, executor, remoteAddress, journal)
183template <
class Handler,
class Impl>
187 if (!
strand_.running_in_this_thread())
188 return post(
strand_, [self =
impl().shared_from_this()] { self->run(); });
189 impl().ws_.set_option(
port().pmdOptions);
193 boost::beast::websocket::frame_type kind,
194 boost::beast::string_view payload) {
onPingPong(kind, payload); };
198 impl().ws_.set_option(boost::beast::websocket::stream_base::decorator([](
auto& res) {
201 impl().ws_.async_accept(
203 self->onWsHandshake(ec);
207template <
class Handler,
class Impl>
211 if (!
strand_.running_in_this_thread())
214 strand_, [self =
impl().shared_from_this(), w = std::move(w)] { self->send(w); });
218 if (
wq_.size() >
port().wsQueueLimit)
220 cr_.code =
safeCast<
decltype(
cr_.code)>(boost::beast::websocket::close_code::policy_error);
221 cr_.reason =
"Policy error: client is too slow.";
222 JLOG(this->
j_.info()) <<
cr_.reason;
227 wq_.emplace_back(std::move(w));
232template <
class Handler,
class Impl>
236 close(boost::beast::websocket::close_reason{});
239template <
class Handler,
class Impl>
243 if (!
strand_.running_in_this_thread())
244 return post(
strand_, [self =
impl().shared_from_this(), reason] { self->close(reason); });
250 impl().ws_.async_close(
253 strand_, [self =
impl().shared_from_this()](boost::beast::error_code
const& ec) {
263template <
class Handler,
class Impl>
267 if (!
strand_.running_in_this_thread())
268 return post(
strand_, [self =
impl().shared_from_this()] { self->complete(); });
272template <
class Handler,
class Impl>
277 return fail(ec,
"on_ws_handshake");
282template <
class Handler,
class Impl>
286 if (!
strand_.running_in_this_thread())
287 return post(
strand_, [self =
impl().shared_from_this()] { self->doWrite(); });
291template <
class Handler,
class Impl>
296 return fail(ec,
"write");
297 auto& w = *
wq_.front();
298 auto const result = w.prepare(65536, [self =
impl().shared_from_this()] { self->doWrite(); });
299 if (boost::indeterminate(result.first))
304 impl().ws_.async_write_some(
305 static_cast<bool>(result.first),
314 impl().ws_.async_write_some(
315 static_cast<bool>(result.first),
319 self->onWriteFin(ec);
324template <
class Handler,
class Impl>
329 return fail(ec,
"write_fin");
333 impl().ws_.async_close(
338 else if (!
wq_.empty())
344template <
class Handler,
class Impl>
348 if (!
strand_.running_in_this_thread())
349 return post(
strand_, [self =
impl().shared_from_this()] { self->doRead(); });
350 impl().ws_.async_read(
358template <
class Handler,
class Impl>
362 if (ec == boost::beast::websocket::error::closed)
365 return fail(ec,
"read");
366 auto const& data =
rb_.data();
370 this->
handler_.onWSMessage(
impl().shared_from_this(), b);
374template <
class Handler,
class Impl>
381template <
class Handler,
class Impl>
393 catch (boost::system::system_error
const& e)
395 return fail(e.code(),
"start_timer");
398 timer_.async_wait(bind_executor(
403template <
class Handler,
class Impl>
411 catch (boost::system::system_error
const&)
417template <
class Handler,
class Impl>
421 if (ec == boost::asio::error::operation_aborted)
429template <
class Handler,
class Impl>
432 boost::beast::websocket::frame_type kind,
433 boost::beast::string_view payload)
435 if (kind == boost::beast::websocket::frame_type::pong)
437 boost::beast::string_view
const p(
payload_.begin());
441 JLOG(this->
j_.trace()) <<
"got matching pong";
445 JLOG(this->
j_.trace()) <<
"got pong";
450template <
class Handler,
class Impl>
454 if (ec == boost::asio::error::operation_aborted)
465 impl().ws_.async_ping(
470 JLOG(this->
j_.trace()) <<
"sent ping";
473 ec = boost::system::errc::make_error_code(boost::system::errc::timed_out);
478template <
class Handler,
class Impl>
479template <
class String>
483 XRPL_ASSERT(
strand_.running_in_this_thread(),
"xrpl::BaseWSPeer::fail : strand in this thread");
486 if (!
ec_ && ec != boost::asio::error::operation_aborted)
489 JLOG(this->
j_.trace()) << what <<
": " << ec.message();
T back_inserter(T... args)
A generic endpoint for log messages.
boost::asio::strand< boost::asio::executor > strand_
endpoint_type remoteAddress_
BasePeer(Port const &port, Handler &handler, boost::asio::executor const &executor, endpoint_type remoteAddress, beast::Journal journal)
void onPingPong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload)
boost::beast::websocket::close_reason cr_
boost::beast::multi_buffer rb_
void onTimer(error_code ec)
boost::asio::ip::tcp::endpoint endpoint_type
boost::beast::multi_buffer wb_
void fail(error_code ec, String const &what)
void onWriteFin(error_code const &ec)
Port const & port() const override
boost::system::error_code error_code
boost::asio::basic_waitable_timer< clock_type > waitable_timer
void onPing(error_code const &ec)
boost::beast::websocket::ping_data payload_
void onWsHandshake(error_code const &ec)
BaseWSPeer(Port const &port, Handler &handler, boost::asio::executor const &executor, waitable_timer timer, endpoint_type remoteAddress, boost::beast::http::request< Body, Headers > &&request, beast::Journal journal)
std::function< void(boost::beast::websocket::frame_type, boost::beast::string_view)> controlCallback_
void send(std::shared_ptr< WSMsg > w) override
Send a WebSockets message.
void onClose(error_code const &ec)
http_request_type request_
std::chrono::system_clock clock_type
void onRead(error_code const &ec)
bool doClose_
The socket has been closed, or will close after the next write finishes.
boost::asio::ip::tcp::endpoint const & remoteEndpoint() const override
std::list< std::shared_ptr< WSMsg > > wq_
void onWrite(error_code const &ec)
void complete() override
Indicate that the response is complete.
http_request_type const & request() const override
void rngfill(void *const buffer, std::size_t const bytes, Generator &g)
std::string const & getFullVersionString()
Full server version string.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
CsprngEngine & cryptoPrng()
The default cryptographically secure PRNG.
constexpr Dest safeCast(Src s) noexcept
decltype(auto) getLowestLayer(T &t) noexcept
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Configuration information for a Server listening port.