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/beast/core/multi_buffer.hpp>
33#include <boost/beast/http/message.hpp>
34#include <boost/beast/websocket.hpp>
35#include <boost/logic/tribool.hpp>
36
37#include <functional>
38#include <list>
39
40namespace ripple {
41
43template <class Handler, class Impl>
44class BaseWSPeer : public BasePeer<Handler, Impl>, public WSSession
45{
46protected:
48 using error_code = boost::system::error_code;
49 using endpoint_type = boost::asio::ip::tcp::endpoint;
50 using waitable_timer = boost::asio::basic_waitable_timer<clock_type>;
51 using BasePeer<Handler, Impl>::strand_;
52
53private:
54 friend class BasePeer<Handler, Impl>;
55
57 boost::beast::multi_buffer rb_;
58 boost::beast::multi_buffer wb_;
63 bool do_close_ = false;
64 boost::beast::websocket::close_reason cr_;
66 bool close_on_timer_ = false;
67 bool ping_active_ = false;
68 boost::beast::websocket::ping_data payload_;
71 void(boost::beast::websocket::frame_type, boost::beast::string_view)>
73
74public:
75 template <class Body, class Headers>
77 Port const& port,
78 Handler& handler,
79 boost::asio::executor const& executor,
80 waitable_timer timer,
81 endpoint_type remote_address,
82 boost::beast::http::request<Body, Headers>&& request,
83 beast::Journal journal);
84
85 void
86 run() override;
87
88 //
89 // WSSession
90 //
91
92 Port const&
93 port() const override
94 {
95 return this->port_;
96 }
97
99 request() const override
100 {
101 return this->request_;
102 }
103
104 boost::asio::ip::tcp::endpoint const&
105 remote_endpoint() const override
106 {
107 return this->remote_address_;
108 }
109
110 void
112
113 void
114 close() override;
115
116 void
117 close(boost::beast::websocket::close_reason const& reason) override;
118
119 void
120 complete() override;
121
122protected:
123 Impl&
125 {
126 return *static_cast<Impl*>(this);
127 }
128
129 void
131
132 void
134
135 void
137
138 void
140
141 void
143
144 void
145 on_read(error_code const& ec);
146
147 void
149
150 void
152
153 void
155
156 void
157 on_ping(error_code const& ec);
158
159 void
161 boost::beast::websocket::frame_type kind,
162 boost::beast::string_view payload);
163
164 void
166
167 template <class String>
168 void
169 fail(error_code ec, String const& what);
170};
171
172//------------------------------------------------------------------------------
173
174template <class Handler, class Impl>
175template <class Body, class Headers>
177 Port const& port,
178 Handler& handler,
179 boost::asio::executor const& executor,
180 waitable_timer timer,
181 endpoint_type remote_address,
182 boost::beast::http::request<Body, Headers>&& request,
183 beast::Journal journal)
184 : BasePeer<Handler, Impl>(port, handler, executor, remote_address, journal)
185 , request_(std::move(request))
186 , timer_(std::move(timer))
187 , payload_("12345678") // ensures size is 8 bytes
188{
189}
190
191template <class Handler, class Impl>
192void
194{
195 if (!strand_.running_in_this_thread())
196 return post(
197 strand_, std::bind(&BaseWSPeer::run, impl().shared_from_this()));
198 impl().ws_.set_option(port().pmd_options);
199 // Must manage the control callback memory outside of the `control_callback`
200 // function
201 control_callback_ = std::bind(
203 this,
204 std::placeholders::_1,
205 std::placeholders::_2);
206 impl().ws_.control_callback(control_callback_);
207 start_timer();
208 close_on_timer_ = true;
209 impl().ws_.set_option(
210 boost::beast::websocket::stream_base::decorator([](auto& res) {
211 res.set(
212 boost::beast::http::field::server,
214 }));
215 impl().ws_.async_accept(
216 request_,
217 bind_executor(
218 strand_,
219 std::bind(
221 impl().shared_from_this(),
222 std::placeholders::_1)));
223}
224
225template <class Handler, class Impl>
226void
228{
229 if (!strand_.running_in_this_thread())
230 return post(
231 strand_,
232 std::bind(
233 &BaseWSPeer::send, impl().shared_from_this(), std::move(w)));
234 if (do_close_)
235 return;
236 if (wq_.size() > port().ws_queue_limit)
237 {
238 cr_.code = safe_cast<decltype(cr_.code)>(
239 boost::beast::websocket::close_code::policy_error);
240 cr_.reason = "Policy error: client is too slow.";
241 JLOG(this->j_.info()) << cr_.reason;
242 wq_.erase(std::next(wq_.begin()), wq_.end());
243 close(cr_);
244 return;
245 }
246 wq_.emplace_back(std::move(w));
247 if (wq_.size() == 1)
248 on_write({});
249}
250
251template <class Handler, class Impl>
252void
254{
255 close(boost::beast::websocket::close_reason{});
256}
257
258template <class Handler, class Impl>
259void
261 boost::beast::websocket::close_reason const& reason)
262{
263 if (!strand_.running_in_this_thread())
264 return post(strand_, [self = impl().shared_from_this(), reason] {
265 self->close(reason);
266 });
267 if (do_close_)
268 return;
269 do_close_ = true;
270 if (wq_.empty())
271 {
272 impl().ws_.async_close(
273 reason,
274 bind_executor(
275 strand_,
276 [self = impl().shared_from_this()](
277 boost::beast::error_code const& ec) {
278 self->on_close(ec);
279 }));
280 }
281 else
282 {
283 cr_ = reason;
284 }
285}
286
287template <class Handler, class Impl>
288void
290{
291 if (!strand_.running_in_this_thread())
292 return post(
293 strand_,
294 std::bind(&BaseWSPeer::complete, impl().shared_from_this()));
295 do_read();
296}
297
298template <class Handler, class Impl>
299void
301{
302 if (ec)
303 return fail(ec, "on_ws_handshake");
304 close_on_timer_ = false;
305 do_read();
306}
307
308template <class Handler, class Impl>
309void
311{
312 if (!strand_.running_in_this_thread())
313 return post(
314 strand_,
315 std::bind(&BaseWSPeer::do_write, impl().shared_from_this()));
316 on_write({});
317}
318
319template <class Handler, class Impl>
320void
322{
323 if (ec)
324 return fail(ec, "write");
325 auto& w = *wq_.front();
326 auto const result = w.prepare(
327 65536, std::bind(&BaseWSPeer::do_write, impl().shared_from_this()));
328 if (boost::indeterminate(result.first))
329 return;
330 start_timer();
331 if (!result.first)
332 impl().ws_.async_write_some(
333 static_cast<bool>(result.first),
334 result.second,
335 bind_executor(
336 strand_,
337 std::bind(
339 impl().shared_from_this(),
340 std::placeholders::_1)));
341 else
342 impl().ws_.async_write_some(
343 static_cast<bool>(result.first),
344 result.second,
345 bind_executor(
346 strand_,
347 std::bind(
349 impl().shared_from_this(),
350 std::placeholders::_1)));
351}
352
353template <class Handler, class Impl>
354void
356{
357 if (ec)
358 return fail(ec, "write_fin");
359 wq_.pop_front();
360 if (do_close_)
361 {
362 impl().ws_.async_close(
363 cr_,
364 bind_executor(
365 strand_,
366 std::bind(
368 impl().shared_from_this(),
369 std::placeholders::_1)));
370 }
371 else if (!wq_.empty())
372 on_write({});
373}
374
375template <class Handler, class Impl>
376void
378{
379 if (!strand_.running_in_this_thread())
380 return post(
381 strand_,
382 std::bind(&BaseWSPeer::do_read, impl().shared_from_this()));
383 impl().ws_.async_read(
384 rb_,
385 bind_executor(
386 strand_,
387 std::bind(
389 impl().shared_from_this(),
390 std::placeholders::_1)));
391}
392
393template <class Handler, class Impl>
394void
396{
397 if (ec == boost::beast::websocket::error::closed)
398 return on_close({});
399 if (ec)
400 return fail(ec, "read");
401 auto const& data = rb_.data();
403 b.reserve(std::distance(data.begin(), data.end()));
404 std::copy(data.begin(), data.end(), std::back_inserter(b));
405 this->handler_.onWSMessage(impl().shared_from_this(), b);
406 rb_.consume(rb_.size());
407}
408
409template <class Handler, class Impl>
410void
412{
413 cancel_timer();
414}
415
416template <class Handler, class Impl>
417void
419{
420 // Max seconds without completing a message
421 static constexpr std::chrono::seconds timeout{30};
422 static constexpr std::chrono::seconds timeoutLocal{3};
423 error_code ec;
424 timer_.expires_from_now(
425 remote_endpoint().address().is_loopback() ? timeoutLocal : timeout, ec);
426 if (ec)
427 return fail(ec, "start_timer");
428 timer_.async_wait(bind_executor(
429 strand_,
430 std::bind(
432 impl().shared_from_this(),
433 std::placeholders::_1)));
434}
435
436// Convenience for discarding the error code
437template <class Handler, class Impl>
438void
440{
441 error_code ec;
442 timer_.cancel(ec);
443}
444
445template <class Handler, class Impl>
446void
448{
449 if (ec == boost::asio::error::operation_aborted)
450 return;
451 ping_active_ = false;
452 if (!ec)
453 return;
454 fail(ec, "on_ping");
455}
456
457template <class Handler, class Impl>
458void
460 boost::beast::websocket::frame_type kind,
461 boost::beast::string_view payload)
462{
463 if (kind == boost::beast::websocket::frame_type::pong)
464 {
465 boost::beast::string_view p(payload_.begin());
466 if (payload == p)
467 {
468 close_on_timer_ = false;
469 JLOG(this->j_.trace()) << "got matching pong";
470 }
471 else
472 {
473 JLOG(this->j_.trace()) << "got pong";
474 }
475 }
476}
477
478template <class Handler, class Impl>
479void
481{
482 if (ec == boost::asio::error::operation_aborted)
483 return;
484 if (!ec)
485 {
486 if (!close_on_timer_ || !ping_active_)
487 {
488 start_timer();
489 close_on_timer_ = true;
490 ping_active_ = true;
491 // cryptographic is probably overkill..
492 beast::rngfill(payload_.begin(), payload_.size(), crypto_prng());
493 impl().ws_.async_ping(
494 payload_,
495 bind_executor(
496 strand_,
497 std::bind(
499 impl().shared_from_this(),
500 std::placeholders::_1)));
501 JLOG(this->j_.trace()) << "sent ping";
502 return;
503 }
504 ec = boost::system::errc::make_error_code(
505 boost::system::errc::timed_out);
506 }
507 fail(ec, "timer");
508}
509
510template <class Handler, class Impl>
511template <class String>
512void
514{
515 XRPL_ASSERT(
516 strand_.running_in_this_thread(),
517 "ripple::BaseWSPeer::fail : strand in this thread");
518
519 cancel_timer();
520 if (!ec_ && ec != boost::asio::error::operation_aborted)
521 {
522 ec_ = ec;
523 JLOG(this->j_.trace()) << what << ": " << ec.message();
524 ripple::get_lowest_layer(impl().ws_).socket().close(ec);
525 }
526}
527
528} // namespace ripple
529
530#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:45
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:176
boost::asio::ip::tcp::endpoint endpoint_type
Definition: BaseWSPeer.h:49
boost::system::error_code error_code
Definition: BaseWSPeer.h:48
http_request_type const & request() const override
Definition: BaseWSPeer.h:99
std::function< void(boost::beast::websocket::frame_type, boost::beast::string_view)> control_callback_
Definition: BaseWSPeer.h:72
void fail(error_code ec, String const &what)
Definition: BaseWSPeer.h:513
bool do_close_
The socket has been closed, or will close after the next write finishes.
Definition: BaseWSPeer.h:63
void close(boost::beast::websocket::close_reason const &reason) override
Definition: BaseWSPeer.h:260
http_request_type request_
Definition: BaseWSPeer.h:56
void on_read(error_code const &ec)
Definition: BaseWSPeer.h:395
void on_ws_handshake(error_code const &ec)
Definition: BaseWSPeer.h:300
boost::beast::multi_buffer wb_
Definition: BaseWSPeer.h:58
void on_ping_pong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload)
Definition: BaseWSPeer.h:459
boost::asio::basic_waitable_timer< clock_type > waitable_timer
Definition: BaseWSPeer.h:50
void on_close(error_code const &ec)
Definition: BaseWSPeer.h:411
void on_write_fin(error_code const &ec)
Definition: BaseWSPeer.h:355
boost::asio::ip::tcp::endpoint const & remote_endpoint() const override
Definition: BaseWSPeer.h:105
Port const & port() const override
Definition: BaseWSPeer.h:93
void complete() override
Indicate that the response is complete.
Definition: BaseWSPeer.h:289
boost::beast::websocket::close_reason cr_
Definition: BaseWSPeer.h:64
void send(std::shared_ptr< WSMsg > w) override
Send a WebSockets message.
Definition: BaseWSPeer.h:227
void close() override
Definition: BaseWSPeer.h:253
std::list< std::shared_ptr< WSMsg > > wq_
Definition: BaseWSPeer.h:59
void run() override
Definition: BaseWSPeer.h:193
error_code ec_
Definition: BaseWSPeer.h:69
void on_timer(error_code ec)
Definition: BaseWSPeer.h:480
void on_ping(error_code const &ec)
Definition: BaseWSPeer.h:447
boost::beast::multi_buffer rb_
Definition: BaseWSPeer.h:57
waitable_timer timer_
Definition: BaseWSPeer.h:65
void on_write(error_code const &ec)
Definition: BaseWSPeer.h:321
boost::beast::websocket::ping_data payload_
Definition: BaseWSPeer.h:68
T copy(T... args)
T distance(T... args)
void rngfill(void *buffer, std::size_t 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:42
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