rippled
Loading...
Searching...
No Matches
short_read_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4
5 Copyright 2014 Ripple Labs Inc.
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 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
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/envconfig.h>
21
22#include <xrpl/basics/make_SSLContext.h>
23#include <xrpl/beast/core/CurrentThreadName.h>
24#include <xrpl/beast/unit_test.h>
25
26#include <boost/asio/bind_executor.hpp>
27#include <boost/asio/buffer.hpp>
28#include <boost/asio/ip/tcp.hpp>
29#include <boost/asio/read_until.hpp>
30#include <boost/asio/ssl.hpp>
31#include <boost/asio/strand.hpp>
32#include <boost/asio/streambuf.hpp>
33#include <boost/utility/in_place_factory.hpp>
34
35#include <condition_variable>
36#include <functional>
37#include <thread>
38#include <utility>
39
40namespace ripple {
41/*
42
43Findings from the test:
44
45If the remote host calls async_shutdown then the local host's
46async_read will complete with eof.
47
48If both hosts call async_shutdown then the calls to async_shutdown
49will complete with eof.
50
51*/
52
54{
55private:
56 using io_context_type = boost::asio::io_context;
57 using strand_type = boost::asio::strand<io_context_type::executor_type>;
58 using timer_type =
59 boost::asio::basic_waitable_timer<std::chrono::steady_clock>;
60 using acceptor_type = boost::asio::ip::tcp::acceptor;
61 using socket_type = boost::asio::ip::tcp::socket;
62 using stream_type = boost::asio::ssl::stream<socket_type&>;
63 using error_code = boost::system::error_code;
64 using endpoint_type = boost::asio::ip::tcp::endpoint;
65 using address_type = boost::asio::ip::address;
66
68 boost::optional<boost::asio::executor_work_guard<
69 boost::asio::io_context::executor_type>>
73
74 template <class Streambuf>
75 static void
76 write(Streambuf& sb, std::string const& s)
77 {
78 using boost::asio::buffer;
79 using boost::asio::buffer_copy;
80 using boost::asio::buffer_size;
81 boost::asio::const_buffer buf(s.data(), s.size());
82 sb.commit(buffer_copy(sb.prepare(buffer_size(buf)), buf));
83 }
84
85 //--------------------------------------------------------------------------
86
87 class Base
88 {
89 protected:
90 class Child
91 {
92 private:
94
95 public:
96 explicit Child(Base& base) : base_(base)
97 {
98 }
99
100 virtual ~Child()
101 {
102 base_.remove(this);
103 }
104
105 virtual void
106 close() = 0;
107 };
108
109 private:
113 bool closed_ = false;
114
115 public:
117 {
118 // Derived class must call wait() in the destructor
119 assert(list_.empty());
120 }
121
122 void
124 {
126 list_.emplace(child.get(), child);
127 }
128
129 void
130 remove(Child* child)
131 {
133 list_.erase(child);
134 if (list_.empty())
136 }
137
138 void
140 {
142 {
144 v.reserve(list_.size());
145 if (closed_)
146 return;
147 closed_ = true;
148 for (auto const& c : list_)
149 {
150 if (auto p = c.second.lock())
151 {
152 p->close();
153 // Must destroy shared_ptr outside the
154 // lock otherwise deadlock from the
155 // managed object's destructor.
156 v.emplace_back(std::move(p));
157 }
158 }
159 }
160 }
161
162 void
164 {
166 while (!list_.empty())
167 cond_.wait(lock);
168 }
169 };
170
171 //--------------------------------------------------------------------------
172
173 class Server : public Base
174 {
175 private:
178
180 {
186
187 explicit Acceptor(Server& server)
188 : Child(server)
189 , server_(server)
191 , acceptor_(
194 boost::asio::ip::make_address(
195 test::getEnvLocalhostAddr()),
196 0))
198 , strand_(boost::asio::make_strand(test_.io_context_))
199 {
200 acceptor_.listen();
201 server_.endpoint_ = acceptor_.local_endpoint();
202 }
203
204 void
205 close() override
206 {
207 if (!strand_.running_in_this_thread())
208 return post(
209 strand_,
211 acceptor_.close();
212 }
213
214 void
216 {
217 acceptor_.async_accept(
218 socket_,
219 bind_executor(
220 strand_,
221 std::bind(
224 std::placeholders::_1)));
225 }
226
227 void
228 fail(std::string const& what, error_code ec)
229 {
230 if (acceptor_.is_open())
231 {
232 if (ec != boost::asio::error::operation_aborted)
233 test_.log << what << ": " << ec.message() << std::endl;
234 acceptor_.close();
235 }
236 }
237
238 void
240 {
241 if (ec)
242 return fail("accept", ec);
243 auto const p =
245 server_.add(p);
246 p->run();
247 acceptor_.async_accept(
248 socket_,
249 bind_executor(
250 strand_,
251 std::bind(
254 std::placeholders::_1)));
255 }
256 };
257
259 {
266 boost::asio::streambuf buf_;
267
268 Connection(Server& server, socket_type&& socket)
269 : Child(server)
270 , server_(server)
272 , socket_(std::move(socket))
274 , strand_(boost::asio::make_strand(test_.io_context_))
276 {
277 }
278
279 void
280 close() override
281 {
282 if (!strand_.running_in_this_thread())
283 return post(
284 strand_,
286 if (socket_.is_open())
287 {
288 socket_.close();
289 timer_.cancel();
290 }
291 }
292
293 void
295 {
296 timer_.expires_after(std::chrono::seconds(3));
297 timer_.async_wait(bind_executor(
298 strand_,
299 std::bind(
302 std::placeholders::_1)));
303 stream_.async_handshake(
304 stream_type::server,
305 bind_executor(
306 strand_,
307 std::bind(
310 std::placeholders::_1)));
311 }
312
313 void
314 fail(std::string const& what, error_code ec)
315 {
316 if (socket_.is_open())
317 {
318 if (ec != boost::asio::error::operation_aborted)
319 test_.log << "[server] " << what << ": " << ec.message()
320 << std::endl;
321 socket_.close();
322 timer_.cancel();
323 }
324 }
325
326 void
328 {
329 if (ec == boost::asio::error::operation_aborted)
330 return;
331 if (ec)
332 return fail("timer", ec);
333 test_.log << "[server] timeout" << std::endl;
334 socket_.close();
335 }
336
337 void
339 {
340 if (ec)
341 return fail("handshake", ec);
342#if 1
343 boost::asio::async_read_until(
344 stream_,
345 buf_,
346 "\n",
347 bind_executor(
348 strand_,
349 std::bind(
352 std::placeholders::_1,
353 std::placeholders::_2)));
354#else
355 close();
356#endif
357 }
358
359 void
360 on_read(error_code ec, std::size_t bytes_transferred)
361 {
362 if (ec == boost::asio::error::eof)
363 {
364 server_.test_.log << "[server] read: EOF" << std::endl;
365 return stream_.async_shutdown(bind_executor(
366 strand_,
367 std::bind(
370 std::placeholders::_1)));
371 }
372 if (ec)
373 return fail("read", ec);
374
375 buf_.commit(bytes_transferred);
376 buf_.consume(bytes_transferred);
377 write(buf_, "BYE\n");
378 boost::asio::async_write(
379 stream_,
380 buf_.data(),
381 bind_executor(
382 strand_,
383 std::bind(
386 std::placeholders::_1,
387 std::placeholders::_2)));
388 }
389
390 void
391 on_write(error_code ec, std::size_t bytes_transferred)
392 {
393 buf_.consume(bytes_transferred);
394 if (ec)
395 return fail("write", ec);
396 stream_.async_shutdown(bind_executor(
397 strand_,
398 std::bind(
401 std::placeholders::_1)));
402 }
403
404 void
406 {
407 if (ec)
408 return fail("shutdown", ec);
409 socket_.close();
410 timer_.cancel();
411 }
412 };
413
414 public:
415 explicit Server(short_read_test& test) : test_(test)
416 {
417 auto const p = std::make_shared<Acceptor>(*this);
418 add(p);
419 p->run();
420 }
421
423 {
424 close();
425 wait();
426 }
427
428 endpoint_type const&
429 endpoint() const
430 {
431 return endpoint_;
432 }
433 };
434
435 //--------------------------------------------------------------------------
436 class Client : public Base
437
438 {
439 private:
441
443 {
450 boost::asio::streambuf buf_;
452
453 Connection(Client& client, endpoint_type const& ep)
454 : Child(client)
455 , client_(client)
459 , strand_(boost::asio::make_strand(test_.io_context_))
461 , ep_(ep)
462 {
463 }
464
465 void
466 close() override
467 {
468 if (!strand_.running_in_this_thread())
469 return post(
470 strand_,
472 if (socket_.is_open())
473 {
474 socket_.close();
475 timer_.cancel();
476 }
477 }
478
479 void
481 {
482 timer_.expires_after(std::chrono::seconds(3));
483 timer_.async_wait(bind_executor(
484 strand_,
485 std::bind(
488 std::placeholders::_1)));
489 socket_.async_connect(
490 ep,
491 bind_executor(
492 strand_,
493 std::bind(
496 std::placeholders::_1)));
497 }
498
499 void
500 fail(std::string const& what, error_code ec)
501 {
502 if (socket_.is_open())
503 {
504 if (ec != boost::asio::error::operation_aborted)
505 test_.log << "[client] " << what << ": " << ec.message()
506 << std::endl;
507 socket_.close();
508 timer_.cancel();
509 }
510 }
511
512 void
514 {
515 if (ec == boost::asio::error::operation_aborted)
516 return;
517 if (ec)
518 return fail("timer", ec);
519 test_.log << "[client] timeout";
520 socket_.close();
521 }
522
523 void
525 {
526 if (ec)
527 return fail("connect", ec);
528 stream_.async_handshake(
529 stream_type::client,
530 bind_executor(
531 strand_,
532 std::bind(
535 std::placeholders::_1)));
536 }
537
538 void
540 {
541 if (ec)
542 return fail("handshake", ec);
543 write(buf_, "HELLO\n");
544
545#if 1
546 boost::asio::async_write(
547 stream_,
548 buf_.data(),
549 bind_executor(
550 strand_,
551 std::bind(
554 std::placeholders::_1,
555 std::placeholders::_2)));
556#else
557 stream_.async_shutdown(bind_executor(
558 strand_,
559 std::bind(
562 std::placeholders::_1)));
563#endif
564 }
565
566 void
567 on_write(error_code ec, std::size_t bytes_transferred)
568 {
569 buf_.consume(bytes_transferred);
570 if (ec)
571 return fail("write", ec);
572#if 1
573 boost::asio::async_read_until(
574 stream_,
575 buf_,
576 "\n",
577 bind_executor(
578 strand_,
579 std::bind(
582 std::placeholders::_1,
583 std::placeholders::_2)));
584#else
585 stream_.async_shutdown(bind_executor(
586 strand_,
587 std::bind(
590 std::placeholders::_1)));
591#endif
592 }
593
594 void
595 on_read(error_code ec, std::size_t bytes_transferred)
596 {
597 if (ec)
598 return fail("read", ec);
599 buf_.commit(bytes_transferred);
600 stream_.async_shutdown(bind_executor(
601 strand_,
602 std::bind(
605 std::placeholders::_1)));
606 }
607
608 void
610 {
611 if (ec)
612 return fail("shutdown", ec);
613 socket_.close();
614 timer_.cancel();
615 }
616 };
617
618 public:
619 Client(short_read_test& test, endpoint_type const& ep) : test_(test)
620 {
621 auto const p = std::make_shared<Connection>(*this, ep);
622 add(p);
623 p->run(ep);
624 }
625
627 {
628 close();
629 wait();
630 }
631 };
632
633public:
635 : work_(io_context_.get_executor())
636 , thread_(std::thread([this]() {
637 beast::setCurrentThreadName("io_context");
638 this->io_context_.run();
639 }))
641 {
642 }
643
645 {
646 work_.reset();
647 thread_.join();
648 }
649
650 void
651 run() override
652 {
653 Server s(*this);
654 Client c(*this, s.endpoint());
655 c.wait();
656 pass();
657 }
658};
659
660BEAST_DEFINE_TESTSUITE(short_read, overlay, ripple);
661
662} // namespace ripple
T bind(T... args)
A testsuite class.
Definition suite.h:55
log_os< char > log
Logging output stream.
Definition suite.h:152
void pass()
Record a successful test condition.
Definition suite.h:511
friend class thread
Definition suite.h:307
std::map< Child *, std::weak_ptr< Child > > list_
void add(std::shared_ptr< Child > const &child)
std::condition_variable cond_
Client(short_read_test &test, endpoint_type const &ep)
endpoint_type const & endpoint() const
std::shared_ptr< boost::asio::ssl::context > context_
void run() override
Runs the suite.
boost::system::error_code error_code
boost::asio::ssl::stream< socket_type & > stream_type
boost::asio::io_context io_context_type
boost::asio::ip::tcp::endpoint endpoint_type
boost::asio::basic_waitable_timer< std::chrono::steady_clock > timer_type
boost::asio::ip::tcp::acceptor acceptor_type
static void write(Streambuf &sb, std::string const &s)
boost::asio::ip::address address_type
boost::asio::strand< io_context_type::executor_type > strand_type
boost::asio::ip::tcp::socket socket_type
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.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
std::shared_ptr< boost::asio::ssl::context > make_SSLContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
STL namespace.
T reserve(T... args)
T size(T... args)
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(Client &client, endpoint_type const &ep)
void fail(std::string const &what, error_code ec)
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)
Connection(Server &server, socket_type &&socket)