rippled
Loading...
Searching...
No Matches
BaseWSPeer.h
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright(c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#ifndef RIPPLE_SERVER_BASEWSPEER_H_INCLUDED
21#define RIPPLE_SERVER_BASEWSPEER_H_INCLUDED
22
23#include <xrpl/basics/safe_cast.h>
24#include <xrpl/beast/utility/instrumentation.h>
25#include <xrpl/beast/utility/rngfill.h>
26#include <xrpl/crypto/csprng.h>
27#include <xrpl/protocol/BuildInfo.h>
28#include <xrpl/server/WSSession.h>
29#include <xrpl/server/detail/BasePeer.h>
30#include <xrpl/server/detail/LowestLayer.h>
31
32#include <boost/asio/error.hpp>
33#include <boost/beast/core/multi_buffer.hpp>
34#include <boost/beast/http/message.hpp>
35#include <boost/beast/websocket.hpp>
36#include <boost/logic/tribool.hpp>
37
38#include <functional>
39#include <list>
40
41namespace ripple {
42
44template <class Handler, class Impl>
45class BaseWSPeer : public BasePeer<Handler, Impl>, public WSSession
46{
47protected:
49 using error_code = boost::system::error_code;
50 using endpoint_type = boost::asio::ip::tcp::endpoint;
51 using waitable_timer = boost::asio::basic_waitable_timer<clock_type>;
52 using BasePeer<Handler, Impl>::strand_;
53
54private:
55 friend class BasePeer<Handler, Impl>;
56
58 boost::beast::multi_buffer rb_;
59 boost::beast::multi_buffer wb_;
64 bool do_close_ = false;
65 boost::beast::websocket::close_reason cr_;
67 bool close_on_timer_ = false;
68 bool ping_active_ = false;
69 boost::beast::websocket::ping_data payload_;
72 void(boost::beast::websocket::frame_type, boost::beast::string_view)>
74
75public:
76 template <class Body, class Headers>
78 Port const& port,
79 Handler& handler,
80 boost::asio::executor const& executor,
81 waitable_timer timer,
82 endpoint_type remote_address,
83 boost::beast::http::request<Body, Headers>&& request,
84 beast::Journal journal);
85
86 void
87 run() override;
88
89 //
90 // WSSession
91 //
92
93 Port const&
94 port() const override
95 {
96 return this->port_;
97 }
98
100 request() const override
101 {
102 return this->request_;
103 }
104
105 boost::asio::ip::tcp::endpoint const&
106 remote_endpoint() const override
107 {
108 return this->remote_address_;
109 }
110
111 void
113
114 void
115 close() override;
116
117 void
118 close(boost::beast::websocket::close_reason const& reason) override;
119
120 void
121 complete() override;
122
123protected:
124 Impl&
126 {
127 return *static_cast<Impl*>(this);
128 }
129
130 void
132
133 void
135
136 void
138
139 void
141
142 void
144
145 void
146 on_read(error_code const& ec);
147
148 void
150
151 void
153
154 void
156
157 void
158 on_ping(error_code const& ec);
159
160 void
162 boost::beast::websocket::frame_type kind,
163 boost::beast::string_view payload);
164
165 void
167
168 template <class String>
169 void
170 fail(error_code ec, String const& what);
171};
172
173//------------------------------------------------------------------------------
174
175template <class Handler, class Impl>
176template <class Body, class Headers>
178 Port const& port,
179 Handler& handler,
180 boost::asio::executor const& executor,
181 waitable_timer timer,
182 endpoint_type remote_address,
183 boost::beast::http::request<Body, Headers>&& request,
184 beast::Journal journal)
185 : BasePeer<Handler, Impl>(port, handler, executor, remote_address, journal)
186 , request_(std::move(request))
187 , timer_(std::move(timer))
188 , payload_("12345678") // ensures size is 8 bytes
189{
190}
191
192template <class Handler, class Impl>
193void
195{
196 if (!strand_.running_in_this_thread())
197 return post(
198 strand_, std::bind(&BaseWSPeer::run, impl().shared_from_this()));
199 impl().ws_.set_option(port().pmd_options);
200 // Must manage the control callback memory outside of the `control_callback`
201 // function
202 control_callback_ = std::bind(
204 this,
205 std::placeholders::_1,
206 std::placeholders::_2);
207 impl().ws_.control_callback(control_callback_);
208 start_timer();
209 close_on_timer_ = true;
210 impl().ws_.set_option(
211 boost::beast::websocket::stream_base::decorator([](auto& res) {
212 res.set(
213 boost::beast::http::field::server,
215 }));
216 impl().ws_.async_accept(
217 request_,
218 bind_executor(
219 strand_,
220 std::bind(
222 impl().shared_from_this(),
223 std::placeholders::_1)));
224}
225
226template <class Handler, class Impl>
227void
229{
230 if (!strand_.running_in_this_thread())
231 return post(
232 strand_,
233 std::bind(
234 &BaseWSPeer::send, impl().shared_from_this(), std::move(w)));
235 if (do_close_)
236 return;
237 if (wq_.size() > port().ws_queue_limit)
238 {
239 cr_.code = safe_cast<decltype(cr_.code)>(
240 boost::beast::websocket::close_code::policy_error);
241 cr_.reason = "Policy error: client is too slow.";
242 JLOG(this->j_.info()) << cr_.reason;
243 wq_.erase(std::next(wq_.begin()), wq_.end());
244 close(cr_);
245 return;
246 }
247 wq_.emplace_back(std::move(w));
248 if (wq_.size() == 1)
249 on_write({});
250}
251
252template <class Handler, class Impl>
253void
255{
256 close(boost::beast::websocket::close_reason{});
257}
258
259template <class Handler, class Impl>
260void
262 boost::beast::websocket::close_reason const& reason)
263{
264 if (!strand_.running_in_this_thread())
265 return post(strand_, [self = impl().shared_from_this(), reason] {
266 self->close(reason);
267 });
268 if (do_close_)
269 return;
270 do_close_ = true;
271 if (wq_.empty())
272 {
273 impl().ws_.async_close(
274 reason,
275 bind_executor(
276 strand_,
277 [self = impl().shared_from_this()](
278 boost::beast::error_code const& ec) {
279 self->on_close(ec);
280 }));
281 }
282 else
283 {
284 cr_ = reason;
285 }
286}
287
288template <class Handler, class Impl>
289void
291{
292 if (!strand_.running_in_this_thread())
293 return post(
294 strand_,
295 std::bind(&BaseWSPeer::complete, impl().shared_from_this()));
296 do_read();
297}
298
299template <class Handler, class Impl>
300void
302{
303 if (ec)
304 return fail(ec, "on_ws_handshake");
305 close_on_timer_ = false;
306 do_read();
307}
308
309template <class Handler, class Impl>
310void
312{
313 if (!strand_.running_in_this_thread())
314 return post(
315 strand_,
316 std::bind(&BaseWSPeer::do_write, impl().shared_from_this()));
317 on_write({});
318}
319
320template <class Handler, class Impl>
321void
323{
324 if (ec)
325 return fail(ec, "write");
326 auto& w = *wq_.front();
327 auto const result = w.prepare(
328 65536, std::bind(&BaseWSPeer::do_write, impl().shared_from_this()));
329 if (boost::indeterminate(result.first))
330 return;
331 start_timer();
332 if (!result.first)
333 impl().ws_.async_write_some(
334 static_cast<bool>(result.first),
335 result.second,
336 bind_executor(
337 strand_,
338 std::bind(
340 impl().shared_from_this(),
341 std::placeholders::_1)));
342 else
343 impl().ws_.async_write_some(
344 static_cast<bool>(result.first),
345 result.second,
346 bind_executor(
347 strand_,
348 std::bind(
350 impl().shared_from_this(),
351 std::placeholders::_1)));
352}
353
354template <class Handler, class Impl>
355void
357{
358 if (ec)
359 return fail(ec, "write_fin");
360 wq_.pop_front();
361 if (do_close_)
362 {
363 impl().ws_.async_close(
364 cr_,
365 bind_executor(
366 strand_,
367 std::bind(
369 impl().shared_from_this(),
370 std::placeholders::_1)));
371 }
372 else if (!wq_.empty())
373 on_write({});
374}
375
376template <class Handler, class Impl>
377void
379{
380 if (!strand_.running_in_this_thread())
381 return post(
382 strand_,
383 std::bind(&BaseWSPeer::do_read, impl().shared_from_this()));
384 impl().ws_.async_read(
385 rb_,
386 bind_executor(
387 strand_,
388 std::bind(
390 impl().shared_from_this(),
391 std::placeholders::_1)));
392}
393
394template <class Handler, class Impl>
395void
397{
398 if (ec == boost::beast::websocket::error::closed)
399 return on_close({});
400 if (ec)
401 return fail(ec, "read");
402 auto const& data = rb_.data();
404 b.reserve(std::distance(data.begin(), data.end()));
405 std::copy(data.begin(), data.end(), std::back_inserter(b));
406 this->handler_.onWSMessage(impl().shared_from_this(), b);
407 rb_.consume(rb_.size());
408}
409
410template <class Handler, class Impl>
411void
413{
414 cancel_timer();
415}
416
417template <class Handler, class Impl>
418void
420{
421 // Max seconds without completing a message
422 static constexpr std::chrono::seconds timeout{30};
423 static constexpr std::chrono::seconds timeoutLocal{3};
424
425 try
426 {
427 timer_.expires_after(
428 remote_endpoint().address().is_loopback() ? timeoutLocal : timeout);
429 }
430 catch (boost::system::system_error const& e)
431 {
432 return fail(e.code(), "start_timer");
433 }
434
435 timer_.async_wait(bind_executor(
436 strand_,
437 std::bind(
439 impl().shared_from_this(),
440 std::placeholders::_1)));
441}
442
443// Convenience for discarding the error code
444template <class Handler, class Impl>
445void
447{
448 try
449 {
450 timer_.cancel();
451 }
452 catch (boost::system::system_error const&)
453 {
454 // ignored
455 }
456}
457
458template <class Handler, class Impl>
459void
461{
462 if (ec == boost::asio::error::operation_aborted)
463 return;
464 ping_active_ = false;
465 if (!ec)
466 return;
467 fail(ec, "on_ping");
468}
469
470template <class Handler, class Impl>
471void
473 boost::beast::websocket::frame_type kind,
474 boost::beast::string_view payload)
475{
476 if (kind == boost::beast::websocket::frame_type::pong)
477 {
478 boost::beast::string_view p(payload_.begin());
479 if (payload == p)
480 {
481 close_on_timer_ = false;
482 JLOG(this->j_.trace()) << "got matching pong";
483 }
484 else
485 {
486 JLOG(this->j_.trace()) << "got pong";
487 }
488 }
489}
490
491template <class Handler, class Impl>
492void
494{
495 if (ec == boost::asio::error::operation_aborted)
496 return;
497 if (!ec)
498 {
499 if (!close_on_timer_ || !ping_active_)
500 {
501 start_timer();
502 close_on_timer_ = true;
503 ping_active_ = true;
504 // cryptographic is probably overkill..
505 beast::rngfill(payload_.begin(), payload_.size(), crypto_prng());
506 impl().ws_.async_ping(
507 payload_,
508 bind_executor(
509 strand_,
510 std::bind(
512 impl().shared_from_this(),
513 std::placeholders::_1)));
514 JLOG(this->j_.trace()) << "sent ping";
515 return;
516 }
517 ec = boost::system::errc::make_error_code(
518 boost::system::errc::timed_out);
519 }
520 fail(ec, "timer");
521}
522
523template <class Handler, class Impl>
524template <class String>
525void
527{
528 XRPL_ASSERT(
529 strand_.running_in_this_thread(),
530 "ripple::BaseWSPeer::fail : strand in this thread");
531
532 cancel_timer();
533 if (!ec_ && ec != boost::asio::error::operation_aborted)
534 {
535 ec_ = ec;
536 JLOG(this->j_.trace()) << what << ": " << ec.message();
537 ripple::get_lowest_layer(impl().ws_).socket().close(ec);
538 }
539}
540
541} // namespace ripple
542
543#endif
T back_inserter(T... args)
T bind(T... args)
A generic endpoint for log messages.
Definition Journal.h:60
Stream info() const
Definition Journal.h:334
Stream trace() const
Severity stream access functions.
Definition Journal.h:322
Port const & port_
Definition BasePeer.h:47
endpoint_type remote_address_
Definition BasePeer.h:49
boost::asio::strand< boost::asio::executor > strand_
Definition BasePeer.h:54
Represents an active WebSocket connection.
Definition BaseWSPeer.h:46
BaseWSPeer(Port const &port, Handler &handler, boost::asio::executor const &executor, waitable_timer timer, endpoint_type remote_address, boost::beast::http::request< Body, Headers > &&request, beast::Journal journal)
Definition BaseWSPeer.h:177
boost::asio::ip::tcp::endpoint endpoint_type
Definition BaseWSPeer.h:50
boost::system::error_code error_code
Definition BaseWSPeer.h:49
http_request_type const & request() const override
Definition BaseWSPeer.h:100
std::function< void(boost::beast::websocket::frame_type, boost::beast::string_view)> control_callback_
Definition BaseWSPeer.h:73
void fail(error_code ec, String const &what)
Definition BaseWSPeer.h:526
bool do_close_
The socket has been closed, or will close after the next write finishes.
Definition BaseWSPeer.h:64
void close(boost::beast::websocket::close_reason const &reason) override
Definition BaseWSPeer.h:261
http_request_type request_
Definition BaseWSPeer.h:57
void on_read(error_code const &ec)
Definition BaseWSPeer.h:396
void on_ws_handshake(error_code const &ec)
Definition BaseWSPeer.h:301
boost::beast::multi_buffer wb_
Definition BaseWSPeer.h:59
void on_ping_pong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload)
Definition BaseWSPeer.h:472
boost::asio::basic_waitable_timer< clock_type > waitable_timer
Definition BaseWSPeer.h:51
void on_close(error_code const &ec)
Definition BaseWSPeer.h:412
void on_write_fin(error_code const &ec)
Definition BaseWSPeer.h:356
boost::asio::ip::tcp::endpoint const & remote_endpoint() const override
Definition BaseWSPeer.h:106
Port const & port() const override
Definition BaseWSPeer.h:94
void complete() override
Indicate that the response is complete.
Definition BaseWSPeer.h:290
boost::beast::websocket::close_reason cr_
Definition BaseWSPeer.h:65
void send(std::shared_ptr< WSMsg > w) override
Send a WebSockets message.
Definition BaseWSPeer.h:228
void close() override
Definition BaseWSPeer.h:254
std::list< std::shared_ptr< WSMsg > > wq_
Definition BaseWSPeer.h:60
void run() override
Definition BaseWSPeer.h:194
void on_timer(error_code ec)
Definition BaseWSPeer.h:493
void on_ping(error_code const &ec)
Definition BaseWSPeer.h:460
boost::beast::multi_buffer rb_
Definition BaseWSPeer.h:58
waitable_timer timer_
Definition BaseWSPeer.h:66
void on_write(error_code const &ec)
Definition BaseWSPeer.h:322
boost::beast::websocket::ping_data payload_
Definition BaseWSPeer.h:69
T copy(T... args)
T distance(T... args)
void rngfill(void *const buffer, std::size_t const bytes, Generator &g)
Definition rngfill.h:34
std::string const & getFullVersionString()
Full server version string.
Definition BuildInfo.cpp:81
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
constexpr std::enable_if_t< std::is_integral_v< Dest > &&std::is_integral_v< Src >, Dest > safe_cast(Src s) noexcept
Definition safe_cast.h:41
csprng_engine & crypto_prng()
The default cryptographically secure PRNG.
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:33
decltype(auto) get_lowest_layer(T &t) noexcept
Definition LowestLayer.h:35
STL namespace.
T next(T... args)
T reserve(T... args)
Configuration information for a Server listening port.
Definition Port.h:50