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 <functional>
30#include <optional>
31#include <thread>
32#include <utility>
33
34namespace ripple {
35/*
36
37Findings from the test:
38
39If the remote host calls async_shutdown then the local host's
40async_read will complete with eof.
41
42If both hosts call async_shutdown then the calls to async_shutdown
43will complete with eof.
44
45*/
46
48{
49private:
50 using io_context_type = boost::asio::io_context;
51 using strand_type = boost::asio::io_context::strand;
52 using timer_type =
53 boost::asio::basic_waitable_timer<std::chrono::steady_clock>;
54 using acceptor_type = boost::asio::ip::tcp::acceptor;
55 using socket_type = boost::asio::ip::tcp::socket;
56 using stream_type = boost::asio::ssl::stream<socket_type&>;
57 using error_code = boost::system::error_code;
58 using endpoint_type = boost::asio::ip::tcp::endpoint;
59 using address_type = boost::asio::ip::address;
60
66
67 template <class Streambuf>
68 static void
69 write(Streambuf& sb, std::string const& s)
70 {
71 using boost::asio::buffer;
72 using boost::asio::buffer_copy;
73 using boost::asio::buffer_size;
74 boost::asio::const_buffers_1 buf(s.data(), s.size());
75 sb.commit(buffer_copy(sb.prepare(buffer_size(buf)), buf));
76 }
77
78 //--------------------------------------------------------------------------
79
80 class Base
81 {
82 protected:
83 class Child
84 {
85 private:
87
88 public:
89 explicit Child(Base& base) : base_(base)
90 {
91 }
92
93 virtual ~Child()
94 {
95 base_.remove(this);
96 }
97
98 virtual void
99 close() = 0;
100 };
101
102 private:
106 bool closed_ = false;
107
108 public:
110 {
111 // Derived class must call wait() in the destructor
112 assert(list_.empty());
113 }
114
115 void
117 {
119 list_.emplace(child.get(), child);
120 }
121
122 void
123 remove(Child* child)
124 {
126 list_.erase(child);
127 if (list_.empty())
129 }
130
131 void
133 {
135 {
137 v.reserve(list_.size());
138 if (closed_)
139 return;
140 closed_ = true;
141 for (auto const& c : list_)
142 {
143 if (auto p = c.second.lock())
144 {
145 p->close();
146 // Must destroy shared_ptr outside the
147 // lock otherwise deadlock from the
148 // managed object's destructor.
149 v.emplace_back(std::move(p));
150 }
151 }
152 }
153 }
154
155 void
157 {
159 while (!list_.empty())
160 cond_.wait(lock);
161 }
162 };
163
164 //--------------------------------------------------------------------------
165
166 class Server : public Base
167 {
168 private:
171
173 {
179
180 explicit Acceptor(Server& server)
181 : Child(server)
182 , server_(server)
184 , acceptor_(
187 beast::IP::Address::from_string(
188 test::getEnvLocalhostAddr()),
189 0))
192 {
193 acceptor_.listen();
194 server_.endpoint_ = acceptor_.local_endpoint();
195 }
196
197 void
198 close() override
199 {
200 if (!strand_.running_in_this_thread())
201 return post(
202 strand_,
204 acceptor_.close();
205 }
206
207 void
209 {
210 acceptor_.async_accept(
211 socket_,
212 bind_executor(
213 strand_,
214 std::bind(
217 std::placeholders::_1)));
218 }
219
220 void
221 fail(std::string const& what, error_code ec)
222 {
223 if (acceptor_.is_open())
224 {
225 if (ec != boost::asio::error::operation_aborted)
226 test_.log << what << ": " << ec.message() << std::endl;
227 acceptor_.close();
228 }
229 }
230
231 void
233 {
234 if (ec)
235 return fail("accept", ec);
236 auto const p =
237 std::make_shared<Connection>(server_, std::move(socket_));
238 server_.add(p);
239 p->run();
240 acceptor_.async_accept(
241 socket_,
242 bind_executor(
243 strand_,
244 std::bind(
247 std::placeholders::_1)));
248 }
249 };
250
252 {
259 boost::asio::streambuf buf_;
260
261 Connection(Server& server, socket_type&& socket)
262 : Child(server)
263 , server_(server)
265 , socket_(std::move(socket))
269 {
270 }
271
272 void
273 close() override
274 {
275 if (!strand_.running_in_this_thread())
276 return post(
277 strand_,
279 if (socket_.is_open())
280 {
281 socket_.close();
282 timer_.cancel();
283 }
284 }
285
286 void
288 {
289 timer_.expires_from_now(std::chrono::seconds(3));
290 timer_.async_wait(bind_executor(
291 strand_,
292 std::bind(
295 std::placeholders::_1)));
296 stream_.async_handshake(
297 stream_type::server,
298 bind_executor(
299 strand_,
300 std::bind(
303 std::placeholders::_1)));
304 }
305
306 void
307 fail(std::string const& what, error_code ec)
308 {
309 if (socket_.is_open())
310 {
311 if (ec != boost::asio::error::operation_aborted)
312 test_.log << "[server] " << what << ": " << ec.message()
313 << std::endl;
314 socket_.close();
315 timer_.cancel();
316 }
317 }
318
319 void
321 {
322 if (ec == boost::asio::error::operation_aborted)
323 return;
324 if (ec)
325 return fail("timer", ec);
326 test_.log << "[server] timeout" << std::endl;
327 socket_.close();
328 }
329
330 void
332 {
333 if (ec)
334 return fail("handshake", ec);
335#if 1
336 boost::asio::async_read_until(
337 stream_,
338 buf_,
339 "\n",
340 bind_executor(
341 strand_,
342 std::bind(
345 std::placeholders::_1,
346 std::placeholders::_2)));
347#else
348 close();
349#endif
350 }
351
352 void
353 on_read(error_code ec, std::size_t bytes_transferred)
354 {
355 if (ec == boost::asio::error::eof)
356 {
357 server_.test_.log << "[server] read: EOF" << std::endl;
358 return stream_.async_shutdown(bind_executor(
359 strand_,
360 std::bind(
363 std::placeholders::_1)));
364 }
365 if (ec)
366 return fail("read", ec);
367
368 buf_.commit(bytes_transferred);
369 buf_.consume(bytes_transferred);
370 write(buf_, "BYE\n");
371 boost::asio::async_write(
372 stream_,
373 buf_.data(),
374 bind_executor(
375 strand_,
376 std::bind(
379 std::placeholders::_1,
380 std::placeholders::_2)));
381 }
382
383 void
384 on_write(error_code ec, std::size_t bytes_transferred)
385 {
386 buf_.consume(bytes_transferred);
387 if (ec)
388 return fail("write", ec);
389 stream_.async_shutdown(bind_executor(
390 strand_,
391 std::bind(
394 std::placeholders::_1)));
395 }
396
397 void
399 {
400 if (ec)
401 return fail("shutdown", ec);
402 socket_.close();
403 timer_.cancel();
404 }
405 };
406
407 public:
408 explicit Server(short_read_test& test) : test_(test)
409 {
410 auto const p = std::make_shared<Acceptor>(*this);
411 add(p);
412 p->run();
413 }
414
416 {
417 close();
418 wait();
419 }
420
421 endpoint_type const&
422 endpoint() const
423 {
424 return endpoint_;
425 }
426 };
427
428 //--------------------------------------------------------------------------
429 class Client : public Base
430
431 {
432 private:
434
436 {
443 boost::asio::streambuf buf_;
445
446 Connection(Client& client, endpoint_type const& ep)
447 : Child(client)
448 , client_(client)
454 , ep_(ep)
455 {
456 }
457
458 void
459 close() override
460 {
461 if (!strand_.running_in_this_thread())
462 return post(
463 strand_,
465 if (socket_.is_open())
466 {
467 socket_.close();
468 timer_.cancel();
469 }
470 }
471
472 void
474 {
475 timer_.expires_from_now(std::chrono::seconds(3));
476 timer_.async_wait(bind_executor(
477 strand_,
478 std::bind(
481 std::placeholders::_1)));
482 socket_.async_connect(
483 ep,
484 bind_executor(
485 strand_,
486 std::bind(
489 std::placeholders::_1)));
490 }
491
492 void
493 fail(std::string const& what, error_code ec)
494 {
495 if (socket_.is_open())
496 {
497 if (ec != boost::asio::error::operation_aborted)
498 test_.log << "[client] " << what << ": " << ec.message()
499 << std::endl;
500 socket_.close();
501 timer_.cancel();
502 }
503 }
504
505 void
507 {
508 if (ec == boost::asio::error::operation_aborted)
509 return;
510 if (ec)
511 return fail("timer", ec);
512 test_.log << "[client] timeout";
513 socket_.close();
514 }
515
516 void
518 {
519 if (ec)
520 return fail("connect", ec);
521 stream_.async_handshake(
522 stream_type::client,
523 bind_executor(
524 strand_,
525 std::bind(
528 std::placeholders::_1)));
529 }
530
531 void
533 {
534 if (ec)
535 return fail("handshake", ec);
536 write(buf_, "HELLO\n");
537
538#if 1
539 boost::asio::async_write(
540 stream_,
541 buf_.data(),
542 bind_executor(
543 strand_,
544 std::bind(
547 std::placeholders::_1,
548 std::placeholders::_2)));
549#else
550 stream_.async_shutdown(bind_executor(
551 strand_,
552 std::bind(
555 std::placeholders::_1)));
556#endif
557 }
558
559 void
560 on_write(error_code ec, std::size_t bytes_transferred)
561 {
562 buf_.consume(bytes_transferred);
563 if (ec)
564 return fail("write", ec);
565#if 1
566 boost::asio::async_read_until(
567 stream_,
568 buf_,
569 "\n",
570 bind_executor(
571 strand_,
572 std::bind(
575 std::placeholders::_1,
576 std::placeholders::_2)));
577#else
578 stream_.async_shutdown(bind_executor(
579 strand_,
580 std::bind(
583 std::placeholders::_1)));
584#endif
585 }
586
587 void
588 on_read(error_code ec, std::size_t bytes_transferred)
589 {
590 if (ec)
591 return fail("read", ec);
592 buf_.commit(bytes_transferred);
593 stream_.async_shutdown(bind_executor(
594 strand_,
595 std::bind(
598 std::placeholders::_1)));
599 }
600
601 void
603 {
604 if (ec)
605 return fail("shutdown", ec);
606 socket_.close();
607 timer_.cancel();
608 }
609 };
610
611 public:
612 Client(short_read_test& test, endpoint_type const& ep) : test_(test)
613 {
614 auto const p = std::make_shared<Connection>(*this, ep);
615 add(p);
616 p->run(ep);
617 }
618
620 {
621 close();
622 wait();
623 }
624 };
625
626public:
628 : work_(io_context_.get_executor())
629 , thread_(std::thread([this]() {
630 beast::setCurrentThreadName("io_context");
631 this->io_context_.run();
632 }))
634 {
635 }
636
638 {
639 work_.reset();
640 thread_.join();
641 }
642
643 void
644 run() override
645 {
646 Server s(*this);
647 Client c(*this, s.endpoint());
648 c.wait();
649 pass();
650 }
651};
652
653BEAST_DEFINE_TESTSUITE(short_read, overlay, ripple);
654
655} // 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::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)