rippled
WSClient.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2016 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 #include <test/jtx/WSClient.h>
21 #include <test/jtx.h>
22 #include <ripple/json/json_reader.h>
23 #include <ripple/json/to_string.h>
24 #include <ripple/protocol/jss.h>
25 #include <ripple/server/Port.h>
26 #include <boost/beast/core/multi_buffer.hpp>
27 #include <boost/beast/websocket.hpp>
28 
29 #include <condition_variable>
30 #include <string>
31 #include <unordered_map>
32 
33 #include <iostream>
34 
35 #include <ripple/beast/unit_test.h>
36 
37 namespace ripple {
38 namespace test {
39 
40 class WSClientImpl : public WSClient
41 {
42  using error_code = boost::system::error_code;
43 
44  struct msg
45  {
47 
48  explicit
50  : jv(jv_)
51  {
52  }
53  };
54 
55  static
56  boost::asio::ip::tcp::endpoint
57  getEndpoint(BasicConfig const& cfg, bool v2)
58  {
59  auto& log = std::cerr;
60  ParsedPort common;
61  parse_Port (common, cfg["server"], log);
62  auto const ps = v2 ? "ws2" : "ws";
63  for (auto const& name : cfg.section("server").values())
64  {
65  if (! cfg.exists(name))
66  continue;
67  ParsedPort pp;
68  parse_Port(pp, cfg[name], log);
69  if(pp.protocol.count(ps) == 0)
70  continue;
71  using namespace boost::asio::ip;
72  if(pp.ip && pp.ip->is_unspecified())
73  *pp.ip = pp.ip->is_v6() ? address{address_v6::loopback()} : address{address_v4::loopback()};
74  return { *pp.ip, *pp.port };
75  }
76  Throw<std::runtime_error>("Missing WebSocket port");
77  return {}; // Silence compiler control paths return value warning
78  }
79 
80  template <class ConstBuffers>
81  static
83  buffer_string (ConstBuffers const& b)
84  {
85  using boost::asio::buffer;
86  using boost::asio::buffer_size;
87  std::string s;
88  s.resize(buffer_size(b));
89  buffer_copy(buffer(&s[0], s.size()), b);
90  return s;
91  }
92 
93  boost::asio::io_service ios_;
94  boost::optional<
95  boost::asio::io_service::work> work_;
96  boost::asio::io_service::strand strand_;
98  boost::asio::ip::tcp::socket stream_;
99  boost::beast::websocket::stream<boost::asio::ip::tcp::socket&> ws_;
100  boost::beast::multi_buffer rb_;
101 
102  bool peerClosed_ = false;
103 
104  // synchronize destructor
105  bool b0_ = false;
108 
109  // synchronize message queue
113 
114  unsigned rpc_version_;
115 
116  void
118  {
119  ios_.post(strand_.wrap([this] {
120  if (!peerClosed_)
121  {
122  ws_.async_close({}, strand_.wrap([&](error_code ec) {
123  stream_.cancel(ec);
124  }));
125  }
126  }));
127  work_ = boost::none;
128  thread_.join();
129  }
130 
131 public:
132  WSClientImpl(Config const& cfg, bool v2, unsigned rpc_version,
134  : work_(ios_)
135  , strand_(ios_)
136  , thread_([&]{ ios_.run(); })
137  , stream_(ios_)
138  , ws_(stream_)
139  , rpc_version_(rpc_version)
140  {
141  try
142  {
143  auto const ep = getEndpoint(cfg, v2);
144  stream_.connect(ep);
145  ws_.handshake_ex(ep.address().to_string() +
146  ":" + std::to_string(ep.port()), "/",
147  [&](boost::beast::websocket::request_type &req)
148  {
149  for (auto const& h : headers)
150  req.set(h.first, h.second);
151  });
152  ws_.async_read(rb_,
154  this, std::placeholders::_1)));
155  }
156  catch(std::exception&)
157  {
158  cleanup();
159  Rethrow();
160  }
161  }
162 
163  ~WSClientImpl() override
164  {
165  cleanup();
166  }
167 
169  invoke(std::string const& cmd,
170  Json::Value const& params) override
171  {
172  using boost::asio::buffer;
173  using namespace std::chrono_literals;
174 
175  {
176  Json::Value jp;
177  if(params)
178  jp = params;
179  if (rpc_version_ == 2)
180  {
181  jp[jss::method] = cmd;
182  jp[jss::jsonrpc] = "2.0";
183  jp[jss::ripplerpc] = "2.0";
184  jp[jss::id] = 5;
185  }
186  else
187  jp[jss::command] = cmd;
188  auto const s = to_string(jp);
189  ws_.write_some(true, buffer(s));
190  }
191 
192  auto jv = findMsg(5s,
193  [&](Json::Value const& jval)
194  {
195  return jval[jss::type] == jss::response;
196  });
197  if (jv)
198  {
199  // Normalize JSON output
200  jv->removeMember(jss::type);
201  if ((*jv).isMember(jss::status) &&
202  (*jv)[jss::status] == jss::error)
203  {
204  Json::Value ret;
205  ret[jss::result] = *jv;
206  if ((*jv).isMember(jss::error))
207  ret[jss::error] = (*jv)[jss::error];
208  ret[jss::status] = jss::error;
209  return ret;
210  }
211  if ((*jv).isMember(jss::status) &&
212  (*jv).isMember(jss::result))
213  (*jv)[jss::result][jss::status] =
214  (*jv)[jss::status];
215  return *jv;
216  }
217  return {};
218  }
219 
220  boost::optional<Json::Value>
221  getMsg(std::chrono::milliseconds const& timeout) override
222  {
224  {
226  if(! cv_.wait_for(lock, timeout,
227  [&]{ return ! msgs_.empty(); }))
228  return boost::none;
229  m = std::move(msgs_.back());
230  msgs_.pop_back();
231  }
232  return std::move(m->jv);
233  }
234 
235  boost::optional<Json::Value>
237  std::function<bool(Json::Value const&)> pred) override
238  {
240  {
242  if(! cv_.wait_for(lock, timeout,
243  [&]
244  {
245  for (auto it = msgs_.begin();
246  it != msgs_.end(); ++it)
247  {
248  if (pred((*it)->jv))
249  {
250  m = std::move(*it);
251  msgs_.erase(it);
252  return true;
253  }
254  }
255  return false;
256  }))
257  {
258  return boost::none;
259  }
260  }
261  return std::move(m->jv);
262  }
263 
264  unsigned version() const override
265  {
266  return rpc_version_;
267  }
268 
269 private:
270  void
272  {
273  if(ec)
274  {
275  if(ec == boost::beast::websocket::error::closed)
276  peerClosed_ = true;
277  return;
278  }
279 
280  Json::Value jv;
281  Json::Reader jr;
282  jr.parse(buffer_string(rb_.data()), jv);
283  rb_.consume(rb_.size());
284  auto m = std::make_shared<msg>(
285  std::move(jv));
286  {
287  std::lock_guard lock(m_);
288  msgs_.push_front(m);
289  cv_.notify_all();
290  }
291  ws_.async_read(rb_, strand_.wrap(
293  this, std::placeholders::_1)));
294  }
295 
296  // Called when the read op terminates
297  void
299  {
300  std::lock_guard lock(m0_);
301  b0_ = true;
302  cv0_.notify_all();
303  }
304 };
305 
307 makeWSClient(Config const& cfg, bool v2, unsigned rpc_version,
309 {
310  return std::make_unique<WSClientImpl>(cfg, v2, rpc_version, headers);
311 }
312 
313 } // test
314 } // ripple
ripple::test::WSClientImpl::version
unsigned version() const override
Get RPC 1.0 or RPC 2.0.
Definition: WSClient.cpp:264
ripple::test::WSClientImpl::cv_
std::condition_variable cv_
Definition: WSClient.cpp:111
std::string::resize
T resize(T... args)
ripple::test::WSClientImpl::ios_
boost::asio::io_service ios_
Definition: WSClient.cpp:93
std::bind
T bind(T... args)
std::string
STL class.
std::shared_ptr
STL class.
std::exception
STL class.
std::list
STL class.
ripple::test::WSClientImpl::error_code
boost::system::error_code error_code
Definition: WSClient.cpp:42
ripple::test::WSClientImpl::getMsg
boost::optional< Json::Value > getMsg(std::chrono::milliseconds const &timeout) override
Retrieve a message.
Definition: WSClient.cpp:221
std::string::size
T size(T... args)
ripple::test::WSClientImpl::msg::msg
msg(Json::Value &&jv_)
Definition: WSClient.cpp:49
std::chrono::milliseconds
ripple::ParsedPort::ip
boost::optional< boost::asio::ip::address > ip
Definition: Port.h:99
ripple::test::WSClientImpl::on_read_msg
void on_read_msg(error_code const &ec)
Definition: WSClient.cpp:271
std::lock_guard
STL class.
std::cerr
ripple::test::WSClientImpl::stream_
boost::asio::ip::tcp::socket stream_
Definition: WSClient.cpp:98
ripple::test::WSClientImpl::msg::jv
Json::Value jv
Definition: WSClient.cpp:46
ripple::test::WSClientImpl::invoke
Json::Value invoke(std::string const &cmd, Json::Value const &params) override
Submit a command synchronously.
Definition: WSClient.cpp:169
std::function
ripple::to_string
std::string to_string(ListDisposition disposition)
Definition: ValidatorList.cpp:41
ripple::parse_Port
void parse_Port(ParsedPort &port, Section const &section, std::ostream &log)
Definition: Port.cpp:139
Json::Reader
Unserialize a JSON document into a Value.
Definition: json_reader.h:36
ripple::test::WSClientImpl::buffer_string
static std::string buffer_string(ConstBuffers const &b)
Definition: WSClient.cpp:83
ripple::test::WSClientImpl::findMsg
boost::optional< Json::Value > findMsg(std::chrono::milliseconds const &timeout, std::function< bool(Json::Value const &)> pred) override
Retrieve a message that meets the predicate criteria.
Definition: WSClient.cpp:236
ripple::ParsedPort
Definition: Port.h:81
iostream
ripple::test::WSClientImpl::b0_
bool b0_
Definition: WSClient.cpp:105
ripple::test::WSClientImpl::ws_
boost::beast::websocket::stream< boost::asio::ip::tcp::socket & > ws_
Definition: WSClient.cpp:99
ripple::test::WSClientImpl::m0_
std::mutex m0_
Definition: WSClient.cpp:106
ripple::Section::values
std::vector< std::string > const & values() const
Returns all the values in the section.
Definition: BasicConfig.h:78
ripple::test::WSClientImpl
Definition: WSClient.cpp:40
ripple::test::WSClientImpl::on_read_done
void on_read_done()
Definition: WSClient.cpp:298
std::thread
STL class.
ripple::Config
Definition: Config.h:67
ripple::test::WSClientImpl::~WSClientImpl
~WSClientImpl() override
Definition: WSClient.cpp:163
ripple::Rethrow
void Rethrow()
Rethrow the exception currently being handled.
Definition: contract.h:50
std::unique_lock
STL class.
std::to_string
T to_string(T... args)
ripple::test::WSClientImpl::msgs_
std::list< std::shared_ptr< msg > > msgs_
Definition: WSClient.cpp:112
ripple::test::WSClientImpl::peerClosed_
bool peerClosed_
Definition: WSClient.cpp:102
std::condition_variable::wait_for
T wait_for(T... args)
ripple::test::WSClient
Definition: WSClient.h:32
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::ParsedPort::port
boost::optional< std::uint16_t > port
Definition: Port.h:100
ripple::test::WSClientImpl::rpc_version_
unsigned rpc_version_
Definition: WSClient.cpp:114
Json::Reader::parse
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:76
condition_variable
std::set::count
T count(T... args)
ripple::test::makeWSClient
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition: WSClient.cpp:307
ripple::test::WSClientImpl::msg
Definition: WSClient.cpp:44
ripple::test::WSClientImpl::m_
std::mutex m_
Definition: WSClient.cpp:110
std::mutex
STL class.
ripple::test::WSClientImpl::rb_
boost::beast::multi_buffer rb_
Definition: WSClient.cpp:100
ripple::test::WSClientImpl::thread_
std::thread thread_
Definition: WSClient.cpp:97
ripple::ParsedPort::protocol
std::set< std::string, boost::beast::iless > protocol
Definition: Port.h:86
ripple::test::WSClientImpl::cv0_
std::condition_variable cv0_
Definition: WSClient.cpp:107
std::unique_ptr
STL class.
ripple::test::WSClientImpl::strand_
boost::asio::io_service::strand strand_
Definition: WSClient.cpp:96
ripple::test::WSClientImpl::WSClientImpl
WSClientImpl(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers={})
Definition: WSClient.cpp:132
unordered_map
ripple::test::WSClientImpl::cleanup
void cleanup()
Definition: WSClient.cpp:117
ripple::test::WSClientImpl::work_
boost::optional< boost::asio::io_service::work > work_
Definition: WSClient.cpp:95
std::condition_variable::notify_all
T notify_all(T... args)
ripple::BasicConfig
Holds unparsed configuration information.
Definition: BasicConfig.h:177
std::thread::join
T join(T... args)
ripple::test::WSClientImpl::getEndpoint
static boost::asio::ip::tcp::endpoint getEndpoint(BasicConfig const &cfg, bool v2)
Definition: WSClient.cpp:57
ripple::BasicConfig::exists
bool exists(std::string const &name) const
Returns true if a section with the given name exists.
Definition: BasicConfig.cpp:134
Json::Value
Represents a JSON value.
Definition: json_value.h:141
ripple::BasicConfig::section
Section & section(std::string const &name)
Returns the section with the given name.
Definition: BasicConfig.cpp:140
string