xrpld
Loading...
Searching...
No Matches
BaseWSPeer.h
1#pragma once
2
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>
14
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>
20
21#include <algorithm>
22#include <chrono>
23#include <cstddef>
24#include <functional>
25#include <iterator>
26#include <list>
27#include <memory>
28#include <utility>
29#include <vector>
30
31namespace xrpl {
32
36template <class Handler, class Impl>
37class BaseWSPeer : public BasePeer<Handler, Impl>, public WSSession
38{
39protected:
41 using error_code = boost::system::error_code;
42 using endpoint_type = boost::asio::ip::tcp::endpoint;
43 using waitable_timer = boost::asio::basic_waitable_timer<clock_type>;
44 using BasePeer<Handler, Impl>::strand_;
45
46private:
47 friend class BasePeer<Handler, Impl>;
48
50 boost::beast::multi_buffer rb_;
51 boost::beast::multi_buffer wb_;
58 bool doClose_ = false;
59 boost::beast::websocket::close_reason cr_;
61 bool closeOnTimer_ = false;
62 bool pingActive_ = false;
63 boost::beast::websocket::ping_data payload_;
65 std::function<void(boost::beast::websocket::frame_type, boost::beast::string_view)>
67
68public:
69 template <class Body, class Headers>
71 Port const& port,
72 Handler& handler,
73 boost::asio::executor const& executor,
74 waitable_timer timer,
75 endpoint_type remoteAddress,
76 boost::beast::http::request<Body, Headers>&& request,
77 beast::Journal journal);
78
79 void
80 run() override;
81
82 //
83 // WSSession
84 //
85
86 [[nodiscard]] Port const&
87 port() const override
88 {
89 return this->port_;
90 }
91
92 [[nodiscard]] http_request_type const&
93 request() const override
94 {
95 return this->request_;
96 }
97
98 [[nodiscard]] boost::asio::ip::tcp::endpoint const&
99 remoteEndpoint() const override
100 {
101 return this->remoteAddress_;
102 }
103
104 void
106
107 void
108 close() override;
109
110 void
111 close(boost::beast::websocket::close_reason const& reason) override;
112
113 void
114 complete() override;
115
116protected:
117 Impl&
119 {
120 return *static_cast<Impl*>(this);
121 }
122
123 void
125
126 void
128
129 void
130 onWrite(error_code const& ec);
131
132 void
134
135 void
137
138 void
139 onRead(error_code const& ec);
140
141 void
142 onClose(error_code const& ec);
143
144 void
146
147 void
149
150 void
151 onPing(error_code const& ec);
152
153 void
154 onPingPong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload);
155
156 void
158
159 template <class String>
160 void
161 fail(error_code ec, String const& what);
162};
163
164//------------------------------------------------------------------------------
165
166template <class Handler, class Impl>
167template <class Body, class Headers>
169 Port const& port,
170 Handler& handler,
171 boost::asio::executor const& executor,
172 waitable_timer timer,
173 endpoint_type remoteAddress,
174 boost::beast::http::request<Body, Headers>&& request,
175 beast::Journal journal)
176 : BasePeer<Handler, Impl>(port, handler, executor, remoteAddress, journal)
177 , request_(std::move(request))
178 , timer_(std::move(timer))
179 , payload_("12345678") // ensures size is 8 bytes
180{
181}
182
183template <class Handler, class Impl>
184void
186{
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);
190 // Must manage the control callback memory outside of the `control_callback`
191 // function
192 controlCallback_ = [this](
193 boost::beast::websocket::frame_type kind,
194 boost::beast::string_view payload) { onPingPong(kind, payload); };
195 impl().ws_.control_callback(controlCallback_);
196 startTimer();
197 closeOnTimer_ = true;
198 impl().ws_.set_option(boost::beast::websocket::stream_base::decorator([](auto& res) {
199 res.set(boost::beast::http::field::server, build_info::getFullVersionString());
200 }));
201 impl().ws_.async_accept(
202 request_, bind_executor(strand_, [self = impl().shared_from_this()](error_code const& ec) {
203 self->onWsHandshake(ec);
204 }));
205}
206
207template <class Handler, class Impl>
208void
210{
211 if (!strand_.running_in_this_thread())
212 {
213 return post(
214 strand_, [self = impl().shared_from_this(), w = std::move(w)] { self->send(w); });
215 }
216 if (doClose_)
217 return;
218 if (wq_.size() > port().wsQueueLimit)
219 {
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;
223 wq_.erase(std::next(wq_.begin()), wq_.end());
224 close(cr_);
225 return;
226 }
227 wq_.emplace_back(std::move(w));
228 if (wq_.size() == 1)
229 onWrite({});
230}
231
232template <class Handler, class Impl>
233void
235{
236 close(boost::beast::websocket::close_reason{});
237}
238
239template <class Handler, class Impl>
240void
241BaseWSPeer<Handler, Impl>::close(boost::beast::websocket::close_reason const& reason)
242{
243 if (!strand_.running_in_this_thread())
244 return post(strand_, [self = impl().shared_from_this(), reason] { self->close(reason); });
245 if (doClose_)
246 return;
247 doClose_ = true;
248 if (wq_.empty())
249 {
250 impl().ws_.async_close(
251 reason,
252 bind_executor(
253 strand_, [self = impl().shared_from_this()](boost::beast::error_code const& ec) {
254 self->onClose(ec);
255 }));
256 }
257 else
258 {
259 cr_ = reason;
260 }
261}
262
263template <class Handler, class Impl>
264void
266{
267 if (!strand_.running_in_this_thread())
268 return post(strand_, [self = impl().shared_from_this()] { self->complete(); });
269 doRead();
270}
271
272template <class Handler, class Impl>
273void
275{
276 if (ec)
277 return fail(ec, "on_ws_handshake");
278 closeOnTimer_ = false;
279 doRead();
280}
281
282template <class Handler, class Impl>
283void
285{
286 if (!strand_.running_in_this_thread())
287 return post(strand_, [self = impl().shared_from_this()] { self->doWrite(); });
288 onWrite({});
289}
290
291template <class Handler, class Impl>
292void
294{
295 if (ec)
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))
300 return;
301 startTimer();
302 if (!result.first)
303 {
304 impl().ws_.async_write_some(
305 static_cast<bool>(result.first),
306 result.second,
307 bind_executor(
308 strand_, [self = impl().shared_from_this()](error_code const& ec, std::size_t) {
309 self->onWrite(ec);
310 }));
311 }
312 else
313 {
314 impl().ws_.async_write_some(
315 static_cast<bool>(result.first),
316 result.second,
317 bind_executor(
318 strand_, [self = impl().shared_from_this()](error_code const& ec, std::size_t) {
319 self->onWriteFin(ec);
320 }));
321 }
322}
323
324template <class Handler, class Impl>
325void
327{
328 if (ec)
329 return fail(ec, "write_fin");
330 wq_.pop_front();
331 if (doClose_)
332 {
333 impl().ws_.async_close(
334 cr_, bind_executor(strand_, [self = impl().shared_from_this()](error_code const& ec) {
335 self->onClose(ec);
336 }));
337 }
338 else if (!wq_.empty())
339 {
340 onWrite({});
341 }
342}
343
344template <class Handler, class Impl>
345void
347{
348 if (!strand_.running_in_this_thread())
349 return post(strand_, [self = impl().shared_from_this()] { self->doRead(); });
350 impl().ws_.async_read(
351 rb_,
352 bind_executor(
353 strand_, [self = impl().shared_from_this()](error_code const& ec, std::size_t) {
354 self->onRead(ec);
355 }));
356}
357
358template <class Handler, class Impl>
359void
361{
362 if (ec == boost::beast::websocket::error::closed)
363 return onClose({});
364 if (ec)
365 return fail(ec, "read");
366 auto const& data = rb_.data();
368 b.reserve(std::distance(data.begin(), data.end()));
370 this->handler_.onWSMessage(impl().shared_from_this(), b);
371 rb_.consume(rb_.size());
372}
373
374template <class Handler, class Impl>
375void
380
381template <class Handler, class Impl>
382void
384{
385 // Max seconds without completing a message
386 static constexpr std::chrono::seconds kTimeout{30};
387 static constexpr std::chrono::seconds kTimeoutLocal{3};
388
389 try
390 {
391 timer_.expires_after(remoteEndpoint().address().is_loopback() ? kTimeoutLocal : kTimeout);
392 }
393 catch (boost::system::system_error const& e)
394 {
395 return fail(e.code(), "start_timer");
396 }
397
398 timer_.async_wait(bind_executor(
399 strand_, [self = impl().shared_from_this()](error_code const& ec) { self->onTimer(ec); }));
400}
401
402// Convenience for discarding the error code
403template <class Handler, class Impl>
404void
406{
407 try
408 {
409 timer_.cancel();
410 }
411 catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch)
412 {
413 // ignored
414 }
415}
416
417template <class Handler, class Impl>
418void
420{
421 if (ec == boost::asio::error::operation_aborted)
422 return;
423 pingActive_ = false;
424 if (!ec)
425 return;
426 fail(ec, "on_ping");
427}
428
429template <class Handler, class Impl>
430void
432 boost::beast::websocket::frame_type kind,
433 boost::beast::string_view payload)
434{
435 if (kind == boost::beast::websocket::frame_type::pong)
436 {
437 boost::beast::string_view const p(payload_.begin());
438 if (payload == p)
439 {
440 closeOnTimer_ = false;
441 JLOG(this->j_.trace()) << "got matching pong";
442 }
443 else
444 {
445 JLOG(this->j_.trace()) << "got pong";
446 }
447 }
448}
449
450template <class Handler, class Impl>
451void
453{
454 if (ec == boost::asio::error::operation_aborted)
455 return;
456 if (!ec)
457 {
458 if (!closeOnTimer_ || !pingActive_)
459 {
460 startTimer();
461 closeOnTimer_ = true;
462 pingActive_ = true;
463 // cryptographic is probably overkill..
464 beast::rngfill(payload_.begin(), payload_.size(), cryptoPrng());
465 impl().ws_.async_ping(
466 payload_,
467 bind_executor(strand_, [self = impl().shared_from_this()](error_code const& ec) {
468 self->onPing(ec);
469 }));
470 JLOG(this->j_.trace()) << "sent ping";
471 return;
472 }
473 ec = boost::system::errc::make_error_code(boost::system::errc::timed_out);
474 }
475 fail(ec, "timer");
476}
477
478template <class Handler, class Impl>
479template <class String>
480void
482{
483 XRPL_ASSERT(strand_.running_in_this_thread(), "xrpl::BaseWSPeer::fail : strand in this thread");
484
485 cancelTimer();
486 if (!ec_ && ec != boost::asio::error::operation_aborted)
487 {
488 ec_ = ec;
489 JLOG(this->j_.trace()) << what << ": " << ec.message();
490 xrpl::getLowestLayer(impl().ws_).socket().close(ec);
491 }
492}
493
494} // namespace xrpl
T back_inserter(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
boost::asio::strand< boost::asio::executor > strand_
Definition BasePeer.h:35
Port const & port_
Definition BasePeer.h:28
endpoint_type remoteAddress_
Definition BasePeer.h:30
BasePeer(Port const &port, Handler &handler, boost::asio::executor const &executor, endpoint_type remoteAddress, beast::Journal journal)
Definition BasePeer.h:60
Handler & handler_
Definition BasePeer.h:29
beast::Journal const j_
Definition BasePeer.h:32
void onPingPong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload)
Definition BaseWSPeer.h:431
boost::beast::websocket::close_reason cr_
Definition BaseWSPeer.h:59
boost::beast::multi_buffer rb_
Definition BaseWSPeer.h:50
void onTimer(error_code ec)
Definition BaseWSPeer.h:452
error_code ec_
Definition BaseWSPeer.h:64
boost::asio::ip::tcp::endpoint endpoint_type
Definition BaseWSPeer.h:42
boost::beast::multi_buffer wb_
Definition BaseWSPeer.h:51
void fail(error_code ec, String const &what)
Definition BaseWSPeer.h:481
void onWriteFin(error_code const &ec)
Definition BaseWSPeer.h:326
boost::system::error_code error_code
Definition BaseWSPeer.h:41
boost::asio::basic_waitable_timer< clock_type > waitable_timer
Definition BaseWSPeer.h:43
void onPing(error_code const &ec)
Definition BaseWSPeer.h:419
boost::beast::websocket::ping_data payload_
Definition BaseWSPeer.h:63
void run() override
Definition BaseWSPeer.h:185
void onWsHandshake(error_code const &ec)
Definition BaseWSPeer.h:274
void close() override
Definition BaseWSPeer.h:234
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)
Definition BaseWSPeer.h:168
std::function< void(boost::beast::websocket::frame_type, boost::beast::string_view)> controlCallback_
Definition BaseWSPeer.h:66
void send(std::shared_ptr< WSMsg > w) override
Send a WebSockets message.
Definition BaseWSPeer.h:209
void onClose(error_code const &ec)
Definition BaseWSPeer.h:376
http_request_type request_
Definition BaseWSPeer.h:49
std::chrono::system_clock clock_type
Definition BaseWSPeer.h:40
waitable_timer timer_
Definition BaseWSPeer.h:60
void onRead(error_code const &ec)
Definition BaseWSPeer.h:360
bool doClose_
The socket has been closed, or will close after the next write finishes.
Definition BaseWSPeer.h:58
boost::asio::ip::tcp::endpoint const & remoteEndpoint() const override
Definition BaseWSPeer.h:99
std::list< std::shared_ptr< WSMsg > > wq_
Definition BaseWSPeer.h:52
void onWrite(error_code const &ec)
Definition BaseWSPeer.h:293
void complete() override
Indicate that the response is complete.
Definition BaseWSPeer.h:265
http_request_type const & request() const override
Definition BaseWSPeer.h:93
T copy(T... args)
T distance(T... args)
void rngfill(void *const buffer, std::size_t const bytes, Generator &g)
Definition rngfill.h:11
STL namespace.
std::string const & getFullVersionString()
Full server version string.
Definition BuildInfo.cpp:82
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
CsprngEngine & cryptoPrng()
The default cryptographically secure PRNG.
constexpr Dest safeCast(Src s) noexcept
Definition safe_cast.h:21
decltype(auto) getLowestLayer(T &t) noexcept
Definition LowestLayer.h:9
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:12
T next(T... args)
T reserve(T... args)
Configuration information for a Server listening port.
Definition Port.h:29
WSSession()=default