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