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#include <xrpl/basics/make_SSLContext.h>
22#include <xrpl/beast/core/CurrentThreadName.h>
23#include <xrpl/beast/unit_test.h>
24
25#include <boost/asio.hpp>
26#include <boost/asio/ssl.hpp>
27#include <boost/utility/in_place_factory.hpp>
28
29#include <condition_variable>
30#include <functional>
31#include <memory>
32#include <optional>
33#include <thread>
34#include <utility>
35
36namespace ripple {
37/*
38
39Findings from the test:
40
41If the remote host calls async_shutdown then the local host's
42async_read will complete with eof.
43
44If both hosts call async_shutdown then the calls to async_shutdown
45will complete with eof.
46
47*/
48
50{
51private:
52 using io_context_type = boost::asio::io_context;
53 using strand_type = boost::asio::io_context::strand;
54 using timer_type =
55 boost::asio::basic_waitable_timer<std::chrono::steady_clock>;
56 using acceptor_type = boost::asio::ip::tcp::acceptor;
57 using socket_type = boost::asio::ip::tcp::socket;
58 using stream_type = boost::asio::ssl::stream<socket_type&>;
59 using error_code = boost::system::error_code;
60 using endpoint_type = boost::asio::ip::tcp::endpoint;
61 using address_type = boost::asio::ip::address;
62
68
69 template <class Streambuf>
70 static void
71 write(Streambuf& sb, std::string const& s)
72 {
73 using boost::asio::buffer;
74 using boost::asio::buffer_copy;
75 using boost::asio::buffer_size;
76 boost::asio::const_buffers_1 buf(s.data(), s.size());
77 sb.commit(buffer_copy(sb.prepare(buffer_size(buf)), buf));
78 }
79
80 //--------------------------------------------------------------------------
81
82 class Base
83 {
84 protected:
85 class Child
86 {
87 private:
89
90 public:
91 explicit Child(Base& base) : base_(base)
92 {
93 }
94
95 virtual ~Child()
96 {
97 base_.remove(this);
98 }
99
100 virtual void
101 close() = 0;
102 };
103
104 private:
108 bool closed_ = false;
109
110 public:
112 {
113 // Derived class must call wait() in the destructor
114 assert(list_.empty());
115 }
116
117 void
119 {
121 list_.emplace(child.get(), child);
122 }
123
124 void
125 remove(Child* child)
126 {
128 list_.erase(child);
129 if (list_.empty())
131 }
132
133 void
135 {
137 {
139 v.reserve(list_.size());
140 if (closed_)
141 return;
142 closed_ = true;
143 for (auto const& c : list_)
144 {
145 if (auto p = c.second.lock())
146 {
147 p->close();
148 // Must destroy shared_ptr outside the
149 // lock otherwise deadlock from the
150 // managed object's destructor.
151 v.emplace_back(std::move(p));
152 }
153 }
154 }
155 }
156
157 void
159 {
161 while (!list_.empty())
162 cond_.wait(lock);
163 }
164 };
165
166 //--------------------------------------------------------------------------
167
168 class Server : public Base
169 {
170 private:
173
175 {
181
182 explicit Acceptor(Server& server)
183 : Child(server)
184 , server_(server)
186 , acceptor_(
189 beast::IP::Address::from_string(
190 test::getEnvLocalhostAddr()),
191 0))
194 {
195 acceptor_.listen();
196 server_.endpoint_ = acceptor_.local_endpoint();
197 }
198
199 void
200 close() override
201 {
202 if (!strand_.running_in_this_thread())
203 return post(
204 strand_,
206 acceptor_.close();
207 }
208
209 void
211 {
212 acceptor_.async_accept(
213 socket_,
214 bind_executor(
215 strand_,
216 std::bind(
219 std::placeholders::_1)));
220 }
221
222 void
223 fail(std::string const& what, error_code ec)
224 {
225 if (acceptor_.is_open())
226 {
227 if (ec != boost::asio::error::operation_aborted)
228 test_.log << what << ": " << ec.message() << std::endl;
229 acceptor_.close();
230 }
231 }
232
233 void
235 {
236 if (ec)
237 return fail("accept", ec);
238 auto const p =
239 std::make_shared<Connection>(server_, std::move(socket_));
240 server_.add(p);
241 p->run();
242 acceptor_.async_accept(
243 socket_,
244 bind_executor(
245 strand_,
246 std::bind(
249 std::placeholders::_1)));
250 }
251 };
252
254 {
261 boost::asio::streambuf buf_;
262
263 Connection(Server& server, socket_type&& socket)
264 : Child(server)
265 , server_(server)
267 , socket_(std::move(socket))
271 {
272 }
273
274 void
275 close() override
276 {
277 if (!strand_.running_in_this_thread())
278 return post(
279 strand_,
281 if (socket_.is_open())
282 {
283 socket_.close();
284 timer_.cancel();
285 }
286 }
287
288 void
290 {
291 timer_.expires_from_now(std::chrono::seconds(3));
292 timer_.async_wait(bind_executor(
293 strand_,
294 std::bind(
297 std::placeholders::_1)));
298 stream_.async_handshake(
299 stream_type::server,
300 bind_executor(
301 strand_,
302 std::bind(
305 std::placeholders::_1)));
306 }
307
308 void
309 fail(std::string const& what, error_code ec)
310 {
311 if (socket_.is_open())
312 {
313 if (ec != boost::asio::error::operation_aborted)
314 test_.log << "[server] " << what << ": " << ec.message()
315 << std::endl;
316 socket_.close();
317 timer_.cancel();
318 }
319 }
320
321 void
323 {
324 if (ec == boost::asio::error::operation_aborted)
325 return;
326 if (ec)
327 return fail("timer", ec);
328 test_.log << "[server] timeout" << std::endl;
329 socket_.close();
330 }
331
332 void
334 {
335 if (ec)
336 return fail("handshake", ec);
337#if 1
338 boost::asio::async_read_until(
339 stream_,
340 buf_,
341 "\n",
342 bind_executor(
343 strand_,
344 std::bind(
347 std::placeholders::_1,
348 std::placeholders::_2)));
349#else
350 close();
351#endif
352 }
353
354 void
355 on_read(error_code ec, std::size_t bytes_transferred)
356 {
357 if (ec == boost::asio::error::eof)
358 {
359 server_.test_.log << "[server] read: EOF" << std::endl;
360 return stream_.async_shutdown(bind_executor(
361 strand_,
362 std::bind(
365 std::placeholders::_1)));
366 }
367 if (ec)
368 return fail("read", ec);
369
370 buf_.commit(bytes_transferred);
371 buf_.consume(bytes_transferred);
372 write(buf_, "BYE\n");
373 boost::asio::async_write(
374 stream_,
375 buf_.data(),
376 bind_executor(
377 strand_,
378 std::bind(
381 std::placeholders::_1,
382 std::placeholders::_2)));
383 }
384
385 void
386 on_write(error_code ec, std::size_t bytes_transferred)
387 {
388 buf_.consume(bytes_transferred);
389 if (ec)
390 return fail("write", ec);
391 stream_.async_shutdown(bind_executor(
392 strand_,
393 std::bind(
396 std::placeholders::_1)));
397 }
398
399 void
401 {
402 if (ec)
403 return fail("shutdown", ec);
404 socket_.close();
405 timer_.cancel();
406 }
407 };
408
409 public:
410 explicit Server(short_read_test& test) : test_(test)
411 {
412 auto const p = std::make_shared<Acceptor>(*this);
413 add(p);
414 p->run();
415 }
416
418 {
419 close();
420 wait();
421 }
422
423 endpoint_type const&
424 endpoint() const
425 {
426 return endpoint_;
427 }
428 };
429
430 //--------------------------------------------------------------------------
431 class Client : public Base
432
433 {
434 private:
436
438 {
445 boost::asio::streambuf buf_;
447
448 Connection(Client& client, endpoint_type const& ep)
449 : Child(client)
450 , client_(client)
456 , ep_(ep)
457 {
458 }
459
460 void
461 close() override
462 {
463 if (!strand_.running_in_this_thread())
464 return post(
465 strand_,
467 if (socket_.is_open())
468 {
469 socket_.close();
470 timer_.cancel();
471 }
472 }
473
474 void
476 {
477 timer_.expires_from_now(std::chrono::seconds(3));
478 timer_.async_wait(bind_executor(
479 strand_,
480 std::bind(
483 std::placeholders::_1)));
484 socket_.async_connect(
485 ep,
486 bind_executor(
487 strand_,
488 std::bind(
491 std::placeholders::_1)));
492 }
493
494 void
495 fail(std::string const& what, error_code ec)
496 {
497 if (socket_.is_open())
498 {
499 if (ec != boost::asio::error::operation_aborted)
500 test_.log << "[client] " << what << ": " << ec.message()
501 << std::endl;
502 socket_.close();
503 timer_.cancel();
504 }
505 }
506
507 void
509 {
510 if (ec == boost::asio::error::operation_aborted)
511 return;
512 if (ec)
513 return fail("timer", ec);
514 test_.log << "[client] timeout";
515 socket_.close();
516 }
517
518 void
520 {
521 if (ec)
522 return fail("connect", ec);
523 stream_.async_handshake(
524 stream_type::client,
525 bind_executor(
526 strand_,
527 std::bind(
530 std::placeholders::_1)));
531 }
532
533 void
535 {
536 if (ec)
537 return fail("handshake", ec);
538 write(buf_, "HELLO\n");
539
540#if 1
541 boost::asio::async_write(
542 stream_,
543 buf_.data(),
544 bind_executor(
545 strand_,
546 std::bind(
549 std::placeholders::_1,
550 std::placeholders::_2)));
551#else
552 stream_.async_shutdown(bind_executor(
553 strand_,
554 std::bind(
557 std::placeholders::_1)));
558#endif
559 }
560
561 void
562 on_write(error_code ec, std::size_t bytes_transferred)
563 {
564 buf_.consume(bytes_transferred);
565 if (ec)
566 return fail("write", ec);
567#if 1
568 boost::asio::async_read_until(
569 stream_,
570 buf_,
571 "\n",
572 bind_executor(
573 strand_,
574 std::bind(
577 std::placeholders::_1,
578 std::placeholders::_2)));
579#else
580 stream_.async_shutdown(bind_executor(
581 strand_,
582 std::bind(
585 std::placeholders::_1)));
586#endif
587 }
588
589 void
590 on_read(error_code ec, std::size_t bytes_transferred)
591 {
592 if (ec)
593 return fail("read", ec);
594 buf_.commit(bytes_transferred);
595 stream_.async_shutdown(bind_executor(
596 strand_,
597 std::bind(
600 std::placeholders::_1)));
601 }
602
603 void
605 {
606 if (ec)
607 return fail("shutdown", ec);
608 socket_.close();
609 timer_.cancel();
610 }
611 };
612
613 public:
614 Client(short_read_test& test, endpoint_type const& ep) : test_(test)
615 {
616 auto const p = std::make_shared<Connection>(*this, ep);
617 add(p);
618 p->run(ep);
619 }
620
622 {
623 close();
624 wait();
625 }
626 };
627
628public:
630 : work_(io_context_.get_executor())
631 , thread_(std::thread([this]() {
632 beast::setCurrentThreadName("io_context");
633 this->io_context_.run();
634 }))
636 {
637 }
638
640 {
641 work_.reset();
642 thread_.join();
643 }
644
645 void
646 run() override
647 {
648 Server s(*this);
649 Client c(*this, s.endpoint());
650 c.wait();
651 pass();
652 }
653};
654
655BEAST_DEFINE_TESTSUITE(short_read, overlay, ripple);
656
657} // namespace ripple
T bind(T... args)
A testsuite class.
Definition: suite.h:53
log_os< char > log
Logging output stream.
Definition: suite.h:150
void pass()
Record a successful test condition.
Definition: suite.h:509
friend class thread
Definition: suite.h:305
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::asio::io_context::strand strand_type
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
std::optional< boost::asio::executor_work_guard< boost::asio::executor > > work_
boost::asio::basic_waitable_timer< std::chrono::steady_clock > timer_type
boost::asio::ip::tcp::acceptor acceptor_type
io_context_type io_context_
static void write(Streambuf &sb, std::string const &s)
boost::asio::ip::address address_type
boost::asio::ip::tcp::socket socket_type
T data(T... args)
T emplace_back(T... args)
T endl(T... args)
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:26
std::shared_ptr< boost::asio::ssl::context > make_SSLContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
bool from_string(RangeSet< T > &rs, std::string const &s)
Convert the given styled string to a RangeSet.
Definition: RangeSet.h:124
STL namespace.
T reserve(T... args)
T reset(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)