rippled
Loading...
Searching...
No Matches
short_read_test.cpp
1#include <test/jtx/envconfig.h>
2
3#include <xrpl/basics/make_SSLContext.h>
4#include <xrpl/beast/core/CurrentThreadName.h>
5#include <xrpl/beast/unit_test.h>
6
7#include <boost/asio/bind_executor.hpp>
8#include <boost/asio/buffer.hpp>
9#include <boost/asio/ip/tcp.hpp>
10#include <boost/asio/read_until.hpp>
11#include <boost/asio/ssl.hpp>
12#include <boost/asio/strand.hpp>
13#include <boost/asio/streambuf.hpp>
14#include <boost/utility/in_place_factory.hpp>
15
16#include <condition_variable>
17#include <functional>
18#include <thread>
19#include <utility>
20
21namespace xrpl {
22/*
23
24Findings from the test:
25
26If the remote host calls async_shutdown then the local host's
27async_read will complete with eof.
28
29If both hosts call async_shutdown then the calls to async_shutdown
30will complete with eof.
31
32*/
33
35{
36private:
37 using io_context_type = boost::asio::io_context;
38 using strand_type = boost::asio::strand<io_context_type::executor_type>;
39 using timer_type = boost::asio::basic_waitable_timer<std::chrono::steady_clock>;
40 using acceptor_type = boost::asio::ip::tcp::acceptor;
41 using socket_type = boost::asio::ip::tcp::socket;
42 using stream_type = boost::asio::ssl::stream<socket_type&>;
43 using error_code = boost::system::error_code;
44 using endpoint_type = boost::asio::ip::tcp::endpoint;
45 using address_type = boost::asio::ip::address;
46
48 boost::optional<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_;
51
52 template <class Streambuf>
53 static void
54 write(Streambuf& sb, std::string const& s)
55 {
56 using boost::asio::buffer;
57 using boost::asio::buffer_copy;
58 using boost::asio::buffer_size;
59 boost::asio::const_buffer buf(s.data(), s.size());
60 sb.commit(buffer_copy(sb.prepare(buffer_size(buf)), buf));
61 }
62
63 //--------------------------------------------------------------------------
64
65 class Base
66 {
67 protected:
68 class Child
69 {
70 private:
72
73 public:
74 explicit Child(Base& base) : base_(base)
75 {
76 }
77
78 virtual ~Child()
79 {
80 base_.remove(this);
81 }
82
83 virtual void
84 close() = 0;
85 };
86
87 private:
91 bool closed_ = false;
92
93 public:
95 {
96 // Derived class must call wait() in the destructor
97 assert(list_.empty());
98 }
99
100 void
102 {
104 list_.emplace(child.get(), child);
105 }
106
107 void
108 remove(Child* child)
109 {
111 list_.erase(child);
112 if (list_.empty())
114 }
115
116 void
118 {
120 {
122 v.reserve(list_.size());
123 if (closed_)
124 return;
125 closed_ = true;
126 for (auto const& c : list_)
127 {
128 if (auto p = c.second.lock())
129 {
130 p->close();
131 // Must destroy shared_ptr outside the
132 // lock otherwise deadlock from the
133 // managed object's destructor.
134 v.emplace_back(std::move(p));
135 }
136 }
137 }
138 }
139
140 void
142 {
144 while (!list_.empty())
145 cond_.wait(lock);
146 }
147 };
148
149 //--------------------------------------------------------------------------
150
151 class Server : public Base
152 {
153 private:
156
158 {
164
165 explicit Acceptor(Server& server)
166 : Child(server)
167 , server_(server)
169 , acceptor_(
171 endpoint_type(boost::asio::ip::make_address(test::getEnvLocalhostAddr()), 0))
173 , strand_(boost::asio::make_strand(test_.io_context_))
174 {
175 acceptor_.listen();
176 server_.endpoint_ = acceptor_.local_endpoint();
177 }
178
179 void
180 close() override
181 {
182 if (!strand_.running_in_this_thread())
184 acceptor_.close();
185 }
186
187 void
189 {
190 acceptor_.async_accept(
191 socket_,
192 bind_executor(strand_, std::bind(&Acceptor::on_accept, shared_from_this(), std::placeholders::_1)));
193 }
194
195 void
196 fail(std::string const& what, error_code ec)
197 {
198 if (acceptor_.is_open())
199 {
200 if (ec != boost::asio::error::operation_aborted)
201 test_.log << what << ": " << ec.message() << std::endl;
202 acceptor_.close();
203 }
204 }
205
206 void
208 {
209 if (ec)
210 return fail("accept", ec);
211 auto const p = std::make_shared<Connection>(server_, std::move(socket_));
212 server_.add(p);
213 p->run();
214 acceptor_.async_accept(
215 socket_,
216 bind_executor(strand_, std::bind(&Acceptor::on_accept, shared_from_this(), std::placeholders::_1)));
217 }
218 };
219
221 {
228 boost::asio::streambuf buf_;
229
230 Connection(Server& server, socket_type&& socket)
231 : Child(server)
232 , server_(server)
234 , socket_(std::move(socket))
236 , strand_(boost::asio::make_strand(test_.io_context_))
238 {
239 }
240
241 void
242 close() override
243 {
244 if (!strand_.running_in_this_thread())
246 if (socket_.is_open())
247 {
248 socket_.close();
249 timer_.cancel();
250 }
251 }
252
253 void
255 {
256 timer_.expires_after(std::chrono::seconds(3));
257 timer_.async_wait(bind_executor(
258 strand_, std::bind(&Connection::on_timer, shared_from_this(), std::placeholders::_1)));
259 stream_.async_handshake(
260 stream_type::server,
261 bind_executor(
262 strand_, std::bind(&Connection::on_handshake, shared_from_this(), std::placeholders::_1)));
263 }
264
265 void
266 fail(std::string const& what, error_code ec)
267 {
268 if (socket_.is_open())
269 {
270 if (ec != boost::asio::error::operation_aborted)
271 test_.log << "[server] " << what << ": " << ec.message() << std::endl;
272 socket_.close();
273 timer_.cancel();
274 }
275 }
276
277 void
279 {
280 if (ec == boost::asio::error::operation_aborted)
281 return;
282 if (ec)
283 return fail("timer", ec);
284 test_.log << "[server] timeout" << std::endl;
285 socket_.close();
286 }
287
288 void
290 {
291 if (ec)
292 return fail("handshake", ec);
293#if 1
294 boost::asio::async_read_until(
295 stream_,
296 buf_,
297 "\n",
298 bind_executor(
299 strand_,
300 std::bind(
301 &Connection::on_read, shared_from_this(), std::placeholders::_1, std::placeholders::_2)));
302#else
303 close();
304#endif
305 }
306
307 void
308 on_read(error_code ec, std::size_t bytes_transferred)
309 {
310 if (ec == boost::asio::error::eof)
311 {
312 server_.test_.log << "[server] read: EOF" << std::endl;
313 return stream_.async_shutdown(bind_executor(
314 strand_, std::bind(&Connection::on_shutdown, shared_from_this(), std::placeholders::_1)));
315 }
316 if (ec)
317 return fail("read", ec);
318
319 buf_.commit(bytes_transferred);
320 buf_.consume(bytes_transferred);
321 write(buf_, "BYE\n");
322 boost::asio::async_write(
323 stream_,
324 buf_.data(),
325 bind_executor(
326 strand_,
327 std::bind(
328 &Connection::on_write, shared_from_this(), std::placeholders::_1, std::placeholders::_2)));
329 }
330
331 void
332 on_write(error_code ec, std::size_t bytes_transferred)
333 {
334 buf_.consume(bytes_transferred);
335 if (ec)
336 return fail("write", ec);
337 stream_.async_shutdown(bind_executor(
338 strand_, std::bind(&Connection::on_shutdown, shared_from_this(), std::placeholders::_1)));
339 }
340
341 void
343 {
344 if (ec)
345 return fail("shutdown", ec);
346 socket_.close();
347 timer_.cancel();
348 }
349 };
350
351 public:
352 explicit Server(short_read_test& test) : test_(test)
353 {
354 auto const p = std::make_shared<Acceptor>(*this);
355 add(p);
356 p->run();
357 }
358
360 {
361 close();
362 wait();
363 }
364
365 endpoint_type const&
366 endpoint() const
367 {
368 return endpoint_;
369 }
370 };
371
372 //--------------------------------------------------------------------------
373 class Client : public Base
374
375 {
376 private:
378
380 {
387 boost::asio::streambuf buf_;
389
390 Connection(Client& client, endpoint_type const& ep)
391 : Child(client)
392 , client_(client)
396 , strand_(boost::asio::make_strand(test_.io_context_))
398 , ep_(ep)
399 {
400 }
401
402 void
403 close() override
404 {
405 if (!strand_.running_in_this_thread())
407 if (socket_.is_open())
408 {
409 socket_.close();
410 timer_.cancel();
411 }
412 }
413
414 void
416 {
417 timer_.expires_after(std::chrono::seconds(3));
418 timer_.async_wait(bind_executor(
419 strand_, std::bind(&Connection::on_timer, shared_from_this(), std::placeholders::_1)));
420 socket_.async_connect(
421 ep,
422 bind_executor(
423 strand_, std::bind(&Connection::on_connect, shared_from_this(), std::placeholders::_1)));
424 }
425
426 void
427 fail(std::string const& what, error_code ec)
428 {
429 if (socket_.is_open())
430 {
431 if (ec != boost::asio::error::operation_aborted)
432 test_.log << "[client] " << what << ": " << ec.message() << std::endl;
433 socket_.close();
434 timer_.cancel();
435 }
436 }
437
438 void
440 {
441 if (ec == boost::asio::error::operation_aborted)
442 return;
443 if (ec)
444 return fail("timer", ec);
445 test_.log << "[client] timeout";
446 socket_.close();
447 }
448
449 void
451 {
452 if (ec)
453 return fail("connect", ec);
454 stream_.async_handshake(
455 stream_type::client,
456 bind_executor(
457 strand_, std::bind(&Connection::on_handshake, shared_from_this(), std::placeholders::_1)));
458 }
459
460 void
462 {
463 if (ec)
464 return fail("handshake", ec);
465 write(buf_, "HELLO\n");
466
467#if 1
468 boost::asio::async_write(
469 stream_,
470 buf_.data(),
471 bind_executor(
472 strand_,
473 std::bind(
474 &Connection::on_write, shared_from_this(), std::placeholders::_1, std::placeholders::_2)));
475#else
476 stream_.async_shutdown(bind_executor(
477 strand_, std::bind(&Connection::on_shutdown, shared_from_this(), std::placeholders::_1)));
478#endif
479 }
480
481 void
482 on_write(error_code ec, std::size_t bytes_transferred)
483 {
484 buf_.consume(bytes_transferred);
485 if (ec)
486 return fail("write", ec);
487#if 1
488 boost::asio::async_read_until(
489 stream_,
490 buf_,
491 "\n",
492 bind_executor(
493 strand_,
494 std::bind(
495 &Connection::on_read, shared_from_this(), std::placeholders::_1, std::placeholders::_2)));
496#else
497 stream_.async_shutdown(bind_executor(
498 strand_, std::bind(&Connection::on_shutdown, shared_from_this(), std::placeholders::_1)));
499#endif
500 }
501
502 void
503 on_read(error_code ec, std::size_t bytes_transferred)
504 {
505 if (ec)
506 return fail("read", ec);
507 buf_.commit(bytes_transferred);
508 stream_.async_shutdown(bind_executor(
509 strand_, std::bind(&Connection::on_shutdown, shared_from_this(), std::placeholders::_1)));
510 }
511
512 void
514 {
515 if (ec)
516 return fail("shutdown", ec);
517 socket_.close();
518 timer_.cancel();
519 }
520 };
521
522 public:
523 Client(short_read_test& test, endpoint_type const& ep) : test_(test)
524 {
525 auto const p = std::make_shared<Connection>(*this, ep);
526 add(p);
527 p->run(ep);
528 }
529
531 {
532 close();
533 wait();
534 }
535 };
536
537public:
539 : work_(io_context_.get_executor())
540 , thread_(std::thread([this]() {
541 beast::setCurrentThreadName("io_context");
542 this->io_context_.run();
543 }))
545 {
546 }
547
549 {
550 work_.reset();
551 thread_.join();
552 }
553
554 void
555 run() override
556 {
557 Server s(*this);
558 Client c(*this, s.endpoint());
559 c.wait();
560 pass();
561 }
562};
563
564BEAST_DEFINE_TESTSUITE(short_read, overlay, xrpl);
565
566} // namespace xrpl
T bind(T... args)
A testsuite class.
Definition suite.h:51
log_os< char > log
Logging output stream.
Definition suite.h:144
void pass()
Record a successful test condition.
Definition suite.h:494
friend class thread
Definition suite.h:295
std::map< Child *, std::weak_ptr< Child > > list_
std::condition_variable cond_
void add(std::shared_ptr< Child > const &child)
Client(short_read_test &test, endpoint_type const &ep)
Server(short_read_test &test)
endpoint_type const & endpoint() const
boost::asio::ip::tcp::endpoint endpoint_type
boost::asio::io_context io_context_type
boost::asio::ip::address address_type
boost::asio::strand< io_context_type::executor_type > strand_type
void run() override
Runs the suite.
io_context_type io_context_
boost::asio::ip::tcp::socket socket_type
boost::system::error_code error_code
std::shared_ptr< boost::asio::ssl::context > context_
boost::asio::ip::tcp::acceptor acceptor_type
boost::asio::basic_waitable_timer< std::chrono::steady_clock > timer_type
boost::asio::ssl::stream< socket_type & > stream_type
static void write(Streambuf &sb, std::string const &s)
boost::optional< boost::asio::executor_work_guard< boost::asio::io_context::executor_type > > work_
T data(T... args)
T emplace_back(T... args)
T endl(T... args)
T is_same_v
T join(T... args)
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
STL namespace.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::shared_ptr< boost::asio::ssl::context > make_SSLContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
T reserve(T... args)
T size(T... args)
void on_write(error_code ec, std::size_t bytes_transferred)
Connection(Client &client, endpoint_type const &ep)
void fail(std::string const &what, error_code ec)
void on_read(error_code ec, std::size_t bytes_transferred)
void fail(std::string const &what, error_code ec)
void on_read(error_code ec, std::size_t bytes_transferred)
void on_write(error_code ec, std::size_t bytes_transferred)
void fail(std::string const &what, error_code ec)
Connection(Server &server, socket_type &&socket)