From 2914ec166d7cb5b0e049a2cb62e0f7e01b3c3860 Mon Sep 17 00:00:00 2001 From: Nikita Nikolaenko <6870920@gmail.com> Date: Wed, 1 Dec 2021 15:41:15 +0300 Subject: [PATCH 1/7] Add support for asio::io_context inside EventLoop --- eventuals/event-loop.cc | 45 +++++++++++++++++++++++++++++++++-------- eventuals/event-loop.h | 23 +++++++++++++++++++-- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/eventuals/event-loop.cc b/eventuals/event-loop.cc index 23c9b4f5..f23e3227 100644 --- a/eventuals/event-loop.cc +++ b/eventuals/event-loop.cc @@ -148,7 +148,26 @@ void EventLoop::ConstructDefaultAndRunForeverDetached() { ConstructDefault(); auto thread = std::thread([]() { - EventLoop::Default().RunForever(); + while (true) { + EventLoop::Default().running_ = true; + + EventLoop::Default().io_context().restart(); + EventLoop::Default().io_context().poll(); + + // NOTE: callbacks running in the asio::io_context + // are not considered to be running in the libuv loop. + EventLoop::Default().in_event_loop_ = true; + + // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on + // I/O. + uv_run(&EventLoop::Default().loop_, UV_RUN_NOWAIT); + + EventLoop::Default().running_ = false; + EventLoop::Default().in_event_loop_ = false; + } + + // We should never get out of run. + LOG(FATAL) << "unreachable"; }); thread.detach(); @@ -274,15 +293,25 @@ EventLoop::~EventLoop() { //////////////////////////////////////////////////////////////////////// void EventLoop::RunForever() { - in_event_loop_ = true; - running_ = true; + while (true) { + running_ = true; + + io_context().restart(); + io_context().poll(); + + // NOTE: callbacks running in the asio::io_context + // are not considered to be running in the libuv loop. + in_event_loop_ = true; - // NOTE: we'll truly run forever because handles like 'async_' will - // keep the loop alive forever. - uv_run(&loop_, UV_RUN_DEFAULT); + // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on + // I/O. + uv_run(&EventLoop::Default().loop_, UV_RUN_NOWAIT); + + running_ = false; + in_event_loop_ = false; + } - running_ = false; - in_event_loop_ = false; + LOG(FATAL) << "unreachable"; } //////////////////////////////////////////////////////////////////////// diff --git a/eventuals/event-loop.h b/eventuals/event-loop.h index 1c009dde..e4d2c346 100644 --- a/eventuals/event-loop.h +++ b/eventuals/event-loop.h @@ -11,6 +11,7 @@ #include #include +#include "asio.hpp" #include "eventuals/callback.h" #include "eventuals/closure.h" #include "eventuals/lazy.h" @@ -471,9 +472,15 @@ class EventLoop final : public Scheduler { void RunUntil(std::future& future) { auto status = std::future_status::ready; do { - in_event_loop_ = true; running_ = true; + io_context().restart(); + io_context().poll(); + + // NOTE: callbacks running in the asio::io_context + // are not considered to be running in the libuv loop. + in_event_loop_ = true; + // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on // I/O. uv_run(&loop_, UV_RUN_NOWAIT); @@ -487,9 +494,15 @@ class EventLoop final : public Scheduler { void RunWhileWaiters() { do { - in_event_loop_ = true; running_ = true; + io_context().restart(); + io_context().poll(); + + // NOTE: callbacks running in the asio::io_context + // are not considered to be running in the libuv loop. + in_event_loop_ = true; + // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on // I/O. uv_run(&loop_, UV_RUN_NOWAIT); @@ -533,6 +546,10 @@ class EventLoop final : public Scheduler { return &loop_; } + asio::io_context& io_context() { + return io_context_; + } + Clock& clock() { return clock_; } @@ -1049,6 +1066,8 @@ class EventLoop final : public Scheduler { uv_check_t check_ = {}; uv_async_t async_ = {}; + asio::io_context io_context_; + std::atomic running_ = false; static inline thread_local bool in_event_loop_ = false; From ac782c01b5b130884890ac830f10d1f8b1b31f22 Mon Sep 17 00:00:00 2001 From: Nikita Nikolaenko <6870920@gmail.com> Date: Sat, 18 Jun 2022 22:13:52 +0200 Subject: [PATCH 2/7] Implement TCP Api --- eventuals/BUILD.bazel | 8 + eventuals/tcp-acceptor.h | 427 ++++++ eventuals/tcp-base.h | 372 +++++ eventuals/tcp-socket.h | 304 +++++ eventuals/tcp-ssl-context-builder.h | 1213 +++++++++++++++++ eventuals/tcp-ssl-context.h | 130 ++ eventuals/tcp-ssl-socket.h | 457 +++++++ eventuals/tcp-ssl.h | 6 + eventuals/tcp.h | 5 + test/BUILD.bazel | 24 +- test/tcp/BUILD.bazel | 103 ++ test/tcp/ipv6/tcp-acceptor-accept.cc | 351 +++++ test/tcp/ipv6/tcp-acceptor-bind.cc | 182 +++ test/tcp/ipv6/tcp-acceptor-listen.cc | 170 +++ test/tcp/ipv6/tcp-acceptor-open-close.cc | 160 +++ test/tcp/ipv6/tcp-socket-bind.cc | 289 ++++ test/tcp/ipv6/tcp-socket-connect-winapi.cc | 143 ++ test/tcp/ipv6/tcp-socket-connect.cc | 418 ++++++ test/tcp/ipv6/tcp-socket-open-close.cc | 154 +++ .../ipv6/tcp-socket-send-receive-winapi.cc | 265 ++++ test/tcp/ipv6/tcp-socket-send-receive.cc | 587 ++++++++ test/tcp/ipv6/tcp-socket-shutdown.cc | 695 ++++++++++ test/tcp/ipv6/tcp.h | 13 + test/tcp/ssl/tcp-socket-handshake.cc | 528 +++++++ test/tcp/ssl/tcp-socket-send-receive.cc | 890 ++++++++++++ test/tcp/ssl/tcp.cc | 69 + test/tcp/ssl/tcp.h | 28 + test/tcp/tcp-acceptor-accept.cc | 294 ++++ test/tcp/tcp-acceptor-bind.cc | 182 +++ test/tcp/tcp-acceptor-listen.cc | 170 +++ test/tcp/tcp-acceptor-open-close.cc | 160 +++ test/tcp/tcp-socket-bind.cc | 287 ++++ test/tcp/tcp-socket-connect-winapi.cc | 143 ++ test/tcp/tcp-socket-connect.cc | 408 ++++++ test/tcp/tcp-socket-open-close.cc | 154 +++ test/tcp/tcp-socket-send-receive-winapi.cc | 265 ++++ test/tcp/tcp-socket-send-receive.cc | 578 ++++++++ test/tcp/tcp-socket-shutdown.cc | 681 +++++++++ test/tcp/tcp.h | 23 + 39 files changed, 11335 insertions(+), 1 deletion(-) create mode 100644 eventuals/tcp-acceptor.h create mode 100644 eventuals/tcp-base.h create mode 100644 eventuals/tcp-socket.h create mode 100644 eventuals/tcp-ssl-context-builder.h create mode 100644 eventuals/tcp-ssl-context.h create mode 100644 eventuals/tcp-ssl-socket.h create mode 100644 eventuals/tcp-ssl.h create mode 100644 eventuals/tcp.h create mode 100644 test/tcp/BUILD.bazel create mode 100644 test/tcp/ipv6/tcp-acceptor-accept.cc create mode 100644 test/tcp/ipv6/tcp-acceptor-bind.cc create mode 100644 test/tcp/ipv6/tcp-acceptor-listen.cc create mode 100644 test/tcp/ipv6/tcp-acceptor-open-close.cc create mode 100644 test/tcp/ipv6/tcp-socket-bind.cc create mode 100644 test/tcp/ipv6/tcp-socket-connect-winapi.cc create mode 100644 test/tcp/ipv6/tcp-socket-connect.cc create mode 100644 test/tcp/ipv6/tcp-socket-open-close.cc create mode 100644 test/tcp/ipv6/tcp-socket-send-receive-winapi.cc create mode 100644 test/tcp/ipv6/tcp-socket-send-receive.cc create mode 100644 test/tcp/ipv6/tcp-socket-shutdown.cc create mode 100644 test/tcp/ipv6/tcp.h create mode 100644 test/tcp/ssl/tcp-socket-handshake.cc create mode 100644 test/tcp/ssl/tcp-socket-send-receive.cc create mode 100644 test/tcp/ssl/tcp.cc create mode 100644 test/tcp/ssl/tcp.h create mode 100644 test/tcp/tcp-acceptor-accept.cc create mode 100644 test/tcp/tcp-acceptor-bind.cc create mode 100644 test/tcp/tcp-acceptor-listen.cc create mode 100644 test/tcp/tcp-acceptor-open-close.cc create mode 100644 test/tcp/tcp-socket-bind.cc create mode 100644 test/tcp/tcp-socket-connect-winapi.cc create mode 100644 test/tcp/tcp-socket-connect.cc create mode 100644 test/tcp/tcp-socket-open-close.cc create mode 100644 test/tcp/tcp-socket-send-receive-winapi.cc create mode 100644 test/tcp/tcp-socket-send-receive.cc create mode 100644 test/tcp/tcp-socket-shutdown.cc create mode 100644 test/tcp/tcp.h diff --git a/eventuals/BUILD.bazel b/eventuals/BUILD.bazel index 25620d13..42bdc08f 100644 --- a/eventuals/BUILD.bazel +++ b/eventuals/BUILD.bazel @@ -78,6 +78,14 @@ cc_library( "event-loop.h", "filesystem.h", "signal.h", + "tcp.h", + "tcp-acceptor.h", + "tcp-base.h", + "tcp-socket.h", + "tcp-ssl.h", + "tcp-ssl-context.h", + "tcp-ssl-context-builder.h", + "tcp-ssl-socket.h", "timer.h", ], copts = copts(), diff --git a/eventuals/tcp-acceptor.h b/eventuals/tcp-acceptor.h new file mode 100644 index 00000000..86433cd0 --- /dev/null +++ b/eventuals/tcp-acceptor.h @@ -0,0 +1,427 @@ +#pragma once + +#include +#include + +#include "tcp-base.h" + +//////////////////////////////////////////////////////////////////////// + +namespace eventuals { +namespace ip { +namespace tcp { + +//////////////////////////////////////////////////////////////////////// + +class Acceptor final { + public: + Acceptor(Protocol protocol, EventLoop& loop = EventLoop::Default()) + : loop_(loop), + protocol_(protocol), + acceptor_(loop.io_context()) { + // NOTE: Compiling on MacOS produces weird + // 'unused-private-field' warnings, hence why this + // piece of code is needed. +#ifdef __MACH__ + (void) is_listening_; + (void) protocol_; +#endif + } + + ~Acceptor() { + CHECK(!IsOpen()) << "Close the acceptor before destructing"; + } + + Acceptor(const Acceptor& that) = delete; + // TODO(folming): implement move. + Acceptor(Acceptor&& that) = delete; + + Acceptor& operator=(const Acceptor& that) = delete; + // TODO(folming): implement move. + Acceptor& operator=(Acceptor&& that) = delete; + + [[nodiscard]] auto Open(); + + [[nodiscard]] auto Bind(std::string&& ip, uint16_t port); + + [[nodiscard]] auto Listen(const int backlog); + + [[nodiscard]] auto Accept(SocketBase& socket); + + [[nodiscard]] auto Close(); + + bool IsOpen() { + return is_open_.load(); + } + + uint16_t ListeningPort() { + auto port_optional = port_.load(); + CHECK(port_optional.has_value()) << "Listen must be called beforehand"; + return *port_optional; + } + + std::string ListeningIP() { + std::lock_guard lk(ip_mutex_); + CHECK(ip_.has_value()) << "Listen must be called beforehand"; + return *ip_; + } + + private: + asio::ip::tcp::acceptor& acceptor_handle() { + return acceptor_; + } + + asio::io_context& io_context() { + return loop_.io_context(); + } + + EventLoop& loop_; + // asio::ip::tcp::acceptor's methods are not thread-safe, + // so we store the state in an atomic variables by ourselves. + std::atomic is_open_ = false; + std::atomic> port_; + + // std::string type is not trivially copyable, + // so we can't use std::atomic here. + // Instead, we use std::mutex to access and modify this variable. + std::mutex ip_mutex_; + std::optional ip_; + + // This variable is only accessed or modified inside event loop, + // so we don't need std::atomic wrapper. + bool is_listening_ = false; + + Protocol protocol_; + + asio::ip::tcp::acceptor acceptor_; +}; + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Acceptor::Open() { + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(this) + .start([](auto& acceptor, auto& k, Interrupt::Handler& handler) { + asio::post( + acceptor->io_context(), + [&]() { + if (handler.interrupt().Triggered()) { + k.Stop(); + return; + } + + if (acceptor->IsOpen()) { + k.Fail(std::runtime_error("Acceptor is already opened")); + return; + } + + asio::error_code error; + + switch (acceptor->protocol_) { + case Protocol::IPV4: + acceptor->acceptor_handle().open( + asio::ip::tcp::v4(), + error); + break; + case Protocol::IPV6: + acceptor->acceptor_handle().open( + asio::ip::tcp::v6(), + error); + break; + } + + if (!error) { + acceptor->is_open_.store(true); + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Acceptor::Bind(std::string&& ip, uint16_t port) { + struct Data { + Acceptor* acceptor; + std::string ip; + uint16_t port; + }; + + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(Data{this, std::move(ip), port}) + .start([](auto& data, auto& k, Interrupt::Handler& handler) { + asio::post( + data.acceptor->io_context(), + [&]() { + if (handler.interrupt().Triggered()) { + k.Stop(); + return; + } + + if (!data.acceptor->IsOpen()) { + k.Fail(std::runtime_error("Acceptor is closed")); + return; + } + + if (data.acceptor->is_listening_) { + k.Fail( + std::runtime_error( + "Bind call is forbidden " + "while acceptor is listening")); + return; + } + + asio::error_code error; + asio::ip::tcp::endpoint endpoint; + + switch (data.acceptor->protocol_) { + case Protocol::IPV4: + endpoint = asio::ip::tcp::endpoint( + asio::ip::make_address_v4(data.ip, error), + data.port); + break; + case Protocol::IPV6: + endpoint = asio::ip::tcp::endpoint( + asio::ip::make_address_v6(data.ip, error), + data.port); + break; + } + + + if (error) { + k.Fail(std::runtime_error(error.message())); + return; + } + + data.acceptor->acceptor_handle().bind(endpoint, error); + + if (!error) { + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Acceptor::Listen(const int backlog) { + struct Data { + Acceptor* acceptor; + const int backlog; + }; + + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(Data{this, backlog}) + .start([](auto& data, auto& k, Interrupt::Handler& handler) { + asio::post( + data.acceptor->io_context(), + [&]() { + if (handler.interrupt().Triggered()) { + k.Stop(); + return; + } + + if (!data.acceptor->IsOpen()) { + k.Fail(std::runtime_error("Acceptor is closed")); + return; + } + + if (data.acceptor->is_listening_) { + k.Fail( + std::runtime_error( + "Acceptor is already listening")); + return; + } + + asio::error_code error; + + data.acceptor->acceptor_handle().listen( + data.backlog, + error); + + if (!error) { + data.acceptor->is_listening_ = true; + data.acceptor->port_.store(data.acceptor->acceptor_handle() + .local_endpoint() + .port()); + std::lock_guard lk(data.acceptor->ip_mutex_); + data.acceptor->ip_ = data.acceptor->acceptor_handle() + .local_endpoint() + .address() + .to_string(); + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Acceptor::Accept(SocketBase& socket) { + struct Data { + Acceptor* acceptor; + SocketBase* socket; + + // Used for interrupt handler due to + // static_assert(sizeof(Handler) <= SIZE) (callback.h(59,5)) + // requirement for handler.Install(). + void* k = nullptr; + + bool started = false; + bool completed = false; + }; + + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(Data{this, &socket}) + .start([](auto& data, auto& k, Interrupt::Handler& handler) { + using K = std::decay_t; + data.k = &k; + + handler.Install([&data]() { + asio::post(data.acceptor->io_context(), [&]() { + K& k = *static_cast(data.k); + + if (!data.started) { + data.completed = true; + k.Stop(); + return; + } else if (!data.completed) { + data.completed = true; + asio::error_code error; + data.acceptor->acceptor_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + }); + + asio::post( + data.acceptor->io_context(), + [&]() { + if (!data.completed) { + if (handler.interrupt().Triggered()) { + data.completed = true; + k.Stop(); + return; + } + + CHECK(!data.started); + data.started = true; + + if (!data.acceptor->IsOpen()) { + data.completed = true; + k.Fail(std::runtime_error("Acceptor is closed")); + return; + } + + if (!data.acceptor->is_listening_) { + data.completed = true; + k.Fail(std::runtime_error("Acceptor is not listening")); + return; + } + + if (data.socket->IsOpen()) { + data.completed = true; + k.Fail( + std::runtime_error( + "Passed socket is not closed")); + return; + } + + if (data.acceptor->protocol_ != data.socket->protocol_) { + data.completed = true; + k.Fail( + std::runtime_error( + "Passed socket's protocol " + "is different from acceptor's")); + return; + } + + data.acceptor->acceptor_handle().async_accept( + data.socket->socket_handle(), + [&](const asio::error_code& error) { + if (!data.completed) { + data.completed = true; + + if (!error) { + data.socket->is_open_.store(true); + data.socket->is_connected_ = true; + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Acceptor::Close() { + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(this) + .start([](auto& acceptor, auto& k, Interrupt::Handler& handler) { + asio::post( + acceptor->io_context(), + [&]() { + if (handler.interrupt().Triggered()) { + k.Stop(); + return; + } + + if (!acceptor->IsOpen()) { + k.Fail(std::runtime_error("Acceptor is closed")); + return; + } + + asio::error_code error; + acceptor->acceptor_handle().close(error); + + if (!error) { + acceptor->is_open_.store(false); + acceptor->is_listening_ = false; + acceptor->port_.store(std::nullopt); + std::lock_guard lk(acceptor->ip_mutex_); + acceptor->ip_ = std::nullopt; + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +} // namespace tcp +} // namespace ip +} // namespace eventuals + +//////////////////////////////////////////////////////////////////////// diff --git a/eventuals/tcp-base.h b/eventuals/tcp-base.h new file mode 100644 index 00000000..a2dbeba3 --- /dev/null +++ b/eventuals/tcp-base.h @@ -0,0 +1,372 @@ +#pragma once + +#include + +#include "asio.hpp" +#include "eventuals/event-loop.h" +#include "stout/borrowed_ptr.h" + +//////////////////////////////////////////////////////////////////////// + +namespace eventuals { +namespace ip { +namespace tcp { + +//////////////////////////////////////////////////////////////////////// + +enum class Protocol { + IPV4, + IPV6 +}; + +//////////////////////////////////////////////////////////////////////// + +// Different ways a socket may be shutdown. +enum class ShutdownType { + // Shutdown the send side of the socket. + SEND = asio::ip::tcp::socket::shutdown_type::shutdown_send, + + // Shutdown the receive side of the socket. + RECEIVE = asio::ip::tcp::socket::shutdown_type::shutdown_receive, + + // Shutdown both send and receive on the socket. + BOTH = asio::ip::tcp::socket::shutdown_type::shutdown_both, +}; + +//////////////////////////////////////////////////////////////////////// + +class SocketBase { + public: + SocketBase() = delete; + + virtual ~SocketBase() { + CHECK(!IsOpen()) << "Close the socket before destructing"; + } + + [[nodiscard]] auto Open(); + + [[nodiscard]] auto Bind(std::string&& ip, uint16_t port); + + [[nodiscard]] auto Connect(std::string&& ip, uint16_t port); + + [[nodiscard]] auto Shutdown(ShutdownType shutdown_type); + + // NOTE: It's not possible to implement a virtual auto + // method, so we have to omit a Close() method here + // and make its implementations in Socket and ssl::Socket. + // TODO: Implement Close() method here instead of being + // two different implementations for Socket and ssl::Socket. + + bool IsOpen() { + return is_open_.load(); + } + + protected: + SocketBase(Protocol protocol, EventLoop& loop = EventLoop::Default()) + : loop_(loop), + protocol_(protocol) {} + + virtual asio::ip::tcp::socket& socket_handle() = 0; + + asio::io_context& io_context() { + return loop_.io_context(); + } + + EventLoop& loop_; + // asio::ip::tcp::socket::is_open() method is not thread-safe, + // so we store the state in an atomic variable by ourselves. + std::atomic is_open_ = false; + + // This variable is only accessed or modified inside event loop, + // so we don't need std::atomic wrapper. + bool is_connected_ = false; + + Protocol protocol_; + + friend class Acceptor; +}; + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto SocketBase::Open() { + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(this) + .start([](auto& socket, auto& k, Interrupt::Handler& handler) { + asio::post( + socket->io_context(), + [&]() { + if (handler.interrupt().Triggered()) { + k.Stop(); + return; + } + + if (socket->IsOpen()) { + k.Fail(std::runtime_error("Socket is already opened")); + return; + } + + asio::error_code error; + + switch (socket->protocol_) { + case Protocol::IPV4: + socket->socket_handle().open( + asio::ip::tcp::v4(), + error); + break; + case Protocol::IPV6: + socket->socket_handle().open( + asio::ip::tcp::v6(), + error); + break; + } + + if (!error) { + socket->is_open_.store(true); + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto SocketBase::Bind(std::string&& ip, uint16_t port) { + struct Data { + SocketBase* socket; + std::string ip; + uint16_t port; + }; + + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(Data{this, std::move(ip), port}) + .start([](auto& data, auto& k, Interrupt::Handler& handler) { + asio::post( + data.socket->io_context(), + [&]() { + if (handler.interrupt().Triggered()) { + k.Stop(); + return; + } + + if (!data.socket->socket_handle().is_open()) { + k.Fail(std::runtime_error("Socket is closed")); + return; + } + + if (data.socket->is_connected_) { + k.Fail( + std::runtime_error( + "Bind call is forbidden " + "while socket is connected")); + return; + } + + asio::error_code error; + asio::ip::tcp::endpoint endpoint; + + switch (data.socket->protocol_) { + case Protocol::IPV4: + endpoint = asio::ip::tcp::endpoint( + asio::ip::make_address_v4(data.ip, error), + data.port); + break; + case Protocol::IPV6: + endpoint = asio::ip::tcp::endpoint( + asio::ip::make_address_v6(data.ip, error), + data.port); + break; + } + + + if (error) { + k.Fail(std::runtime_error(error.message())); + return; + } + + data.socket->socket_handle().bind(endpoint, error); + + if (!error) { + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto SocketBase::Connect( + std::string&& ip, + uint16_t port) { + struct Data { + SocketBase* socket; + std::string ip; + uint16_t port; + + // Used for interrupt handler due to + // static_assert(sizeof(Handler) <= SIZE) (callback.h(59,5)) + // requirement for handler.Install(). + void* k = nullptr; + + bool started = false; + bool completed = false; + }; + + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(Data{this, std::move(ip), port}) + .start([](auto& data, auto& k, Interrupt::Handler& handler) { + using K = std::decay_t; + data.k = &k; + + handler.Install([&data]() { + asio::post(data.socket->io_context(), [&]() { + K& k = *static_cast(data.k); + + if (!data.started) { + data.completed = true; + k.Stop(); + } else if (!data.completed) { + data.completed = true; + asio::error_code error; + data.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + }); + + asio::post( + data.socket->io_context(), + [&]() { + if (!data.completed) { + if (handler.interrupt().Triggered()) { + data.completed = true; + k.Stop(); + return; + } + + CHECK(!data.started); + data.started = true; + + if (!data.socket->socket_handle().is_open()) { + data.completed = true; + k.Fail(std::runtime_error("Socket is closed")); + return; + } + + if (data.socket->is_connected_) { + data.completed = true; + k.Fail( + std::runtime_error( + "Socket is already connected")); + return; + } + + asio::error_code error; + asio::ip::tcp::endpoint endpoint; + + switch (data.socket->protocol_) { + case Protocol::IPV4: + endpoint = asio::ip::tcp::endpoint( + asio::ip::make_address_v4(data.ip, error), + data.port); + break; + case Protocol::IPV6: + endpoint = asio::ip::tcp::endpoint( + asio::ip::make_address_v6(data.ip, error), + data.port); + break; + } + + if (error) { + data.completed = true; + k.Fail(std::runtime_error(error.message())); + return; + } + + data.socket->socket_handle().async_connect( + endpoint, + [&](const asio::error_code& error) { + if (!data.completed) { + data.completed = true; + + if (!error) { + data.socket->is_connected_ = true; + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto SocketBase::Shutdown(ShutdownType shutdown_type) { + struct Data { + SocketBase* socket; + ShutdownType shutdown_type; + }; + + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(Data{this, shutdown_type}) + .start([](auto& data, auto& k, Interrupt::Handler& handler) { + asio::post( + data.socket->io_context(), + [&]() { + if (handler.interrupt().Triggered()) { + k.Stop(); + return; + } + + if (!data.socket->IsOpen()) { + k.Fail(std::runtime_error("Socket is closed")); + return; + } + + asio::error_code error; + + data.socket->socket_handle().shutdown( + static_cast< + asio::socket_base::shutdown_type>( + data.shutdown_type), + error); + + if (!error) { + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +} // namespace tcp +} // namespace ip +} // namespace eventuals + +//////////////////////////////////////////////////////////////////////// diff --git a/eventuals/tcp-socket.h b/eventuals/tcp-socket.h new file mode 100644 index 00000000..14dd4901 --- /dev/null +++ b/eventuals/tcp-socket.h @@ -0,0 +1,304 @@ +#pragma once + +#include "tcp-base.h" + +//////////////////////////////////////////////////////////////////////// + +namespace eventuals { +namespace ip { +namespace tcp { + +//////////////////////////////////////////////////////////////////////// + +class Socket final : public SocketBase { + public: + Socket(Protocol protocol, EventLoop& loop = EventLoop::Default()) + : SocketBase(protocol, loop), + socket_(loop.io_context()) {} + + Socket(const Socket& that) = delete; + // TODO(folming): implement move. + // It should be possible since asio provides move + // operations on their socket implementation. + Socket(Socket&& that) = delete; + + Socket& operator=(const Socket& that) = delete; + // TODO(folming): implement move. + // It should be possible since asio provides move + // operations on their socket implementation. + Socket& operator=(Socket&& that) = delete; + + [[nodiscard]] auto Receive( + void* destination, + size_t destination_size, + size_t bytes_to_read); + + [[nodiscard]] auto Send(const void* source, size_t source_size); + + [[nodiscard]] auto Close(); + + private: + asio::ip::tcp::socket& socket_handle() override { + return socket_; + } + + asio::ip::tcp::socket socket_; +}; + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Socket::Receive( + void* destination, + size_t destination_size, + size_t bytes_to_read) { + struct Data { + Socket* socket; + void* destination; + size_t destination_size; + size_t bytes_to_read; + + // Used for interrupt handler due to + // static_assert(sizeof(Handler) <= SIZE) (callback.h(59,5)) + // requirement for handler.Install(). + void* k = nullptr; + + bool started = false; + bool completed = false; + }; + + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(Data{this, destination, destination_size, bytes_to_read}) + .start([](auto& data, auto& k, Interrupt::Handler& handler) { + using K = std::decay_t; + data.k = &k; + + handler.Install([&data]() { + asio::post(data.socket->io_context(), [&]() { + K& k = *static_cast(data.k); + + if (!data.started) { + data.completed = true; + k.Stop(); + } else if (!data.completed) { + data.completed = true; + asio::error_code error; + data.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + }); + + asio::post( + data.socket->io_context(), + [&]() { + if (!data.completed) { + if (handler.interrupt().Triggered()) { + data.completed = true; + k.Stop(); + return; + } + + CHECK(!data.started); + data.started = true; + + if (!data.socket->IsOpen()) { + data.completed = true; + k.Fail(std::runtime_error("Socket is closed")); + return; + } + + if (!data.socket->is_connected_) { + data.completed = true; + k.Fail(std::runtime_error("Socket is not connected")); + return; + } + + // Do not allow to read more than destination_size. + data.bytes_to_read = std::min( + data.bytes_to_read, + data.destination_size); + + // Do not call async_read() if there're 0 bytes to be read. + if (data.bytes_to_read == 0) { + data.completed = true; + k.Start(0); + return; + } + + // Start receiving. + // Will only succeed after the supplied buffer is full. + asio::async_read( + data.socket->socket_handle(), + asio::buffer(data.destination, data.bytes_to_read), + [&](const asio::error_code& error, + size_t bytes_transferred) { + if (!data.completed) { + data.completed = true; + + if (!error) { + k.Start(bytes_transferred); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Socket::Send( + const void* source, + size_t source_size) { + struct Data { + Socket* socket; + const void* source; + size_t source_size; + + // Used for interrupt handler due to + // static_assert(sizeof(Handler) <= SIZE) (callback.h(59,5)) + // requirement for handler.Install(). + void* k = nullptr; + + bool started = false; + bool completed = false; + }; + + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(Data{this, source, source_size}) + .start([](auto& data, auto& k, Interrupt::Handler& handler) { + using K = std::decay_t; + data.k = &k; + + handler.Install([&data]() { + asio::post(data.socket->io_context(), [&]() { + K& k = *static_cast(data.k); + + if (!data.started) { + data.completed = true; + k.Stop(); + } else if (!data.completed) { + data.completed = true; + asio::error_code error; + data.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + }); + + asio::post( + data.socket->io_context(), + [&]() { + if (!data.completed) { + if (handler.interrupt().Triggered()) { + data.completed = true; + k.Stop(); + return; + } + + CHECK(!data.started); + data.started = true; + + if (!data.socket->IsOpen()) { + data.completed = true; + k.Fail(std::runtime_error("Socket is closed")); + return; + } + + if (!data.socket->is_connected_) { + data.completed = true; + k.Fail(std::runtime_error("Socket is not connected")); + return; + } + + // Do not call async_write() + // if there're 0 bytes to be sent. + if (data.source_size == 0) { + data.completed = true; + k.Start(0); + return; + } + + // Will only succeed after + // writing all of the data to socket. + asio::async_write( + data.socket->socket_handle(), + asio::buffer(data.source, data.source_size), + [&](const asio::error_code& error, + size_t bytes_transferred) { + if (!data.completed) { + data.completed = true; + + if (!error) { + k.Start(bytes_transferred); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Socket::Close() { + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(this) + .start([](auto& socket, auto& k, Interrupt::Handler& handler) { + asio::post( + socket->io_context(), + [&]() { + if (handler.interrupt().Triggered()) { + k.Stop(); + return; + } + + if (!socket->IsOpen()) { + k.Fail(std::runtime_error("Socket is closed")); + return; + } + + asio::error_code error; + socket->socket_handle().close(error); + + if (!error) { + socket->is_connected_ = false; + socket->is_open_.store(false); + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +} // namespace tcp +} // namespace ip +} // namespace eventuals + +//////////////////////////////////////////////////////////////////////// diff --git a/eventuals/tcp-ssl-context-builder.h b/eventuals/tcp-ssl-context-builder.h new file mode 100644 index 00000000..8eb39ae9 --- /dev/null +++ b/eventuals/tcp-ssl-context-builder.h @@ -0,0 +1,1213 @@ +#pragma once + +#include "eventuals/builder.h" + +//////////////////////////////////////////////////////////////////////// + +namespace eventuals { +namespace ip { +namespace tcp { +namespace ssl { + +//////////////////////////////////////////////////////////////////////// + +enum class SSLVerifyMode { + // No verification. + NONE = asio::ssl::verify_none, + + // Verify the peer. + PEER = asio::ssl::verify_peer, + + // Fail verification if the peer has no certificate. + // Ignored unless PEER is set. + FAIL_IF_NO_PEER_CERT = asio::ssl::verify_fail_if_no_peer_cert, + + // Do not request client certificate on renegotiation. + // Ignored unless PEER is set. + CLIENT_ONCE = asio::ssl::verify_client_once +}; + +// Bitmask type for peer verification. +using SSLVerifyModes = long; + +//////////////////////////////////////////////////////////////////////// + +enum class SSLOption { + // Implement various bug workarounds. + DEFAULT_WORKAROUNDS = asio::ssl::context_base::default_workarounds, + + // Disable compression. Compression is disabled by default. + NO_COMPRESSION = asio::ssl::context_base::no_compression, + + // Disable SSL v2. + NO_SSLv2 = asio::ssl::context_base::no_sslv2, + + // Disable SSL v3. + NO_SSLv3 = asio::ssl::context_base::no_sslv3, + + // Disable TLS v1. + NO_TLSv1 = asio::ssl::context_base::no_tlsv1, + + // Disable TLS v1.1. + NO_TLSv1_1 = asio::ssl::context_base::no_tlsv1_1, + + // Disable TLS v1.2. + NO_TLSv1_2 = asio::ssl::context_base::no_tlsv1_2, + + // Disable TLS v1.3. + NO_TLSv1_3 = asio::ssl::context_base::no_tlsv1_3, + + // Always create a new key when using tmp_dh parameters. + SINGLE_DH_USE = asio::ssl::context_base::single_dh_use +}; + +// Bitmask type for SSL options. +using SSLOptions = long; + +//////////////////////////////////////////////////////////////////////// + +// File format types. +enum class FileFormat { + PEM = asio::ssl::context_base::file_format::pem, + ASN1 = asio::ssl::context_base::file_format::asn1 +}; + +//////////////////////////////////////////////////////////////////////// + +// Purpose of PEM password. +enum class PasswordPurpose { + // The password is needed for reading/decryption. + FOR_READING = asio::ssl::context::password_purpose::for_reading, + + // The password is needed for writing/encryption. + FOR_WRITING = asio::ssl::context::password_purpose::for_writing +}; + +//////////////////////////////////////////////////////////////////////// + +// NOTE: we can't specify the structures inside private section of +// the builder, because they will inherit template arguments, +// which will produce compilation errors. +namespace helpers { + +//////////////////////////////////////////////////////////////////////// + +struct ConstBuffer final { + const char* data; + size_t size; +}; + +struct LoadFromFile final { + std::string filename; + FileFormat file_format; +}; + +struct LoadFromMemory final { + const char* data; + size_t size; + FileFormat file_format; +}; + +using ConstBuffers = std::vector; +using VerifyPaths = std::vector; +using VerifyFiles = std::vector; + +using Certificate = LoadFromMemory; +using PrivateKey = LoadFromMemory; +using RSAPrivateKey = LoadFromMemory; + +using CertificateFile = LoadFromFile; +using PrivateKeyFile = LoadFromFile; +using RSAPrivateKeyFile = LoadFromFile; + +//////////////////////////////////////////////////////////////////////// + +} // namespace helpers + +//////////////////////////////////////////////////////////////////////// + +template < + bool has_method_ = false, + bool has_certificate_authority_ = false, + bool has_verify_path_ = false, + bool has_verify_file_ = false, + bool has_default_verify_paths_ = false, + bool has_ssl_options_ = false, + bool has_password_callback_ = false, + bool has_verify_callback_ = false, + bool has_verify_depth_ = false, + bool has_verify_modes_ = false, + bool has_certificate_ = false, + bool has_certificate_file_ = false, + bool has_certificate_chain_ = false, + bool has_certificate_chain_file_ = false, + bool has_private_key_ = false, + bool has_private_key_file_ = false, + bool has_rsa_private_key_ = false, + bool has_rsa_private_key_file_ = false, + bool has_tmp_dh_ = false, + bool has_tmp_dh_file_ = false> +class SSLContext::_Builder final : public builder::Builder { + public: + ~_Builder() override = default; + + public: + auto ssl_version( + SSLVersion ssl_version) && { + static_assert(!has_method_, "Duplicate 'ssl_version'"); + return Construct<_Builder>( + ssl_version_.Set(ssl_version), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + // This function is used to add one trusted certification authority + // from a memory buffer. + auto certificate_authority( + const char* source, + size_t source_size) && { + certificate_authorities_->emplace_back(source, source_size); + return Construct<_Builder>( + std::move(ssl_version_), + certificate_authorities_.Set( + std::move(certificate_authorities_).value()), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to specify the name of a directory containing + // certification authority certificates. + // Each file in the directory must contain a single certificate. + // The files must be named using the subject name's hash + // and an extension of ".0". + auto verify_path( + std::string&& path) && { + verify_paths_->emplace_back(std::move(path)); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + verify_paths_.Set(std::move(verify_paths_).value()), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to load the certificates for one or more trusted + // certification authorities from a file. + auto verify_file( + std::string&& filename) && { + verify_files_->emplace_back(std::move(filename)); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + verify_files_.Set(std::move(verify_files_).value()), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function specifies that the context should use the default, + // system-dependent directories for locating certification + // authority certificates. + auto default_verify_paths() && { + static_assert( + !has_default_verify_paths_, + "Duplicate 'set_default_verify_paths'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + default_verify_paths_.Set(true), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function may be used to + // configure the SSL options used by the context. + auto options( + SSLOptions ssl_options) && { + static_assert(!has_ssl_options_, "Duplicate 'set_options'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + ssl_options_.Set(ssl_options), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to specify a callback function + // to obtain password information about an encrypted key in PEM format. + // + // The function signature of the handler must be: + // + // std::string password_callback( + // std::size_t max_length, // The maximum size for a password. + // password_purpose purpose // Whether password is for reading or writing. + // ); + // + // The return value of the callback is a string containing the password. + template + auto password_callback( + PasswordCallback&& callback) && { + static_assert( + !has_password_callback_, + "Duplicate 'set_password_callback'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + password_callback_.Set(callback), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to specify a callback function + // that will be called by the implementation when it + // needs to verify a peer certificate. + // + // The function signature of the handler must be: + // + // bool verify_callback( + // bool preverified, // True if the certificate passed pre-verification. + // verify_context& ctx // The peer certificate and other context. + // ); + // + // The return value of the callback is true + // if the certificate has passed verification, false otherwise. + template + auto verify_callback( + VerifyCallback&& callback) && { + static_assert(!has_verify_callback_, "Duplicate 'set_verify_callback'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + verify_callback_.Set(callback), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function may be used to configure + // the maximum verification depth allowed by the context. + auto verify_depth( + int depth) && { + static_assert(!has_verify_depth_, "Duplicate 'set_verify_depth'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + verify_depth_.Set(depth), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function may be used to configure the peer verification mode used + // by the context. + auto verify_modes( + SSLVerifyModes ssl_verify_mode) && { + static_assert(!has_verify_modes_, "Duplicate 'set_verify_modes'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + verify_modes_.Set(ssl_verify_mode), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to load + // a certificate into the context from a buffer. + auto certificate( + const char* source, + size_t source_size, + FileFormat file_format) && { + static_assert(!has_certificate_, "Duplicate 'use_certificate'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + certificate_.Set( + source, + source_size, + file_format), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to load a certificate into the context from a file. + auto certificate_file( + std::string&& filename, + FileFormat file_format) && { + static_assert(!has_certificate_file_, "Duplicate 'use_certificate_file'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + certificate_file_.Set( + std::move(filename), + file_format), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to load a certificate chain into the context + // from a buffer. + auto certificate_chain( + const char* source, + size_t source_size) && { + static_assert( + !has_certificate_chain_, + "Duplicate 'use_certificate_chain'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + certificate_chain_.Set(source, source_size), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to load a certificate chain into the context + // from a file. + // The file must use the PEM format. + auto certificate_chain_file( + std::string&& filename) && { + static_assert( + !has_certificate_chain_file_, + "Duplicate 'use_certificate_chain_file'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + certificate_chain_file_.Set(std::move(filename)), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to load a + // private key into the context from a buffer. + auto private_key( + const char* source, + size_t source_size, + FileFormat file_format) && { + static_assert(!has_private_key_, "Duplicate 'use_private_key'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + private_key_.Set( + source, + source_size, + file_format), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to load a private key into the context from a file. + auto private_key_file( + std::string&& filename, + FileFormat file_format) && { + static_assert(!has_private_key_file_, "Duplicate 'use_private_key_file'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + private_key_file_.Set( + std::move(filename), + file_format), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to load an RSA private key into the context + // from a buffer. + auto rsa_private_key( + const char* source, + size_t source_size, + FileFormat file_format) && { + static_assert(!has_rsa_private_key_, "Duplicate 'use_rsa_private_key'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + rsa_private_key_.Set( + source, + source_size, + file_format), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to load an RSA private key into the context + // from a file. + auto rsa_private_key_file( + std::string&& filename, + FileFormat file_format) && { + static_assert( + !has_rsa_private_key_file_, + "Duplicate 'use_rsa_private_key_file'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + rsa_private_key_file_.Set( + std::move(filename), + file_format), + std::move(tmp_dh_), + std::move(tmp_dh_file_)); + } + + + // This function is used to load Diffie-Hellman parameters into the context + // from a buffer. + // The buffer must use the PEM format. + auto tmp_dh( + const char* source, + size_t source_size) && { + static_assert(!has_tmp_dh_, "Duplicate 'use_tmp_dh'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + tmp_dh_.Set(source, source_size), + std::move(tmp_dh_file_)); + } + + + // This function is used to load Diffie-Hellman parameters into the context + // from a file. + // The file must use the PEM format. + auto tmp_dh_file( + std::string&& filename) && { + static_assert(!has_tmp_dh_file_, "Duplicate 'use_tmp_dh_file'"); + return Construct<_Builder>( + std::move(ssl_version_), + std::move(certificate_authorities_), + std::move(verify_paths_), + std::move(verify_files_), + std::move(default_verify_paths_), + std::move(ssl_options_), + std::move(password_callback_), + std::move(verify_callback_), + std::move(verify_depth_), + std::move(verify_modes_), + std::move(certificate_), + std::move(certificate_file_), + std::move(certificate_chain_), + std::move(certificate_chain_file_), + std::move(private_key_), + std::move(private_key_file_), + std::move(rsa_private_key_), + std::move(rsa_private_key_file_), + std::move(tmp_dh_), + tmp_dh_file_.Set(std::move(filename))); + } + + SSLContext Build() && { + static_assert(has_method_, "Missing 'ssl_version'"); + + SSLContext ssl_context(ssl_version_.value()); + asio::error_code error; + + for (auto& buffer : certificate_authorities_.value()) { + ssl_context.ssl_context_handle().add_certificate_authority( + asio::const_buffer(buffer.data, buffer.size), + error); + CHECK(!error) << "Could not add certificate authority due to error: " + << error.message(); + } + + for (std::string& path : verify_paths_.value()) { + ssl_context.ssl_context_handle().add_verify_path( + path, + error); + CHECK(!error) << "Could not add verify path due to error: " + << error.message(); + } + + for (std::string& path : verify_files_.value()) { + ssl_context.ssl_context_handle().load_verify_file( + path, + error); + CHECK(!error) << "Could not load verify file due to error: " + << error.message(); + } + + if constexpr (has_default_verify_paths_) { + ssl_context.ssl_context_handle().set_default_verify_paths( + error); + CHECK(!error) << "Could not set default verify paths due to error: " + << error.message(); + } + + if constexpr (has_ssl_options_) { + ssl_context.ssl_context_handle().set_options( + ssl_options_.value(), + error); + CHECK(!error) << "Could not set SSL options due to error: " + << error.message(); + } + + if constexpr (has_password_callback_) { + ssl_context.ssl_context_handle().set_password_callback( + [password_callback = std::move(password_callback_.value())]( + std::size_t max_length, + asio::ssl::context_base::password_purpose purpose) { + return password_callback( + max_length, + static_cast(purpose)); + }, + error); + CHECK(!error) << "Could not set password callback due to error: " + << error.message(); + } + + if constexpr (has_verify_callback_) { + ssl_context.ssl_context_handle().set_verify_callback( + [verify_callback = std::move(verify_callback_.value())]( + bool preverified, + asio::ssl::verify_context& ctx) { + return verify_callback(preverified, ctx); + }, + error); + CHECK(!error) << "Could not set verify callback due to error: " + << error.message(); + } + + if constexpr (has_verify_depth_) { + ssl_context.ssl_context_handle().set_verify_depth( + verify_depth_.value(), + error); + CHECK(!error) << "Could not set verify depth due to error: " + << error.message(); + } + + if constexpr (has_verify_modes_) { + ssl_context.ssl_context_handle().set_verify_mode( + verify_modes_.value(), + error); + CHECK(!error) << "Could not set verify mode due to error: " + << error.message(); + } + + if constexpr (has_certificate_) { + ssl_context.ssl_context_handle().use_certificate( + asio::const_buffer( + certificate_->data, + certificate_->size), + static_cast< + asio::ssl::context_base::file_format>( + certificate_->file_format), + error); + CHECK(!error) << "Could not use certificate due to error: " + << error.message(); + } + + if constexpr (has_certificate_file_) { + ssl_context.ssl_context_handle().use_certificate_file( + certificate_file_->filename, + static_cast< + asio::ssl::context_base::file_format>( + certificate_file_->file_format), + error); + CHECK(!error) << "Could not use certificate file due to error: " + << error.message(); + } + + if constexpr (has_certificate_chain_) { + ssl_context.ssl_context_handle().use_certificate_chain( + asio::const_buffer( + certificate_chain_->data, + certificate_chain_->size), + error); + CHECK(!error) << "Could not use certificate chain due to error: " + << error.message(); + } + + if constexpr (has_certificate_chain_file_) { + ssl_context.ssl_context_handle().use_certificate_chain_file( + certificate_chain_file_.value(), + error); + CHECK(!error) << "Could not use certificate chain file due to error: " + << error.message(); + } + + if constexpr (has_private_key_) { + ssl_context.ssl_context_handle().use_private_key( + asio::const_buffer( + private_key_->data, + private_key_->size), + static_cast< + asio::ssl::context_base::file_format>( + private_key_->file_format), + error); + CHECK(!error) << "Could not use private key due to error: " + << error.message(); + } + + if constexpr (has_private_key_file_) { + ssl_context.ssl_context_handle().use_private_key_file( + rsa_private_key_file_->filename, + static_cast< + asio::ssl::context_base::file_format>( + rsa_private_key_file_->file_format), + error); + CHECK(!error) << "Could not use private key file due to error: " + << error.message(); + } + + if constexpr (has_rsa_private_key_) { + ssl_context.ssl_context_handle().use_rsa_private_key( + asio::const_buffer( + rsa_private_key_->data, + rsa_private_key_->size), + static_cast< + asio::ssl::context_base::file_format>( + rsa_private_key_->file_format), + error); + CHECK(!error) << "Could not use RSA private key due to error: " + << error.message(); + } + + if constexpr (has_rsa_private_key_file_) { + ssl_context.ssl_context_handle().use_rsa_private_key_file( + rsa_private_key_file_->filename, + static_cast< + asio::ssl::context_base::file_format>( + rsa_private_key_file_->file_format), + error); + CHECK(!error) << "Could not use RSA private key file due to error: " + << error.message(); + } + + if constexpr (has_tmp_dh_) { + ssl_context.ssl_context_handle().use_tmp_dh( + asio::const_buffer(tmp_dh_->data, tmp_dh_->size), + error); + CHECK(!error) << "Could not use DH parameters due to error: " + << error.message(); + } + + if constexpr (has_tmp_dh_file_) { + ssl_context.ssl_context_handle().use_tmp_dh_file( + tmp_dh_file_.value(), + error); + CHECK(!error) << "Could not use DH parameters file due to error: " + << error.message(); + } + + return ssl_context; + } + + private: + _Builder() = default; + + _Builder( + builder::Field< + SSLVersion, + has_method_> ssl_version, + builder::RepeatedField< + helpers::ConstBuffers, + has_certificate_authority_> certificate_authorities, + builder::RepeatedField< + helpers::VerifyPaths, + has_verify_path_> verify_paths, + builder::RepeatedField< + helpers::VerifyFiles, + has_verify_file_> verify_files, + builder::Field< + bool, + has_default_verify_paths_> default_verify_paths, + builder::Field< + SSLOptions, + has_ssl_options_> ssl_options, + builder::Field< + std::function, + has_password_callback_> password_callback, + builder::Field< + std::function, + has_verify_callback_> verify_callback, + builder::Field< + int, + has_verify_depth_> verify_depth, + builder::Field< + SSLVerifyModes, + has_verify_modes_> verify_modes, + builder::Field< + helpers::Certificate, + has_certificate_> certificate, + builder::Field< + helpers::CertificateFile, + has_certificate_file_> certificate_file, + builder::Field< + helpers::ConstBuffer, + has_certificate_chain_> certificate_chain, + builder::Field< + std::string, + has_certificate_chain_file_> certificate_chain_file, + builder::Field< + helpers::PrivateKey, + has_private_key_> private_key, + builder::Field< + helpers::PrivateKeyFile, + has_private_key_file_> private_key_file, + builder::Field< + helpers::RSAPrivateKey, + has_rsa_private_key_> rsa_private_key, + builder::Field< + helpers::RSAPrivateKeyFile, + has_rsa_private_key_file_> rsa_private_key_file, + builder::Field< + helpers::ConstBuffer, + has_tmp_dh_> tmp_dh, + builder::Field< + std::string, + has_tmp_dh_file_> tmp_dh_file) + : ssl_version_(std::move(ssl_version)), + certificate_authorities_(std::move(certificate_authorities)), + verify_paths_(std::move(verify_paths)), + verify_files_(std::move(verify_files)), + default_verify_paths_(std::move(default_verify_paths)), + ssl_options_(std::move(ssl_options)), + password_callback_(std::move(password_callback)), + verify_callback_(std::move(verify_callback)), + verify_depth_(std::move(verify_depth)), + verify_modes_(std::move(verify_modes)), + certificate_(std::move(certificate)), + certificate_file_(std::move(certificate_file)), + certificate_chain_(std::move(certificate_chain)), + certificate_chain_file_(std::move(certificate_chain_file)), + private_key_(std::move(private_key)), + private_key_file_(std::move(private_key_file)), + rsa_private_key_(std::move(rsa_private_key)), + rsa_private_key_file_(std::move(rsa_private_key_file)), + tmp_dh_(std::move(tmp_dh)), + tmp_dh_file_(std::move(tmp_dh_file)) {} + + builder::Field< + SSLVersion, + has_method_> + ssl_version_; + + builder::RepeatedField< + helpers::ConstBuffers, + has_certificate_authority_> + certificate_authorities_ = helpers::ConstBuffers{}; + + builder::RepeatedField< + helpers::VerifyPaths, + has_verify_path_> + verify_paths_ = helpers::VerifyPaths{}; + + builder::RepeatedField< + helpers::VerifyFiles, + has_verify_file_> + verify_files_ = helpers::VerifyFiles{}; + + builder::Field< + bool, + has_default_verify_paths_> + default_verify_paths_; + + builder::Field< + SSLOptions, + has_ssl_options_> + ssl_options_; + + builder::Field< + std::function, + has_password_callback_> + password_callback_; + + builder::Field< + std::function, + has_verify_callback_> + verify_callback_; + + builder::Field< + int, + has_verify_depth_> + verify_depth_; + + builder::Field< + SSLVerifyModes, + has_verify_modes_> + verify_modes_; + + builder::Field< + helpers::Certificate, + has_certificate_> + certificate_; + + builder::Field< + helpers::CertificateFile, + has_certificate_file_> + certificate_file_; + + builder::Field< + helpers::ConstBuffer, + has_certificate_chain_> + certificate_chain_; + + builder::Field< + std::string, + has_certificate_chain_file_> + certificate_chain_file_; + + builder::Field< + helpers::PrivateKey, + has_private_key_> + private_key_; + + builder::Field< + helpers::PrivateKeyFile, + has_private_key_file_> + private_key_file_; + + builder::Field< + helpers::RSAPrivateKey, + has_rsa_private_key_> + rsa_private_key_; + + builder::Field< + helpers::RSAPrivateKeyFile, + has_rsa_private_key_file_> + rsa_private_key_file_; + + builder::Field< + helpers::ConstBuffer, + has_tmp_dh_> + tmp_dh_; + + builder::Field< + std::string, + has_tmp_dh_file_> + tmp_dh_file_; + + friend class builder::Builder; + friend class SSLContext; +}; + +//////////////////////////////////////////////////////////////////////// + +inline auto SSLContext::Builder() { + return SSLContext::_Builder< + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false>(); +} + +//////////////////////////////////////////////////////////////////////// + +} // namespace ssl +} // namespace tcp +} // namespace ip +} // namespace eventuals + +//////////////////////////////////////////////////////////////////////// diff --git a/eventuals/tcp-ssl-context.h b/eventuals/tcp-ssl-context.h new file mode 100644 index 00000000..5a901a4f --- /dev/null +++ b/eventuals/tcp-ssl-context.h @@ -0,0 +1,130 @@ +#pragma once + +#include "asio/ssl.hpp" + +//////////////////////////////////////////////////////////////////////// + +namespace eventuals { +namespace ip { +namespace tcp { +namespace ssl { + +//////////////////////////////////////////////////////////////////////// + +// Different methods supported by a context. +enum class SSLVersion { + // SSL version 2. + SSLv2 = asio::ssl::context::sslv2, + SSLv2_CLIENT = asio::ssl::context::sslv2_client, + SSLv2_SERVER = asio::ssl::context::sslv2_server, + + // SSL version 3. + SSLv3 = asio::ssl::context::sslv3, + SSLv3_CLIENT = asio::ssl::context::sslv3_client, + SSLv3_SERVER = asio::ssl::context::sslv3_server, + + // SSL/TLS. + // NOTE: the comment above was taken from the ASIO reference. + // It's probably wrong to assume that these methods support TLS. + SSLv23 = asio::ssl::context::sslv23, + SSLv23_CLIENT = asio::ssl::context::sslv23_client, + SSLv23_SERVER = asio::ssl::context::sslv23_server, + + // TLS. + TLS = asio::ssl::context::tls, + TLS_CLIENT = asio::ssl::context::tls_client, + TLS_SERVER = asio::ssl::context::tls_server, + + // TLS version 1. + TLSv1 = asio::ssl::context::tlsv1, + TLSv1_CLIENT = asio::ssl::context::tlsv1_client, + TLSv1_SERVER = asio::ssl::context::tlsv1_server, + + // TLS version 1.1. + TLSv1_1 = asio::ssl::context::tlsv11, + TLSv1_1_CLIENT = asio::ssl::context::tlsv11_client, + TLSv1_1_SERVER = asio::ssl::context::tlsv11_server, + + // TLS version 1.2. + TLSv1_2 = asio::ssl::context::tlsv12, + TLSv1_2_CLIENT = asio::ssl::context::tlsv12_client, + TLSv1_2_SERVER = asio::ssl::context::tlsv12_server, + + // TLS version 1.3. + TLSv1_3 = asio::ssl::context::tlsv13, + TLSv1_3_CLIENT = asio::ssl::context::tlsv13_client, + TLSv1_3_SERVER = asio::ssl::context::tlsv13_server, +}; + +//////////////////////////////////////////////////////////////////////// + +class SSLContext final { + public: + SSLContext(const SSLContext& that) = delete; + SSLContext(SSLContext&& that) + : context_(std::move(that.context_)), + moved_out_(that.moved_out_) { + that.moved_out_ = true; + } + + SSLContext& operator=(const SSLContext& that) = delete; + SSLContext& operator=(SSLContext&& that) { + context_ = std::move(that.context_); + moved_out_ = that.moved_out_; + that.moved_out_ = true; + + return *this; + } + + // Constructs a new ssl::SSLContext "builder" with the default + // undefined values. + static auto Builder(); + + private: + SSLContext(SSLVersion ssl_version) + : context_(static_cast(ssl_version)) {} + + asio::ssl::context& ssl_context_handle() { + CHECK(!moved_out_) << "Using already moved out SSLContext"; + return context_; + } + + asio::ssl::context context_; + bool moved_out_ = false; + + template < + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool, + bool> + class _Builder; + + friend class Socket; +}; + +//////////////////////////////////////////////////////////////////////// + +} // namespace ssl +} // namespace tcp +} // namespace ip +} // namespace eventuals + +//////////////////////////////////////////////////////////////////////// + +#include "tcp-ssl-context-builder.h" diff --git a/eventuals/tcp-ssl-socket.h b/eventuals/tcp-ssl-socket.h new file mode 100644 index 00000000..c236c114 --- /dev/null +++ b/eventuals/tcp-ssl-socket.h @@ -0,0 +1,457 @@ +#pragma once + +#include "eventuals/event-loop.h" +#include "eventuals/tcp-base.h" +#include "eventuals/tcp-ssl-context.h" + +//////////////////////////////////////////////////////////////////////// + +namespace eventuals { +namespace ip { +namespace tcp { +namespace ssl { + +//////////////////////////////////////////////////////////////////////// + +// Different handshake types. +enum class HandshakeType { + // Perform handshaking as a client. + CLIENT = asio::ssl::stream::handshake_type::client, + + // Perform handshaking as a server. + SERVER = asio::ssl::stream::handshake_type::server +}; + +//////////////////////////////////////////////////////////////////////// + +class Socket final : public SocketBase { + public: + Socket( + SSLContext& context, + Protocol protocol, + EventLoop& loop = EventLoop::Default()) + : SocketBase(protocol, loop), + stream_(loop.io_context(), context.ssl_context_handle()) { + // NOTE: Compiling on MacOS produces weird + // 'unused-private-field' warnings, hence why this + // piece of code is needed. +#ifdef __MACH__ + (void) completed_handshake_; +#endif + } + + Socket(const Socket& that) = delete; + // TODO(folming): implement move. + // It should be possible since asio provides move + // operations on their socket implementation. + Socket(Socket&& that) = delete; + + Socket& operator=(const Socket& that) = delete; + // TODO(folming): implement move. + // It should be possible since asio provides move + // operations on their socket implementation. + Socket& operator=(Socket&& that) = delete; + + [[nodiscard]] auto Handshake(HandshakeType handshake_type); + + [[nodiscard]] auto Receive( + void* destination, + size_t destination_size, + size_t bytes_to_read); + + [[nodiscard]] auto Send(const void* source, size_t source_size); + + [[nodiscard]] auto Close(); + + private: + asio::ip::tcp::socket& socket_handle() override { + return stream_.next_layer(); + } + + asio::ssl::stream& stream_handle() { + return stream_; + } + + // This variable is only accessed or modified inside event loop, + // so we don't need std::atomic wrapper. + bool completed_handshake_ = false; + + asio::ssl::stream stream_; +}; + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Socket::Handshake(HandshakeType handshake_type) { + struct Data { + Socket* socket; + HandshakeType handshake_type; + + // Used for interrupt handler due to + // static_assert(sizeof(Handler) <= SIZE) (callback.h(59,5)) + // requirement for handler.Install(). + void* k = nullptr; + + bool started = false; + bool completed = false; + }; + + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(Data{this, handshake_type}) + .start([](auto& data, auto& k, Interrupt::Handler& handler) { + using K = std::decay_t; + data.k = &k; + + handler.Install([&data]() { + asio::post(data.socket->io_context(), [&]() { + K& k = *static_cast(data.k); + + if (!data.started) { + data.completed = true; + k.Stop(); + } else if (!data.completed) { + data.completed = true; + asio::error_code error; + data.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + }); + + asio::post( + data.socket->io_context(), + [&]() { + if (!data.completed) { + if (handler.interrupt().Triggered()) { + data.completed = true; + k.Stop(); + return; + } + + CHECK(!data.started); + data.started = true; + + if (!data.socket->IsOpen()) { + data.completed = true; + k.Fail(std::runtime_error("Socket is closed")); + return; + } + + if (!data.socket->is_connected_) { + data.completed = true; + k.Fail(std::runtime_error("Socket is not connected")); + return; + } + + if (data.socket->completed_handshake_) { + data.completed = true; + k.Fail( + std::runtime_error( + "Handshake was already completed")); + return; + } + + data.socket->stream_handle().async_handshake( + static_cast< + asio::ssl::stream< + asio::ip::tcp::socket>::handshake_type>( + data.handshake_type), + [&](const asio::error_code& error) { + if (!data.completed) { + data.completed = true; + + if (!error) { + data.socket->completed_handshake_ = true; + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Socket::Receive( + void* destination, + size_t destination_size, + size_t bytes_to_read) { + struct Data { + Socket* socket; + void* destination; + size_t destination_size; + size_t bytes_to_read; + + // Used for interrupt handler due to + // static_assert(sizeof(Handler) <= SIZE) (callback.h(59,5)) + // requirement for handler.Install(). + void* k = nullptr; + + bool started = false; + bool completed = false; + }; + + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(Data{this, destination, destination_size, bytes_to_read}) + .start([](auto& data, auto& k, Interrupt::Handler& handler) { + using K = std::decay_t; + data.k = &k; + + handler.Install([&data]() { + asio::post(data.socket->io_context(), [&]() { + K& k = *static_cast(data.k); + + if (!data.started) { + data.completed = true; + k.Stop(); + } else if (!data.completed) { + data.completed = true; + asio::error_code error; + data.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + }); + + asio::post( + data.socket->io_context(), + [&]() { + if (!data.completed) { + if (handler.interrupt().Triggered()) { + data.completed = true; + k.Stop(); + return; + } + + CHECK(!data.started); + data.started = true; + + if (!data.socket->IsOpen()) { + data.completed = true; + k.Fail(std::runtime_error("Socket is closed")); + return; + } + + if (!data.socket->is_connected_) { + data.completed = true; + k.Fail(std::runtime_error("Socket is not connected")); + return; + } + + if (!data.socket->completed_handshake_) { + data.completed = true; + k.Fail( + std::runtime_error( + "Must Handshake before trying to Receive")); + return; + } + + // Do not allow to read more than destination_size. + data.bytes_to_read = std::min( + data.bytes_to_read, + data.destination_size); + + // Do not call async_read() if there're 0 bytes to be read. + if (data.bytes_to_read == 0) { + data.completed = true; + k.Start(0); + return; + } + + // Start receiving. + // Will only succeed after the supplied buffer is full. + asio::async_read( + data.socket->stream_handle(), + asio::buffer(data.destination, data.bytes_to_read), + [&](const asio::error_code& error, + size_t bytes_transferred) { + if (!data.completed) { + data.completed = true; + + if (!error) { + k.Start(bytes_transferred); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Socket::Send( + const void* source, + size_t source_size) { + struct Data { + Socket* socket; + const void* source; + size_t source_size; + + // Used for interrupt handler due to + // static_assert(sizeof(Handler) <= SIZE) (callback.h(59,5)) + // requirement for handler.Install(). + void* k = nullptr; + + bool started = false; + bool completed = false; + }; + + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(Data{this, source, source_size}) + .start([](auto& data, auto& k, Interrupt::Handler& handler) { + using K = std::decay_t; + data.k = &k; + + handler.Install([&data]() { + asio::post(data.socket->io_context(), [&]() { + K& k = *static_cast(data.k); + + if (!data.started) { + data.completed = true; + k.Stop(); + } else if (!data.completed) { + data.completed = true; + asio::error_code error; + data.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + }); + + asio::post( + data.socket->io_context(), + [&]() { + if (!data.completed) { + if (handler.interrupt().Triggered()) { + data.completed = true; + k.Stop(); + return; + } + + CHECK(!data.started); + data.started = true; + + if (!data.socket->IsOpen()) { + data.completed = true; + k.Fail(std::runtime_error("Socket is closed")); + return; + } + + if (!data.socket->is_connected_) { + data.completed = true; + k.Fail(std::runtime_error("Socket is not connected")); + return; + } + + if (!data.socket->completed_handshake_) { + data.completed = true; + k.Fail( + std::runtime_error( + "Must Handshake before trying to Send")); + return; + } + + // Do not call async_write() + // if there're 0 bytes to be sent. + if (data.source_size == 0) { + data.completed = true; + k.Start(0); + return; + } + + // Will only succeed after + // writing all of the data to socket. + asio::async_write( + data.socket->stream_handle(), + asio::buffer(data.source, data.source_size), + [&](const asio::error_code& error, + size_t bytes_transferred) { + if (!data.completed) { + data.completed = true; + + if (!error) { + k.Start(bytes_transferred); + } else { + k.Fail(std::runtime_error(error.message())); + } + } + }); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +[[nodiscard]] inline auto Socket::Close() { + return loop_.Schedule( + Eventual() + .interruptible() + .raises() + .context(this) + .start([](auto& socket, auto& k, Interrupt::Handler& handler) { + asio::post( + socket->io_context(), + [&]() { + if (handler.interrupt().Triggered()) { + k.Stop(); + return; + } + + if (!socket->IsOpen()) { + k.Fail(std::runtime_error("Socket is closed")); + return; + } + + asio::error_code error; + socket->socket_handle().close(error); + + if (!error) { + socket->is_connected_ = false; + socket->completed_handshake_ = false; + socket->is_open_.store(false); + k.Start(); + } else { + k.Fail(std::runtime_error(error.message())); + } + }); + })); +} + +//////////////////////////////////////////////////////////////////////// + +} // namespace ssl +} // namespace tcp +} // namespace ip +} // namespace eventuals + +//////////////////////////////////////////////////////////////////////// diff --git a/eventuals/tcp-ssl.h b/eventuals/tcp-ssl.h new file mode 100644 index 00000000..20c165a1 --- /dev/null +++ b/eventuals/tcp-ssl.h @@ -0,0 +1,6 @@ +#pragma once + +#include "eventuals/tcp-acceptor.h" +#include "eventuals/tcp-base.h" +#include "eventuals/tcp-ssl-context.h" +#include "eventuals/tcp-ssl-socket.h" diff --git a/eventuals/tcp.h b/eventuals/tcp.h new file mode 100644 index 00000000..f13d7368 --- /dev/null +++ b/eventuals/tcp.h @@ -0,0 +1,5 @@ +#pragma once + +#include "eventuals/tcp-acceptor.h" +#include "eventuals/tcp-base.h" +#include "eventuals/tcp-socket.h" diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 2ff2e286..397b5cef 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -26,6 +26,18 @@ cc_library( ], ) +cc_library( + name = "event-loop-test", + hdrs = [ + "event-loop-test.h", + ], + visibility = ["//visibility:public"], + deps = [ + "//eventuals", + "@com_github_google_googletest//:gtest", + ], +) + cc_library( name = "http-mock-server", testonly = True, @@ -39,6 +51,15 @@ cc_library( ], ) +cc_library( + name = "tcp", + deps = [ + "//test/tcp:base", + "//test/tcp:ipv6", + "//test/tcp:ssl", + ], +) + cc_test( name = "eventuals", srcs = [ @@ -50,7 +71,6 @@ cc_test( "conditional.cc", "dns-resolver.cc", "do-all.cc", - "event-loop-test.h", "eventual.cc", "executor.cc", "expected.cc", @@ -86,9 +106,11 @@ cc_test( # Use a faster implementation of malloc (and show that tests pass with it). malloc = malloc(), deps = [ + ":event-loop-test", ":generate-test-task-name", ":http-mock-server", ":promisify-for-test", + ":tcp", "//eventuals", "//test/concurrent", "@com_github_google_googletest//:gtest_main", diff --git a/test/tcp/BUILD.bazel b/test/tcp/BUILD.bazel new file mode 100644 index 00000000..62ef2c05 --- /dev/null +++ b/test/tcp/BUILD.bazel @@ -0,0 +1,103 @@ +# We build "tcp" tests in a separate target to significantly speed up +# linking on platforms which prefer shared libraries (e.g., macos). + +# We've split all of the tests into separate files so they can be +# compiled in parallel which is significantly faster than having +# all of the tests in a single file. + +load("@rules_cc//cc:defs.bzl", "cc_library") +load("//bazel:copts.bzl", "copts") + +cc_library( + name = "base", + srcs = [ + "tcp.h", + "tcp-acceptor-accept.cc", + "tcp-acceptor-bind.cc", + "tcp-acceptor-listen.cc", + "tcp-acceptor-open-close.cc", + "tcp-socket-bind.cc", + "tcp-socket-connect.cc", + "tcp-socket-connect-winapi.cc", + "tcp-socket-open-close.cc", + "tcp-socket-send-receive.cc", + "tcp-socket-send-receive-winapi.cc", + "tcp-socket-shutdown.cc", + ], + copts = copts(), + visibility = ["//test:__pkg__"], + deps = [ + "//eventuals", + "//test:event-loop-test", + "//test:promisify-for-test", + "@com_github_google_googletest//:gtest", + "@com_github_google_googletest//:gtest_main", + ], + # Setting it to False or leaving it absent will prevent concurrent + # tests from running on Windows. + alwayslink = True, +) + +# Currently, these tests are separate because our Linux CI +# doesn't support IPv6. +# TODO(folming): Refactor tests using INSTANTIATE_TEST_SUITE_P. +cc_library( + name = "ipv6", + srcs = [ + "ipv6/tcp.h", + "ipv6/tcp-acceptor-accept.cc", + "ipv6/tcp-acceptor-bind.cc", + "ipv6/tcp-acceptor-listen.cc", + "ipv6/tcp-acceptor-open-close.cc", + "ipv6/tcp-socket-bind.cc", + "ipv6/tcp-socket-connect.cc", + "ipv6/tcp-socket-connect-winapi.cc", + "ipv6/tcp-socket-open-close.cc", + "ipv6/tcp-socket-send-receive.cc", + "ipv6/tcp-socket-send-receive-winapi.cc", + "ipv6/tcp-socket-shutdown.cc", + "tcp.h", + ], + copts = copts(), + # Currently we are running the Linux CI using Dazel, + # which uses Docker, which doesn't support IPv6. + target_compatible_with = select({ + "@platforms//os:macos": [], + "@platforms//os:windows": [], + "//conditions:default": ["@platforms//:incompatible"], + }), + visibility = ["//test:__pkg__"], + deps = [ + "//eventuals", + "//test:event-loop-test", + "//test:promisify-for-test", + "@com_github_google_googletest//:gtest", + "@com_github_google_googletest//:gtest_main", + ], + # Setting it to False or leaving it absent will prevent concurrent + # tests from running on Windows. + alwayslink = True, +) + +cc_library( + name = "ssl", + srcs = [ + "ssl/tcp.cc", + "ssl/tcp.h", + "ssl/tcp-socket-handshake.cc", + "ssl/tcp-socket-send-receive.cc", + "tcp.h", + ], + copts = copts(), + visibility = ["//test:__pkg__"], + deps = [ + "//eventuals", + "//test:event-loop-test", + "//test:promisify-for-test", + "@com_github_google_googletest//:gtest", + "@com_github_google_googletest//:gtest_main", + ], + # Setting it to False or leaving it absent will prevent concurrent + # tests from running on Windows. + alwayslink = True, +) diff --git a/test/tcp/ipv6/tcp-acceptor-accept.cc b/test/tcp/ipv6/tcp-acceptor-accept.cc new file mode 100644 index 00000000..dcd15989 --- /dev/null +++ b/test/tcp/ipv6/tcp-acceptor-accept.cc @@ -0,0 +1,351 @@ +// NOTE: here we don't test the successful Accept operation. +// Connect tests should cover this. + +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPIPV6Test, AcceptorAcceptClosedFail) { + Acceptor acceptor(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Acceptor is closed"))); +} + + +TEST_F(TCPIPV6Test, AcceptorAcceptNotListeningFail) { + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Accept(accepted); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Acceptor is not listening"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPIPV6Test, AcceptorAcceptPassOpenSocketArgFail) { + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> accepted.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1) + >> acceptor.Accept(accepted); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Passed socket is not closed"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close() + >> accepted.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: This is the only test which is not in the base ipv4 tests. +TEST_F(TCPIPV6Test, AcceptorAcceptPassDifferentProtocolSocketArgFail) { + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1) + >> acceptor.Accept(accepted); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage( + StrEq( + "Passed socket's protocol is different from acceptor's"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Accept operation is asynchronous. +TEST_F(TCPIPV6Test, AcceptorAcceptInterruptBeforeStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + interrupt.Trigger(); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Accept operation is asynchronous. +TEST_F(TCPIPV6Test, AcceptorAcceptInterruptAfterStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + interrupt.Trigger(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/ipv6/tcp-acceptor-bind.cc b/test/tcp/ipv6/tcp-acceptor-bind.cc new file mode 100644 index 00000000..dc3339ee --- /dev/null +++ b/test/tcp/ipv6/tcp-acceptor-bind.cc @@ -0,0 +1,182 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; + +TEST_F(TCPIPV6Test, AcceptorBindSuccess) { + Acceptor acceptor(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPIPV6Test, AcceptorBindAnyIPSuccess) { + Acceptor acceptor(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPIPV6Test::kAnyIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPIPV6Test, AcceptorBindBadIPFail) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind("::H", TCPIPV6Test::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPIPV6Test, AcceptorBindClosedFail) { + Acceptor acceptor(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Bind(TCPIPV6Test::kAnyIPV6, TCPIPV6Test::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Acceptor is closed"))); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Bind operation is not asynchronous. +TEST_F(TCPIPV6Test, AcceptorBindInterrupt) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> Then([&]() { + interrupt.Trigger(); + }) + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/ipv6/tcp-acceptor-listen.cc b/test/tcp/ipv6/tcp-acceptor-listen.cc new file mode 100644 index 00000000..3f04d413 --- /dev/null +++ b/test/tcp/ipv6/tcp-acceptor-listen.cc @@ -0,0 +1,170 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; + +TEST_F(TCPIPV6Test, AcceptorListenSuccess) { + Acceptor acceptor(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPIPV6Test::kAnyIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1) + >> acceptor.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPIPV6Test, AcceptorListenClosedFail) { + Acceptor acceptor(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Listen(1); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Acceptor is closed"))); +} + + +TEST_F(TCPIPV6Test, AcceptorListenTwiceFail) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPIPV6Test::kAnyIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1) + >> acceptor.Listen(1); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage( + StrEq( + "Acceptor is already listening"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Listen operation is not asynchronous. +TEST_F(TCPIPV6Test, AcceptorListenInterrupt) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPIPV6Test::kAnyIPV6, TCPIPV6Test::kAnyPort) + >> Then([&interrupt]() { + interrupt.Trigger(); + }) + >> acceptor.Listen(1); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/ipv6/tcp-acceptor-open-close.cc b/test/tcp/ipv6/tcp-acceptor-open-close.cc new file mode 100644 index 00000000..c6dbe454 --- /dev/null +++ b/test/tcp/ipv6/tcp-acceptor-open-close.cc @@ -0,0 +1,160 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; + +TEST_F(TCPIPV6Test, AcceptorOpenCloseSuccess) { + Acceptor acceptor(Protocol::IPV6); + + Interrupt interrupt; + + EXPECT_FALSE(acceptor.IsOpen()); + + auto e = [&]() { + return acceptor.Open() + >> Then([&acceptor]() { + EXPECT_TRUE(acceptor.IsOpen()); + }) + >> acceptor.Close() + >> Then([&acceptor]() { + EXPECT_FALSE(acceptor.IsOpen()); + }); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPIPV6Test, AcceptorCloseClosedFail) { + Acceptor acceptor(Protocol::IPV6); + + Interrupt interrupt; + + EXPECT_FALSE(acceptor.IsOpen()); + + auto e = [&]() { + return acceptor.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Acceptor is closed"))); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Open operation is not asynchronous. +TEST_F(TCPIPV6Test, AcceptorOpenInterrupt) { + Acceptor acceptor(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(acceptor.IsOpen()); + + auto e = [&]() { + return acceptor.Open(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + interrupt.Trigger(); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), StoppedException); + + EXPECT_FALSE(acceptor.IsOpen()); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Close operation is not asynchronous. +TEST_F(TCPIPV6Test, AcceptorCloseInterrupt) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(acceptor.IsOpen()); + + auto e = [&]() { + return acceptor.Open() + >> Then([&]() { + EXPECT_TRUE(acceptor.IsOpen()); + interrupt.Trigger(); + }) + >> acceptor.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), StoppedException); + + EXPECT_TRUE(acceptor.IsOpen()); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close() + >> Then([&]() { + EXPECT_FALSE(acceptor.IsOpen()); + }); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/ipv6/tcp-socket-bind.cc b/test/tcp/ipv6/tcp-socket-bind.cc new file mode 100644 index 00000000..165ad0f5 --- /dev/null +++ b/test/tcp/ipv6/tcp-socket-bind.cc @@ -0,0 +1,289 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPIPV6Test, SocketBindSuccess) { + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Open() + >> socket.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> socket.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPIPV6Test, SocketBindAnyIPSuccess) { + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Open() + >> socket.Bind(TCPIPV6Test::kAnyIPV6, TCPIPV6Test::kAnyPort) + >> socket.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPIPV6Test, SocketBindBadIPFail) { + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = socket.Open() + >> socket.Bind("::H", TCPIPV6Test::kAnyPort); + + auto [future, k] = PromisifyForTest(std::move(e)); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPIPV6Test, SocketBindClosedFail) { + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Bind(TCPIPV6Test::kAnyIPV6, TCPIPV6Test::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); +} + + +TEST_F(TCPIPV6Test, SocketBindWhileConnectedFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Bind call is forbidden " + "while socket is connected"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Bind operation is not asynchronous. +TEST_F(TCPIPV6Test, SocketBindInterrupt) { + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Open() + >> Then([&]() { + interrupt.Trigger(); + }) + >> socket.Bind(TCPIPV6Test::kAnyIPV6, TCPIPV6Test::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/ipv6/tcp-socket-connect-winapi.cc b/test/tcp/ipv6/tcp-socket-connect-winapi.cc new file mode 100644 index 00000000..81be5d36 --- /dev/null +++ b/test/tcp/ipv6/tcp-socket-connect-winapi.cc @@ -0,0 +1,143 @@ +#if defined(_WIN32) + +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPIPV6Test, SocketConnectToWinApiSocket) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV6); + + std::thread winapi_thread; + SOCKET socket_winapi = INVALID_SOCKET; + SOCKET accepted_winapi = INVALID_SOCKET; + uint16_t socket_port = 0; + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + struct sockaddr_in6 address = {0}; + address.sin6_family = AF_INET6; + address.sin6_port = htons(TCPIPV6Test::kAnyPort); + inet_pton(AF_INET6, TCPIPV6Test::kLocalHostIPV6, &address.sin6_addr); + + socket_winapi = ::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + EXPECT_NE(socket_winapi, INVALID_SOCKET) + << "Error code: " + << WSAGetLastError(); + + int error = bind( + socket_winapi, + reinterpret_cast(&address), + sizeof(address)); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + error = listen(socket_winapi, 1); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + ZeroMemory(&address, sizeof(address)); + int address_size = sizeof(address); + error = getsockname( + socket_winapi, + reinterpret_cast(&address), + &address_size); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + socket_port = ntohs(address.sin6_port); + + return socket.Open(); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to WinAPI socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + + auto e_accept = [&]() { + return Eventual() + .start([&](auto& k) { + winapi_thread = std::thread([&]() { + accepted_winapi = accept(socket_winapi, nullptr, nullptr); + EXPECT_NE(accepted_winapi, INVALID_SOCKET) + << "Error code: " + << WSAGetLastError(); + + k.Start(); + }); + }); + }; + + auto e_connect = [&]() { + return socket.Connect(TCPIPV6Test::kLocalHostIPV6, socket_port); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + + EXPECT_NO_THROW(future_connect.get()); + + winapi_thread.join(); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + int error = closesocket(socket_winapi); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + error = closesocket(accepted_winapi); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test + +#endif diff --git a/test/tcp/ipv6/tcp-socket-connect.cc b/test/tcp/ipv6/tcp-socket-connect.cc new file mode 100644 index 00000000..95595e2e --- /dev/null +++ b/test/tcp/ipv6/tcp-socket-connect.cc @@ -0,0 +1,418 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPIPV6Test, SocketConnectToAcceptorSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPIPV6Test, SocketConnectToAcceptorTwiceFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor the second time. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect_second; + + auto e_connect_second = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto [future_connect_second, k_connect_second] = + PromisifyForTest(e_connect_second()); + + k_connect_second.Register(interrupt_connect_second); + + k_connect_second.Start(); + + EventLoop::Default().RunUntil(future_connect_second); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_connect_second = &future_connect_second]() { + future_connect_second->get(); + }, + ThrowsMessage(StrEq("Socket is already connected"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPIPV6Test, SocketConnectToBadIpAddressFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return socket.Open(); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Try to connect. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + + auto e_connect = [&]() { + return socket.Connect("127.0.0.256", 8000); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + + k_connect.Register(interrupt_connect); + + k_connect.Start(); + + EventLoop::Default().RunUntil(future_connect); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future_connect.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Connect operation is asynchronous. +TEST_F(TCPIPV6Test, SocketConnectToAcceptorInterruptBeforeStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + + k_connect.Register(interrupt_connect); + + interrupt_connect.Trigger(); + + k_connect.Start(); + + EventLoop::Default().RunUntil(future_connect); + + EXPECT_THROW(future_connect.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Connect operation is asynchronous. +TEST_F(TCPIPV6Test, SocketConnectToAcceptorInterruptAfterStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + + k_connect.Register(interrupt_connect); + + k_connect.Start(); + + interrupt_connect.Trigger(); + + EventLoop::Default().RunUntil(future_connect); + + EXPECT_THROW(future_connect.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/ipv6/tcp-socket-open-close.cc b/test/tcp/ipv6/tcp-socket-open-close.cc new file mode 100644 index 00000000..7aad2a83 --- /dev/null +++ b/test/tcp/ipv6/tcp-socket-open-close.cc @@ -0,0 +1,154 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPIPV6Test, SocketOpenCloseSuccess) { + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(socket.IsOpen()); + + auto e = [&]() { + return socket.Open() + >> Then([&socket]() { + EXPECT_TRUE(socket.IsOpen()); + }) + >> socket.Close() + >> Then([&socket]() { + EXPECT_FALSE(socket.IsOpen()); + }); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPIPV6Test, SocketCloseClosedFail) { + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(socket.IsOpen()); + + auto e = [&]() { + return socket.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); +} + + +TEST_F(TCPIPV6Test, SocketOpenInterrupt) { + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(socket.IsOpen()); + + auto e = [&]() { + return socket.Open(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + interrupt.Trigger(); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), eventuals::StoppedException); + + EXPECT_FALSE(socket.IsOpen()); +} + + +TEST_F(TCPIPV6Test, SocketCloseInterrupt) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(socket.IsOpen()); + + auto e = [&]() { + return socket.Open() + >> Then([&]() { + EXPECT_TRUE(socket.IsOpen()); + interrupt.Trigger(); + }) + >> socket.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), eventuals::StoppedException); + + EXPECT_TRUE(socket.IsOpen()); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close() + >> Then([&]() { + EXPECT_FALSE(socket.IsOpen()); + }); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/ipv6/tcp-socket-send-receive-winapi.cc b/test/tcp/ipv6/tcp-socket-send-receive-winapi.cc new file mode 100644 index 00000000..27e823d7 --- /dev/null +++ b/test/tcp/ipv6/tcp-socket-send-receive-winapi.cc @@ -0,0 +1,265 @@ +#if defined(_WIN32) + +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPIPV6Test, SocketSendReceiveWinApiSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV6); + + std::thread winapi_thread; + SOCKET socket_winapi = INVALID_SOCKET; + SOCKET accepted_winapi = INVALID_SOCKET; + uint16_t socket_port = 0; + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + struct sockaddr_in6 address = {0}; + address.sin6_family = AF_INET6; + address.sin6_port = htons(TCPIPV6Test::kAnyPort); + inet_pton(AF_INET6, TCPIPV6Test::kLocalHostIPV6, &address.sin6_addr); + + socket_winapi = ::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + EXPECT_NE(socket_winapi, INVALID_SOCKET) + << "Error code: " + << WSAGetLastError(); + + int error = bind( + socket_winapi, + reinterpret_cast(&address), + sizeof(address)); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + error = listen(socket_winapi, 1); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + ZeroMemory(&address, sizeof(address)); + int address_size = sizeof(address); + error = getsockname( + socket_winapi, + reinterpret_cast(&address), + &address_size); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + socket_port = ntohs(address.sin6_port); + + return socket.Open(); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to WinAPI socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + + auto e_accept = [&]() { + return Eventual() + .start([&](auto& k) { + winapi_thread = std::thread([&]() { + accepted_winapi = accept(socket_winapi, nullptr, nullptr); + EXPECT_NE(accepted_winapi, INVALID_SOCKET) + << "Error code: " + << WSAGetLastError(); + + k.Start(); + }); + }); + }; + + auto e_connect = [&]() { + return socket.Connect(TCPIPV6Test::kLocalHostIPV6, socket_port); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + + EXPECT_NO_THROW(future_connect.get()); + + winapi_thread.join(); + + // --------------------------------------------------------------------- + // Send and receive data (socket -> winapi). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_winapi; + + char buffer[TCPIPV6Test::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_winapi = [&]() { + return socket.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto e_receive_from_socket = [&]() { + return Eventual() + .start([&](auto& k) { + winapi_thread = std::thread([&]() { + size_t bytes_to_read = TCPIPV6Test::kTestDataSize; + size_t bytes_read = 0; + + while (bytes_read < bytes_to_read) { + char* buffer_ptr = buffer + + bytes_read; + + int bytes_read_this_recv = recv( + accepted_winapi, + buffer_ptr, + bytes_to_read - bytes_read, + 0); + EXPECT_NE(bytes_read_this_recv, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + bytes_read += bytes_read_this_recv; + } + EXPECT_EQ(bytes_read, bytes_to_read); + + k.Start(); + }); + }); + }; + + auto [future_send_to_winapi, k_send_to_winapi] = + PromisifyForTest(e_send_to_winapi()); + auto [future_receive_from_socket, k_receive_from_socket] = + PromisifyForTest(e_receive_from_socket()); + + k_send_to_winapi.Register(interrupt_send_to_winapi); + + k_send_to_winapi.Start(); + k_receive_from_socket.Start(); + + EventLoop::Default().RunUntil(future_send_to_winapi); + + EXPECT_NO_THROW(future_send_to_winapi.get()); + EXPECT_NO_THROW(future_receive_from_socket.get()); + + winapi_thread.join(); + + EXPECT_STREQ(buffer, TCPIPV6Test::kTestData); + + // --------------------------------------------------------------------- + // Send and receive data (winapi -> socket). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive_from_winapi; + + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_socket = [&]() { + return Eventual() + .start([&](auto& k) { + winapi_thread = std::thread([&]() { + size_t bytes_to_write = TCPIPV6Test::kTestDataSize; + size_t bytes_written = 0; + + while (bytes_written < bytes_to_write) { + const char* data_to_send_ptr = TCPIPV6Test::kTestData + + bytes_written; + + int bytes_written_this_send = send( + accepted_winapi, + data_to_send_ptr, + bytes_to_write - bytes_written, + 0); + EXPECT_NE(bytes_written_this_send, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + bytes_written += bytes_written_this_send; + } + EXPECT_EQ(bytes_written, bytes_to_write); + + k.Start(); + }); + }); + }; + + auto e_receive_from_winapi = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPIPV6Test::kTestDataSize); + }; + + auto [future_send_to_socket, k_send_to_socket] = + PromisifyForTest(e_send_to_socket()); + auto [future_receive_from_winapi, k_receive_from_winapi] = + PromisifyForTest(e_receive_from_winapi()); + + k_receive_from_winapi.Register(interrupt_receive_from_winapi); + + k_send_to_socket.Start(); + k_receive_from_winapi.Start(); + + EventLoop::Default().RunUntil(future_receive_from_winapi); + + EXPECT_NO_THROW(future_send_to_socket.get()); + EXPECT_NO_THROW(future_receive_from_winapi.get()); + + winapi_thread.join(); + + EXPECT_STREQ(buffer, TCPIPV6Test::kTestData); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + int error = closesocket(socket_winapi); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + error = closesocket(accepted_winapi); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test + +#endif diff --git a/test/tcp/ipv6/tcp-socket-send-receive.cc b/test/tcp/ipv6/tcp-socket-send-receive.cc new file mode 100644 index 00000000..c049eef3 --- /dev/null +++ b/test/tcp/ipv6/tcp-socket-send-receive.cc @@ -0,0 +1,587 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPIPV6Test, SocketSendReceiveSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Send and receive data (socket -> accepted). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + char buffer[TCPIPV6Test::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto e_receive_from_socket = [&]() { + return accepted.Receive( + buffer, + sizeof(buffer), + TCPIPV6Test::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + auto [future_receive_from_socket, k_receive_from_socket] = + PromisifyForTest(e_receive_from_socket()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + k_receive_from_socket.Register(interrupt_receive_from_socket); + + k_send_to_accepted.Start(); + k_receive_from_socket.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + EventLoop::Default().RunUntil(future_receive_from_socket); + + EXPECT_NO_THROW(future_send_to_accepted.get()); + EXPECT_NO_THROW(future_receive_from_socket.get()); + + EXPECT_STREQ(buffer, TCPIPV6Test::kTestData); + + // --------------------------------------------------------------------- + // Send and receive data (accepted -> socket). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_socket; + eventuals::Interrupt interrupt_receive_from_accepted; + + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_socket = [&]() { + return accepted.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPIPV6Test::kTestDataSize); + }; + + auto [future_send_to_socket, k_send_to_socket] = + PromisifyForTest(e_send_to_socket()); + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_send_to_socket.Register(interrupt_send_to_socket); + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_send_to_socket.Start(); + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_socket); + EventLoop::Default().RunUntil(future_receive_from_accepted); + + EXPECT_NO_THROW(future_send_to_socket.get()); + EXPECT_NO_THROW(future_receive_from_accepted.get()); + + EXPECT_STREQ(buffer, TCPIPV6Test::kTestData); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPIPV6Test, SocketSendReceiveClosedFail) { + Socket socket(Protocol::IPV6); + + char buffer[TCPIPV6Test::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + // --------------------------------------------------------------------- + // Test Send operation. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + k_send.Start(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_send = &future_send]() { future_send->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); + + // --------------------------------------------------------------------- + // Test Receive operation. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPIPV6Test::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + k_receive.Start(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_receive = &future_receive]() { future_receive->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); +} + + +TEST_F(TCPIPV6Test, SocketSendReceiveNotConnectedFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return socket.Open(); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Try to send from socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + k_send.Start(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_send = &future_send]() { future_send->get(); }, + ThrowsMessage(StrEq("Socket is not connected"))); + + // --------------------------------------------------------------------- + // Try to receive. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + char buffer[TCPIPV6Test::kTestDataSize]; + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPIPV6Test::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + k_receive.Start(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_receive = &future_receive]() { future_receive->get(); }, + ThrowsMessage(StrEq("Socket is not connected"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Send and Receive operations are asynchronous. +TEST_F(TCPIPV6Test, SocketSendReceiveInterruptBeforeStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Interrupt: Send data from socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + interrupt_send.Trigger(); + + k_send.Start(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THROW(future_send.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Interrupt: Receive data. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + char buffer[TCPIPV6Test::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPIPV6Test::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + interrupt_receive.Trigger(); + + k_receive.Start(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THROW(future_receive.get(), eventuals::StoppedException); + + EXPECT_EQ(strlen(buffer), 0); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Send and Receive operations are asynchronous. +TEST_F(TCPIPV6Test, SocketSendReceiveInterruptAfterStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Interrupt: Send data from socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + k_send.Start(); + + interrupt_send.Trigger(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THROW(future_send.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Interrupt: Receive data. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + char buffer[TCPIPV6Test::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPIPV6Test::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + k_receive.Start(); + + interrupt_receive.Trigger(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THROW(future_receive.get(), eventuals::StoppedException); + + EXPECT_EQ(strlen(buffer), 0); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/ipv6/tcp-socket-shutdown.cc b/test/tcp/ipv6/tcp-socket-shutdown.cc new file mode 100644 index 00000000..5263736f --- /dev/null +++ b/test/tcp/ipv6/tcp-socket-shutdown.cc @@ -0,0 +1,695 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::ShutdownType; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPIPV6Test, ShutdownSendSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Shutdown socket's send channel. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_shutdown_send; + + auto e_shutdown_send = [&]() { + return socket.Shutdown(ShutdownType::SEND); + }; + + auto [future_shutdown_send, k_shutdown_send] = + PromisifyForTest(e_shutdown_send()); + + k_shutdown_send.Register(interrupt_shutdown_send); + + k_shutdown_send.Start(); + + EventLoop::Default().RunUntil(future_shutdown_send); + + EXPECT_NO_THROW(future_shutdown_send.get()); + + // --------------------------------------------------------------------- + // Try to send data (socket -> accepted). Should fail. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + + k_send_to_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future_send_to_accepted.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Send and receive data (accepted -> socket). Should still work. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_socket; + eventuals::Interrupt interrupt_receive_from_accepted; + + char buffer[TCPIPV6Test::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_socket = [&]() { + return accepted.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPIPV6Test::kTestDataSize); + }; + + auto [future_send_to_socket, k_send_to_socket] = + PromisifyForTest(e_send_to_socket()); + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_send_to_socket.Register(interrupt_send_to_socket); + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_send_to_socket.Start(); + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_socket); + EventLoop::Default().RunUntil(future_receive_from_accepted); + + EXPECT_NO_THROW(future_send_to_socket.get()); + EXPECT_NO_THROW(future_receive_from_accepted.get()); + + EXPECT_STREQ(buffer, TCPIPV6Test::kTestData); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPIPV6Test, ShutdownReceiveSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Shutdown socket's receive channel. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_shutdown_send; + + auto e_shutdown_send = [&]() { + return socket.Shutdown(ShutdownType::RECEIVE); + }; + + auto [future_shutdown_send, k_shutdown_send] = + PromisifyForTest(e_shutdown_send()); + + k_shutdown_send.Register(interrupt_shutdown_send); + + k_shutdown_send.Start(); + + EventLoop::Default().RunUntil(future_shutdown_send); + + EXPECT_NO_THROW(future_shutdown_send.get()); + + // --------------------------------------------------------------------- + // Try to receive data (accepted -> socket). Should fail. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive_from_accepted; + + char buffer[TCPIPV6Test::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPIPV6Test::kTestDataSize); + }; + + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_receive_from_accepted); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future_receive_from_accepted.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Send and receive data (socket -> accepted). Should still work. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto e_receive_from_socket = [&]() { + return accepted.Receive( + buffer, + sizeof(buffer), + TCPIPV6Test::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + auto [future_receive_from_socket, k_receive_from_socket] = + PromisifyForTest(e_receive_from_socket()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + k_receive_from_socket.Register(interrupt_receive_from_socket); + + k_send_to_accepted.Start(); + k_receive_from_socket.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + EventLoop::Default().RunUntil(future_receive_from_socket); + + EXPECT_NO_THROW(future_send_to_accepted.get()); + EXPECT_NO_THROW(future_receive_from_socket.get()); + + EXPECT_STREQ(buffer, TCPIPV6Test::kTestData); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPIPV6Test, ShutdownBothSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Shutdown socket's both send and receive channels. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_shutdown_send; + + auto e_shutdown_send = [&]() { + return socket.Shutdown(ShutdownType::BOTH); + }; + + auto [future_shutdown_send, k_shutdown_send] = + PromisifyForTest(e_shutdown_send()); + + k_shutdown_send.Register(interrupt_shutdown_send); + + k_shutdown_send.Start(); + + EventLoop::Default().RunUntil(future_shutdown_send); + + EXPECT_NO_THROW(future_shutdown_send.get()); + + // --------------------------------------------------------------------- + // Try to send data (socket -> accepted). Should fail. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + + k_send_to_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future_send_to_accepted.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Try to receive data (accepted -> socket). Should fail. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive_from_accepted; + + char buffer[TCPIPV6Test::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPIPV6Test::kTestDataSize); + }; + + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_receive_from_accepted); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future_receive_from_accepted.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPIPV6Test, ShutdownClosedFail) { + Socket socket(Protocol::IPV6); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Shutdown(ShutdownType::BOTH); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Shutdown operation is not asynchronous. +TEST_F(TCPIPV6Test, ShutdownInterrupt) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV6); + Socket socket(Protocol::IPV6); + Socket accepted(Protocol::IPV6); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPIPV6Test::kLocalHostIPV6, TCPIPV6Test::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPIPV6Test::kLocalHostIPV6, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Interrupted: Shutdown socket's both send and receive channels. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_shutdown_send; + + auto e_shutdown_send = [&]() { + return socket.Shutdown(ShutdownType::BOTH); + }; + + auto [future_shutdown_send, k_shutdown_send] = + PromisifyForTest(e_shutdown_send()); + + k_shutdown_send.Register(interrupt_shutdown_send); + + k_shutdown_send.Start(); + + interrupt_shutdown_send.Trigger(); + + EventLoop::Default().RunUntil(future_shutdown_send); + + EXPECT_THROW(future_shutdown_send.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Send and receive data (socket -> accepted). Should still work. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + char buffer[TCPIPV6Test::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto e_receive_from_socket = [&]() { + return accepted.Receive( + buffer, + sizeof(buffer), + TCPIPV6Test::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + auto [future_receive_from_socket, k_receive_from_socket] = + PromisifyForTest(e_receive_from_socket()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + k_receive_from_socket.Register(interrupt_receive_from_socket); + + k_send_to_accepted.Start(); + k_receive_from_socket.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + EventLoop::Default().RunUntil(future_receive_from_socket); + + EXPECT_NO_THROW(future_send_to_accepted.get()); + EXPECT_NO_THROW(future_receive_from_socket.get()); + + EXPECT_STREQ(buffer, TCPIPV6Test::kTestData); + + // --------------------------------------------------------------------- + // Send and receive data (accepted -> socket). Should still work. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_socket; + eventuals::Interrupt interrupt_receive_from_accepted; + + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_socket = [&]() { + return accepted.Send(TCPIPV6Test::kTestData, TCPIPV6Test::kTestDataSize); + }; + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPIPV6Test::kTestDataSize); + }; + + auto [future_send_to_socket, k_send_to_socket] = + PromisifyForTest(e_send_to_socket()); + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_send_to_socket.Register(interrupt_send_to_socket); + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_send_to_socket.Start(); + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_socket); + EventLoop::Default().RunUntil(future_receive_from_accepted); + + EXPECT_NO_THROW(future_send_to_socket.get()); + EXPECT_NO_THROW(future_receive_from_accepted.get()); + + EXPECT_STREQ(buffer, TCPIPV6Test::kTestData); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/ipv6/tcp.h b/test/tcp/ipv6/tcp.h new file mode 100644 index 00000000..2552ce17 --- /dev/null +++ b/test/tcp/ipv6/tcp.h @@ -0,0 +1,13 @@ +#pragma once + +#include "test/tcp/tcp.h" + +namespace eventuals::test { + +class TCPIPV6Test : public TCPTest { + public: + inline static const char* kLocalHostIPV6 = "::1"; + inline static const char* kAnyIPV6 = "::"; +}; + +} // namespace eventuals::test diff --git a/test/tcp/ssl/tcp-socket-handshake.cc b/test/tcp/ssl/tcp-socket-handshake.cc new file mode 100644 index 00000000..b3c4da3a --- /dev/null +++ b/test/tcp/ssl/tcp-socket-handshake.cc @@ -0,0 +1,528 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::ssl::HandshakeType; +using eventuals::ip::tcp::ssl::Socket; +using eventuals::ip::tcp::ssl::SSLContext; + +TEST_F(TCPSSLTest, HandshakeClosedFail) { + SSLContext socket_context(SetupSSLContextClient()); + + Socket socket(socket_context, Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Handshake(HandshakeType::CLIENT); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); +} + + +TEST_F(TCPSSLTest, HandshakeNotConnectedFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + SSLContext socket_context(SetupSSLContextClient()); + + Socket socket(socket_context, Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return socket.Open(); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Handshake(HandshakeType::CLIENT); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Socket is not connected"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPSSLTest, HandshakeTwiceFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + SSLContext socket_context(SetupSSLContextClient()); + SSLContext accepted_context(SetupSSLContextServer()); + + Acceptor acceptor(Protocol::IPV4); + Socket socket(socket_context, Protocol::IPV4); + Socket accepted(accepted_context, Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPSSLTest::kLocalHostIPV4, TCPSSLTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPSSLTest::kLocalHostIPV4, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Handshake. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_socket_handshake; + eventuals::Interrupt interrupt_accepted_handshake; + + auto e_socket_handshake = [&]() { + return socket.Handshake(HandshakeType::CLIENT); + }; + + auto e_accepted_handshake = [&]() { + return accepted.Handshake(HandshakeType::SERVER); + }; + + auto [future_socket_handshake, k_socket_handshake] = + PromisifyForTest(e_socket_handshake()); + auto [future_accepted_handshake, k_accepted_handshake] = + PromisifyForTest(e_accepted_handshake()); + + k_socket_handshake.Register(interrupt_socket_handshake); + k_accepted_handshake.Register(interrupt_accepted_handshake); + + k_socket_handshake.Start(); + k_accepted_handshake.Start(); + + EventLoop::Default().RunUntil(future_socket_handshake); + EventLoop::Default().RunUntil(future_accepted_handshake); + + EXPECT_NO_THROW(future_socket_handshake.get()); + EXPECT_NO_THROW(future_accepted_handshake.get()); + + // --------------------------------------------------------------------- + // Handshake the second time. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_socket_handshake_second; + eventuals::Interrupt interrupt_accepted_handshake_second; + + auto e_socket_handshake_second = [&]() { + return socket.Handshake(HandshakeType::CLIENT); + }; + + auto e_accepted_handshake_second = [&]() { + return accepted.Handshake(HandshakeType::SERVER); + }; + + auto [future_socket_handshake_second, k_socket_handshake_second] = + PromisifyForTest(e_socket_handshake_second()); + auto [future_accepted_handshake_second, k_accepted_handshake_second] = + PromisifyForTest(e_accepted_handshake_second()); + + k_socket_handshake_second.Register(interrupt_socket_handshake_second); + k_accepted_handshake_second.Register(interrupt_accepted_handshake_second); + + k_socket_handshake_second.Start(); + k_accepted_handshake_second.Start(); + + EventLoop::Default().RunUntil(future_socket_handshake_second); + EventLoop::Default().RunUntil(future_accepted_handshake_second); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_socket_handshake_second = &future_socket_handshake_second]() { + future_socket_handshake_second->get(); + }, + ThrowsMessage( + StrEq( + "Handshake was already completed"))); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_accepted_handshake_second = + &future_accepted_handshake_second]() { + future_accepted_handshake_second->get(); + }, + ThrowsMessage( + StrEq( + "Handshake was already completed"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Handshake operation is asynchronous. +TEST_F(TCPSSLTest, HandshakeInterruptBeforeStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + SSLContext socket_context(SetupSSLContextClient()); + SSLContext accepted_context(SetupSSLContextServer()); + + Acceptor acceptor(Protocol::IPV4); + Socket socket(socket_context, Protocol::IPV4); + Socket accepted(accepted_context, Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPSSLTest::kLocalHostIPV4, TCPSSLTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPSSLTest::kLocalHostIPV4, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Interrupt Handshake operation. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_socket_handshake; + eventuals::Interrupt interrupt_accepted_handshake; + + auto e_socket_handshake = [&]() { + return socket.Handshake(HandshakeType::CLIENT); + }; + + auto e_accepted_handshake = [&]() { + return accepted.Handshake(HandshakeType::SERVER); + }; + + auto [future_socket_handshake, k_socket_handshake] = + PromisifyForTest(e_socket_handshake()); + auto [future_accepted_handshake, k_accepted_handshake] = + PromisifyForTest(e_accepted_handshake()); + + k_socket_handshake.Register(interrupt_socket_handshake); + k_accepted_handshake.Register(interrupt_accepted_handshake); + + interrupt_socket_handshake.Trigger(); + interrupt_accepted_handshake.Trigger(); + + k_socket_handshake.Start(); + k_accepted_handshake.Start(); + + EventLoop::Default().RunUntil(future_socket_handshake); + EventLoop::Default().RunUntil(future_accepted_handshake); + + EXPECT_THROW(future_socket_handshake.get(), eventuals::StoppedException); + EXPECT_THROW(future_accepted_handshake.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Handshake operation is asynchronous. +TEST_F(TCPSSLTest, HandshakeInterruptAfterStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + SSLContext socket_context(SetupSSLContextClient()); + SSLContext accepted_context(SetupSSLContextServer()); + + Acceptor acceptor(Protocol::IPV4); + Socket socket(socket_context, Protocol::IPV4); + Socket accepted(accepted_context, Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPSSLTest::kLocalHostIPV4, TCPSSLTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPSSLTest::kLocalHostIPV4, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Interrupt Handshake operation. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_socket_handshake; + eventuals::Interrupt interrupt_accepted_handshake; + + auto e_socket_handshake = [&]() { + return socket.Handshake(HandshakeType::CLIENT); + }; + + auto e_accepted_handshake = [&]() { + return accepted.Handshake(HandshakeType::SERVER); + }; + + auto [future_socket_handshake, k_socket_handshake] = + PromisifyForTest(e_socket_handshake()); + auto [future_accepted_handshake, k_accepted_handshake] = + PromisifyForTest(e_accepted_handshake()); + + k_socket_handshake.Register(interrupt_socket_handshake); + k_accepted_handshake.Register(interrupt_accepted_handshake); + + k_socket_handshake.Start(); + k_accepted_handshake.Start(); + + interrupt_socket_handshake.Trigger(); + interrupt_accepted_handshake.Trigger(); + + EventLoop::Default().RunUntil(future_socket_handshake); + EventLoop::Default().RunUntil(future_accepted_handshake); + + EXPECT_THROW(future_socket_handshake.get(), eventuals::StoppedException); + EXPECT_THROW(future_accepted_handshake.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test \ No newline at end of file diff --git a/test/tcp/ssl/tcp-socket-send-receive.cc b/test/tcp/ssl/tcp-socket-send-receive.cc new file mode 100644 index 00000000..737b4282 --- /dev/null +++ b/test/tcp/ssl/tcp-socket-send-receive.cc @@ -0,0 +1,890 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::ssl::HandshakeType; +using eventuals::ip::tcp::ssl::Socket; +using eventuals::ip::tcp::ssl::SSLContext; + +TEST_F(TCPSSLTest, SocketSendReceiveSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + SSLContext socket_context(SetupSSLContextClient()); + SSLContext accepted_context(SetupSSLContextServer()); + + Acceptor acceptor(Protocol::IPV4); + Socket socket(socket_context, Protocol::IPV4); + Socket accepted(accepted_context, Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPSSLTest::kLocalHostIPV4, TCPSSLTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPSSLTest::kLocalHostIPV4, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Handshake. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_socket_handshake; + eventuals::Interrupt interrupt_accepted_handshake; + + auto e_socket_handshake = [&]() { + return socket.Handshake(HandshakeType::CLIENT); + }; + + auto e_accepted_handshake = [&]() { + return accepted.Handshake(HandshakeType::SERVER); + }; + + auto [future_socket_handshake, k_socket_handshake] = + PromisifyForTest(e_socket_handshake()); + auto [future_accepted_handshake, k_accepted_handshake] = + PromisifyForTest(e_accepted_handshake()); + + k_socket_handshake.Register(interrupt_socket_handshake); + k_accepted_handshake.Register(interrupt_accepted_handshake); + + k_socket_handshake.Start(); + k_accepted_handshake.Start(); + + EventLoop::Default().RunUntil(future_socket_handshake); + EventLoop::Default().RunUntil(future_accepted_handshake); + + EXPECT_NO_THROW(future_socket_handshake.get()); + EXPECT_NO_THROW(future_accepted_handshake.get()); + + // --------------------------------------------------------------------- + // Send and receive data (socket -> accepted). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + char buffer[TCPSSLTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPSSLTest::kTestData, TCPSSLTest::kTestDataSize); + }; + + auto e_receive_from_socket = [&]() { + return accepted.Receive(buffer, sizeof(buffer), TCPSSLTest::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + auto [future_receive_from_socket, k_receive_from_socket] = + PromisifyForTest(e_receive_from_socket()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + k_receive_from_socket.Register(interrupt_receive_from_socket); + + k_send_to_accepted.Start(); + k_receive_from_socket.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + EventLoop::Default().RunUntil(future_receive_from_socket); + + EXPECT_NO_THROW(future_send_to_accepted.get()); + EXPECT_NO_THROW(future_receive_from_socket.get()); + + EXPECT_STREQ(buffer, TCPSSLTest::kTestData); + + // --------------------------------------------------------------------- + // Send and receive data (accepted -> socket). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_socket; + eventuals::Interrupt interrupt_receive_from_accepted; + + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_socket = [&]() { + return accepted.Send(TCPSSLTest::kTestData, TCPSSLTest::kTestDataSize); + }; + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPSSLTest::kTestDataSize); + }; + + auto [future_send_to_socket, k_send_to_socket] = + PromisifyForTest(e_send_to_socket()); + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_send_to_socket.Register(interrupt_send_to_socket); + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_send_to_socket.Start(); + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_socket); + EventLoop::Default().RunUntil(future_receive_from_accepted); + + EXPECT_NO_THROW(future_send_to_socket.get()); + EXPECT_NO_THROW(future_receive_from_accepted.get()); + + EXPECT_STREQ(buffer, TCPSSLTest::kTestData); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPSSLTest, SocketSendReceiveClosedFail) { + SSLContext socket_context(SetupSSLContextClient()); + + Socket socket(socket_context, Protocol::IPV4); + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + // --------------------------------------------------------------------- + // Test Send operation. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + k_send.Start(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_send = &future_send]() { future_send->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); + + // --------------------------------------------------------------------- + // Test Receive operation. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + k_receive.Start(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_receive = &future_receive]() { future_receive->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); +} + + +TEST_F(TCPSSLTest, SocketSendReceiveNotConnectedFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + SSLContext socket_context(SetupSSLContextClient()); + + Socket socket(socket_context, Protocol::IPV4); + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return socket.Open(); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Test Send operation. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + k_send.Start(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_send = &future_send]() { future_send->get(); }, + ThrowsMessage(StrEq("Socket is not connected"))); + + // --------------------------------------------------------------------- + // Test Receive operation. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + k_receive.Start(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_receive = &future_receive]() { future_receive->get(); }, + ThrowsMessage(StrEq("Socket is not connected"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPSSLTest, SocketSendReceiveBeforeHandshakeFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + SSLContext socket_context(SetupSSLContextClient()); + SSLContext accepted_context(SetupSSLContextServer()); + + Acceptor acceptor(Protocol::IPV4); + Socket socket(socket_context, Protocol::IPV4); + Socket accepted(accepted_context, Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPSSLTest::kLocalHostIPV4, TCPSSLTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPSSLTest::kLocalHostIPV4, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Send and receive data (socket -> accepted). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + char buffer[TCPSSLTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPSSLTest::kTestData, TCPSSLTest::kTestDataSize); + }; + + auto e_receive_from_socket = [&]() { + return accepted.Receive(buffer, sizeof(buffer), TCPSSLTest::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + auto [future_receive_from_socket, k_receive_from_socket] = + PromisifyForTest(e_receive_from_socket()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + k_receive_from_socket.Register(interrupt_receive_from_socket); + + k_send_to_accepted.Start(); + k_receive_from_socket.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + EventLoop::Default().RunUntil(future_receive_from_socket); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_send_to_accepted = &future_send_to_accepted]() { + future_send_to_accepted->get(); + }, + ThrowsMessage( + StrEq( + "Must Handshake before trying to Send"))); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_receive_from_socket = &future_receive_from_socket]() { + future_receive_from_socket->get(); + }, + ThrowsMessage( + StrEq( + "Must Handshake before trying to Receive"))); + + // --------------------------------------------------------------------- + // Send and receive data (accepted -> socket). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_socket; + eventuals::Interrupt interrupt_receive_from_accepted; + + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_socket = [&]() { + return accepted.Send(TCPSSLTest::kTestData, TCPSSLTest::kTestDataSize); + }; + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPSSLTest::kTestDataSize); + }; + + auto [future_send_to_socket, k_send_to_socket] = + PromisifyForTest(e_send_to_socket()); + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_send_to_socket.Register(interrupt_send_to_socket); + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_send_to_socket.Start(); + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_socket); + EventLoop::Default().RunUntil(future_receive_from_accepted); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_send_to_socket = &future_send_to_socket]() { + future_send_to_socket->get(); + }, + ThrowsMessage( + StrEq( + "Must Handshake before trying to Send"))); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_receive_from_accepted = &future_receive_from_accepted]() { + future_receive_from_accepted->get(); + }, + ThrowsMessage( + StrEq( + "Must Handshake before trying to Receive"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Send and Receive operations are asynchronous. +TEST_F(TCPSSLTest, SocketSendReceiveInterruptBeforeStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + SSLContext socket_context(SetupSSLContextClient()); + SSLContext accepted_context(SetupSSLContextServer()); + + Acceptor acceptor(Protocol::IPV4); + Socket socket(socket_context, Protocol::IPV4); + Socket accepted(accepted_context, Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPSSLTest::kLocalHostIPV4, TCPSSLTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPSSLTest::kLocalHostIPV4, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Handshake. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_socket_handshake; + eventuals::Interrupt interrupt_accepted_handshake; + + auto e_socket_handshake = [&]() { + return socket.Handshake(HandshakeType::CLIENT); + }; + + auto e_accepted_handshake = [&]() { + return accepted.Handshake(HandshakeType::SERVER); + }; + + auto [future_socket_handshake, k_socket_handshake] = + PromisifyForTest(e_socket_handshake()); + auto [future_accepted_handshake, k_accepted_handshake] = + PromisifyForTest(e_accepted_handshake()); + + k_socket_handshake.Register(interrupt_socket_handshake); + k_accepted_handshake.Register(interrupt_accepted_handshake); + + k_socket_handshake.Start(); + k_accepted_handshake.Start(); + + EventLoop::Default().RunUntil(future_socket_handshake); + EventLoop::Default().RunUntil(future_accepted_handshake); + + EXPECT_NO_THROW(future_socket_handshake.get()); + EXPECT_NO_THROW(future_accepted_handshake.get()); + + // --------------------------------------------------------------------- + // Interrupt: Send data from socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + interrupt_send.Trigger(); + + k_send.Start(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THROW(future_send.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Interrupt: Receive data. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + interrupt_receive.Trigger(); + + k_receive.Start(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THROW(future_receive.get(), eventuals::StoppedException); + + EXPECT_EQ(strlen(buffer), 0); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Send and Receive operations are asynchronous. +TEST_F(TCPSSLTest, SocketSendReceiveInterruptAfterStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + SSLContext socket_context(SetupSSLContextClient()); + SSLContext accepted_context(SetupSSLContextServer()); + + Acceptor acceptor(Protocol::IPV4); + Socket socket(socket_context, Protocol::IPV4); + Socket accepted(accepted_context, Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPSSLTest::kLocalHostIPV4, TCPSSLTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect( + TCPSSLTest::kLocalHostIPV4, + acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Handshake. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_socket_handshake; + eventuals::Interrupt interrupt_accepted_handshake; + + auto e_socket_handshake = [&]() { + return socket.Handshake(HandshakeType::CLIENT); + }; + + auto e_accepted_handshake = [&]() { + return accepted.Handshake(HandshakeType::SERVER); + }; + + auto [future_socket_handshake, k_socket_handshake] = + PromisifyForTest(e_socket_handshake()); + auto [future_accepted_handshake, k_accepted_handshake] = + PromisifyForTest(e_accepted_handshake()); + + k_socket_handshake.Register(interrupt_socket_handshake); + k_accepted_handshake.Register(interrupt_accepted_handshake); + + k_socket_handshake.Start(); + k_accepted_handshake.Start(); + + EventLoop::Default().RunUntil(future_socket_handshake); + EventLoop::Default().RunUntil(future_accepted_handshake); + + EXPECT_NO_THROW(future_socket_handshake.get()); + EXPECT_NO_THROW(future_accepted_handshake.get()); + + // --------------------------------------------------------------------- + // Interrupt: Send data from socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + k_send.Start(); + + interrupt_send.Trigger(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THROW(future_send.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Interrupt: Receive data. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + k_receive.Start(); + + interrupt_receive.Trigger(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THROW(future_receive.get(), eventuals::StoppedException); + + EXPECT_EQ(strlen(buffer), 0); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/ssl/tcp.cc b/test/tcp/ssl/tcp.cc new file mode 100644 index 00000000..fca8b54a --- /dev/null +++ b/test/tcp/ssl/tcp.cc @@ -0,0 +1,69 @@ +#include "test/tcp/ssl/tcp.h" + +namespace eventuals::test { + +using namespace eventuals::ip::tcp::ssl; + +SSLContext TCPSSLTest::SetupSSLContextClient() { + return SSLContext::Builder() + .ssl_version(SSLVersion::TLSv1_2_CLIENT) + .certificate_chain(pem_certificate().c_str(), pem_certificate().length()) + .Build(); +} + +SSLContext TCPSSLTest::SetupSSLContextServer() { + return SSLContext::Builder() + .ssl_version(SSLVersion::TLSv1_2_SERVER) + .private_key(pem_key().c_str(), pem_key().length(), FileFormat::PEM) + .certificate_chain(pem_certificate().c_str(), pem_certificate().length()) + .Build(); +} + +// NOTE: We are using static variables to prevent regeneration +// of keys and certificates on every call. +const rsa::Key& TCPSSLTest::rsa_key() { + static auto key_expected = rsa::Key::Builder().Build(); + + CHECK(key_expected) << "Failed to generate RSA private key"; + + static auto key = *key_expected; + + return key; +} + +const std::string& TCPSSLTest::pem_key() { + static auto pem_key_expected = pem::Encode(rsa::Key(rsa_key())); + + CHECK(pem_key_expected) << "Failed to PEM encode RSA private key"; + + static auto pem_key = *pem_key_expected; + + return pem_key; +} + +const x509::Certificate& TCPSSLTest::certificate() { + static auto certificate_expected = x509::Certificate::Builder() + .subject_key(rsa::Key(rsa_key())) + .sign_key(rsa::Key(rsa_key())) + .hostname(host()) + .Build(); + + CHECK(certificate_expected) << "Failed to generate X509 certificate"; + + static auto certificate = *certificate_expected; + + return certificate; +} + +const std::string& TCPSSLTest::pem_certificate() { + static auto pem_certificate_expected = + pem::Encode(x509::Certificate(certificate())); + + CHECK(pem_certificate_expected) << "Failed to PEM encode X509 certificate"; + + static auto pem_certificate = *pem_certificate_expected; + + return pem_certificate; +} + +} // namespace eventuals::test diff --git a/test/tcp/ssl/tcp.h b/test/tcp/ssl/tcp.h new file mode 100644 index 00000000..79c0b42f --- /dev/null +++ b/test/tcp/ssl/tcp.h @@ -0,0 +1,28 @@ +#pragma once + +#include "eventuals/expected.h" +#include "eventuals/rsa.h" +#include "eventuals/tcp-ssl.h" +#include "eventuals/x509.h" +#include "test/tcp/tcp.h" + +namespace eventuals::test { + +class TCPSSLTest : public TCPTest { + public: + eventuals::ip::tcp::ssl::SSLContext SetupSSLContextClient(); + eventuals::ip::tcp::ssl::SSLContext SetupSSLContextServer(); + + std::string host() const { + return "localhost"; + } + + const x509::Certificate& certificate(); + + private: + const rsa::Key& rsa_key(); + const std::string& pem_key(); + const std::string& pem_certificate(); +}; + +} // namespace eventuals::test diff --git a/test/tcp/tcp-acceptor-accept.cc b/test/tcp/tcp-acceptor-accept.cc new file mode 100644 index 00000000..ac319439 --- /dev/null +++ b/test/tcp/tcp-acceptor-accept.cc @@ -0,0 +1,294 @@ +// NOTE: here we don't test the successful Accept operation. +// Connect tests should cover this. + +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPTest, AcceptorAcceptClosedFail) { + Acceptor acceptor(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Acceptor is closed"))); +} + + +TEST_F(TCPTest, AcceptorAcceptNotListeningFail) { + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Accept(accepted); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Acceptor is not listening"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPTest, AcceptorAcceptPassOpenSocketArgFail) { + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> accepted.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1) + >> acceptor.Accept(accepted); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Passed socket is not closed"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close() + >> accepted.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Accept operation is asynchronous. +TEST_F(TCPTest, AcceptorAcceptInterruptBeforeStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + interrupt.Trigger(); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Accept operation is asynchronous. +TEST_F(TCPTest, AcceptorAcceptInterruptAfterStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + interrupt.Trigger(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/tcp-acceptor-bind.cc b/test/tcp/tcp-acceptor-bind.cc new file mode 100644 index 00000000..867d5440 --- /dev/null +++ b/test/tcp/tcp-acceptor-bind.cc @@ -0,0 +1,182 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; + +TEST_F(TCPTest, AcceptorBindSuccess) { + Acceptor acceptor(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPTest, AcceptorBindAnyIPSuccess) { + Acceptor acceptor(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPTest::kAnyIPV4, TCPTest::kAnyPort) + >> acceptor.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPTest, AcceptorBindBadIPFail) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind("0.0.0.256", TCPTest::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPTest, AcceptorBindClosedFail) { + Acceptor acceptor(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Bind(TCPTest::kAnyIPV4, TCPTest::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Acceptor is closed"))); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Bind operation is not asynchronous. +TEST_F(TCPTest, AcceptorBindInterrupt) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> Then([&]() { + interrupt.Trigger(); + }) + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/tcp-acceptor-listen.cc b/test/tcp/tcp-acceptor-listen.cc new file mode 100644 index 00000000..5195b5d3 --- /dev/null +++ b/test/tcp/tcp-acceptor-listen.cc @@ -0,0 +1,170 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; + +TEST_F(TCPTest, AcceptorListenSuccess) { + Acceptor acceptor(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPTest::kAnyIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1) + >> acceptor.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPTest, AcceptorListenClosedFail) { + Acceptor acceptor(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Listen(1); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Acceptor is closed"))); +} + + +TEST_F(TCPTest, AcceptorListenTwiceFail) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPTest::kAnyIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1) + >> acceptor.Listen(1); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage( + StrEq( + "Acceptor is already listening"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Listen operation is not asynchronous. +TEST_F(TCPTest, AcceptorListenInterrupt) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return acceptor.Open() + >> acceptor.Bind(TCPTest::kAnyIPV4, TCPTest::kAnyPort) + >> Then([&interrupt]() { + interrupt.Trigger(); + }) + >> acceptor.Listen(1); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/tcp-acceptor-open-close.cc b/test/tcp/tcp-acceptor-open-close.cc new file mode 100644 index 00000000..2c8c61e1 --- /dev/null +++ b/test/tcp/tcp-acceptor-open-close.cc @@ -0,0 +1,160 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; + +TEST_F(TCPTest, AcceptorOpenCloseSuccess) { + Acceptor acceptor(Protocol::IPV4); + + Interrupt interrupt; + + EXPECT_FALSE(acceptor.IsOpen()); + + auto e = [&]() { + return acceptor.Open() + >> Then([&acceptor]() { + EXPECT_TRUE(acceptor.IsOpen()); + }) + >> acceptor.Close() + >> Then([&acceptor]() { + EXPECT_FALSE(acceptor.IsOpen()); + }); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPTest, AcceptorCloseClosedFail) { + Acceptor acceptor(Protocol::IPV4); + + Interrupt interrupt; + + EXPECT_FALSE(acceptor.IsOpen()); + + auto e = [&]() { + return acceptor.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Acceptor is closed"))); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Open operation is not asynchronous. +TEST_F(TCPTest, AcceptorOpenInterrupt) { + Acceptor acceptor(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(acceptor.IsOpen()); + + auto e = [&]() { + return acceptor.Open(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + interrupt.Trigger(); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), StoppedException); + + EXPECT_FALSE(acceptor.IsOpen()); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Close operation is not asynchronous. +TEST_F(TCPTest, AcceptorCloseInterrupt) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(acceptor.IsOpen()); + + auto e = [&]() { + return acceptor.Open() + >> Then([&]() { + EXPECT_TRUE(acceptor.IsOpen()); + interrupt.Trigger(); + }) + >> acceptor.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), StoppedException); + + EXPECT_TRUE(acceptor.IsOpen()); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close() + >> Then([&]() { + EXPECT_FALSE(acceptor.IsOpen()); + }); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/tcp-socket-bind.cc b/test/tcp/tcp-socket-bind.cc new file mode 100644 index 00000000..403c5bac --- /dev/null +++ b/test/tcp/tcp-socket-bind.cc @@ -0,0 +1,287 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPTest, SocketBindSuccess) { + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Open() + >> socket.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> socket.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPTest, SocketBindAnyIPSuccess) { + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Open() + >> socket.Bind(TCPTest::kAnyIPV4, TCPTest::kAnyPort) + >> socket.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPTest, SocketBindBadIPFail) { + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = socket.Open() + >> socket.Bind("0.0.0.256", TCPTest::kAnyPort); + + auto [future, k] = PromisifyForTest(std::move(e)); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPTest, SocketBindClosedFail) { + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Bind(TCPTest::kAnyIPV4, TCPTest::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); +} + + +TEST_F(TCPTest, SocketBindWhileConnectedFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Bind call is forbidden " + "while socket is connected"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Bind operation is not asynchronous. +TEST_F(TCPTest, SocketBindInterrupt) { + // --------------------------------------------------------------------- + // Main section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Open() + >> Then([&]() { + interrupt.Trigger(); + }) + >> socket.Bind(TCPTest::kAnyIPV4, TCPTest::kAnyPort); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/tcp-socket-connect-winapi.cc b/test/tcp/tcp-socket-connect-winapi.cc new file mode 100644 index 00000000..0454dc82 --- /dev/null +++ b/test/tcp/tcp-socket-connect-winapi.cc @@ -0,0 +1,143 @@ +#if defined(_WIN32) + +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPTest, SocketConnectToWinApiSocket) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV4); + + std::thread winapi_thread; + SOCKET socket_winapi = INVALID_SOCKET; + SOCKET accepted_winapi = INVALID_SOCKET; + uint16_t socket_port = 0; + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + struct sockaddr_in address = {0}; + address.sin_family = AF_INET; + address.sin_port = htons(TCPTest::kAnyPort); + inet_pton(AF_INET, TCPTest::kLocalHostIPV4, &address.sin_addr); + + socket_winapi = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + EXPECT_NE(socket_winapi, INVALID_SOCKET) + << "Error code: " + << WSAGetLastError(); + + int error = bind( + socket_winapi, + reinterpret_cast(&address), + sizeof(address)); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + error = listen(socket_winapi, 1); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + ZeroMemory(&address, sizeof(address)); + int address_size = sizeof(address); + error = getsockname( + socket_winapi, + reinterpret_cast(&address), + &address_size); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + socket_port = ntohs(address.sin_port); + + return socket.Open(); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to WinAPI socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + + auto e_accept = [&]() { + return Eventual() + .start([&](auto& k) { + winapi_thread = std::thread([&]() { + accepted_winapi = accept(socket_winapi, nullptr, nullptr); + EXPECT_NE(accepted_winapi, INVALID_SOCKET) + << "Error code: " + << WSAGetLastError(); + + k.Start(); + }); + }); + }; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, socket_port); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + + EXPECT_NO_THROW(future_connect.get()); + + winapi_thread.join(); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + int error = closesocket(socket_winapi); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + error = closesocket(accepted_winapi); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test + +#endif diff --git a/test/tcp/tcp-socket-connect.cc b/test/tcp/tcp-socket-connect.cc new file mode 100644 index 00000000..cd548fa6 --- /dev/null +++ b/test/tcp/tcp-socket-connect.cc @@ -0,0 +1,408 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPTest, SocketConnectToAcceptorSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPTest, SocketConnectToAcceptorTwiceFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor the second time. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect_second; + + auto e_connect_second = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto [future_connect_second, k_connect_second] = + PromisifyForTest(e_connect_second()); + + k_connect_second.Register(interrupt_connect_second); + + k_connect_second.Start(); + + EventLoop::Default().RunUntil(future_connect_second); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_connect_second = &future_connect_second]() { + future_connect_second->get(); + }, + ThrowsMessage(StrEq("Socket is already connected"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPTest, SocketConnectToBadIpAddressFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return socket.Open(); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Try to connect. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + + auto e_connect = [&]() { + return socket.Connect("127.0.0.256", 8000); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + + k_connect.Register(interrupt_connect); + + k_connect.Start(); + + EventLoop::Default().RunUntil(future_connect); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future_connect.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Connect operation is asynchronous. +TEST_F(TCPTest, SocketConnectToAcceptorInterruptBeforeStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + + k_connect.Register(interrupt_connect); + + interrupt_connect.Trigger(); + + k_connect.Start(); + + EventLoop::Default().RunUntil(future_connect); + + EXPECT_THROW(future_connect.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Connect operation is asynchronous. +TEST_F(TCPTest, SocketConnectToAcceptorInterruptAfterStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + + k_connect.Register(interrupt_connect); + + k_connect.Start(); + + interrupt_connect.Trigger(); + + EventLoop::Default().RunUntil(future_connect); + + EXPECT_THROW(future_connect.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/tcp-socket-open-close.cc b/test/tcp/tcp-socket-open-close.cc new file mode 100644 index 00000000..2b92eba6 --- /dev/null +++ b/test/tcp/tcp-socket-open-close.cc @@ -0,0 +1,154 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPTest, SocketOpenCloseSuccess) { + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(socket.IsOpen()); + + auto e = [&]() { + return socket.Open() + >> Then([&socket]() { + EXPECT_TRUE(socket.IsOpen()); + }) + >> socket.Close() + >> Then([&socket]() { + EXPECT_FALSE(socket.IsOpen()); + }); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_NO_THROW(future.get()); +} + + +TEST_F(TCPTest, SocketCloseClosedFail) { + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(socket.IsOpen()); + + auto e = [&]() { + return socket.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); +} + + +TEST_F(TCPTest, SocketOpenInterrupt) { + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(socket.IsOpen()); + + auto e = [&]() { + return socket.Open(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + interrupt.Trigger(); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), eventuals::StoppedException); + + EXPECT_FALSE(socket.IsOpen()); +} + + +TEST_F(TCPTest, SocketCloseInterrupt) { + // --------------------------------------------------------------------- + // Main test section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + EXPECT_FALSE(socket.IsOpen()); + + auto e = [&]() { + return socket.Open() + >> Then([&]() { + EXPECT_TRUE(socket.IsOpen()); + interrupt.Trigger(); + }) + >> socket.Close(); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THROW(future.get(), eventuals::StoppedException); + + EXPECT_TRUE(socket.IsOpen()); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close() + >> Then([&]() { + EXPECT_FALSE(socket.IsOpen()); + }); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/tcp-socket-send-receive-winapi.cc b/test/tcp/tcp-socket-send-receive-winapi.cc new file mode 100644 index 00000000..c6d8240f --- /dev/null +++ b/test/tcp/tcp-socket-send-receive-winapi.cc @@ -0,0 +1,265 @@ +#if defined(_WIN32) + +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPTest, SocketSendReceiveWinApiSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV4); + + std::thread winapi_thread; + SOCKET socket_winapi = INVALID_SOCKET; + SOCKET accepted_winapi = INVALID_SOCKET; + uint16_t socket_port = 0; + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + struct sockaddr_in address = {0}; + address.sin_family = AF_INET; + address.sin_port = htons(TCPTest::kAnyPort); + inet_pton(AF_INET, TCPTest::kLocalHostIPV4, &address.sin_addr); + + socket_winapi = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + EXPECT_NE(socket_winapi, INVALID_SOCKET) + << "Error code: " + << WSAGetLastError(); + + int error = bind( + socket_winapi, + reinterpret_cast(&address), + sizeof(address)); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + error = listen(socket_winapi, 1); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + ZeroMemory(&address, sizeof(address)); + int address_size = sizeof(address); + error = getsockname( + socket_winapi, + reinterpret_cast(&address), + &address_size); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + socket_port = ntohs(address.sin_port); + + return socket.Open(); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to WinAPI socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + + auto e_accept = [&]() { + return Eventual() + .start([&](auto& k) { + winapi_thread = std::thread([&]() { + accepted_winapi = accept(socket_winapi, nullptr, nullptr); + EXPECT_NE(accepted_winapi, INVALID_SOCKET) + << "Error code: " + << WSAGetLastError(); + + k.Start(); + }); + }); + }; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, socket_port); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + + EXPECT_NO_THROW(future_connect.get()); + + winapi_thread.join(); + + // --------------------------------------------------------------------- + // Send and receive data (socket -> winapi). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_winapi; + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_winapi = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto e_receive_from_socket = [&]() { + return Eventual() + .start([&](auto& k) { + winapi_thread = std::thread([&]() { + size_t bytes_to_read = TCPTest::kTestDataSize; + size_t bytes_read = 0; + + while (bytes_read < bytes_to_read) { + char* buffer_ptr = buffer + + bytes_read; + + int bytes_read_this_recv = recv( + accepted_winapi, + buffer_ptr, + bytes_to_read - bytes_read, + 0); + EXPECT_NE(bytes_read_this_recv, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + bytes_read += bytes_read_this_recv; + } + EXPECT_EQ(bytes_read, bytes_to_read); + + k.Start(); + }); + }); + }; + + auto [future_send_to_winapi, k_send_to_winapi] = + PromisifyForTest(e_send_to_winapi()); + auto [future_receive_from_socket, k_receive_from_socket] = + PromisifyForTest(e_receive_from_socket()); + + k_send_to_winapi.Register(interrupt_send_to_winapi); + + k_send_to_winapi.Start(); + k_receive_from_socket.Start(); + + EventLoop::Default().RunUntil(future_send_to_winapi); + + EXPECT_NO_THROW(future_send_to_winapi.get()); + EXPECT_NO_THROW(future_receive_from_socket.get()); + + winapi_thread.join(); + + EXPECT_STREQ(buffer, TCPTest::kTestData); + + // --------------------------------------------------------------------- + // Send and receive data (winapi -> socket). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive_from_winapi; + + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_socket = [&]() { + return Eventual() + .start([&](auto& k) { + winapi_thread = std::thread([&]() { + size_t bytes_to_write = TCPTest::kTestDataSize; + size_t bytes_written = 0; + + while (bytes_written < bytes_to_write) { + const char* data_to_send_ptr = TCPTest::kTestData + + bytes_written; + + int bytes_written_this_send = send( + accepted_winapi, + data_to_send_ptr, + bytes_to_write - bytes_written, + 0); + EXPECT_NE(bytes_written_this_send, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + bytes_written += bytes_written_this_send; + } + EXPECT_EQ(bytes_written, bytes_to_write); + + k.Start(); + }); + }); + }; + + auto e_receive_from_winapi = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_send_to_socket, k_send_to_socket] = + PromisifyForTest(e_send_to_socket()); + auto [future_receive_from_winapi, k_receive_from_winapi] = + PromisifyForTest(e_receive_from_winapi()); + + k_receive_from_winapi.Register(interrupt_receive_from_winapi); + + k_send_to_socket.Start(); + k_receive_from_winapi.Start(); + + EventLoop::Default().RunUntil(future_receive_from_winapi); + + EXPECT_NO_THROW(future_send_to_socket.get()); + EXPECT_NO_THROW(future_receive_from_winapi.get()); + + winapi_thread.join(); + + EXPECT_STREQ(buffer, TCPTest::kTestData); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + int error = closesocket(socket_winapi); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + error = closesocket(accepted_winapi); + EXPECT_NE(error, SOCKET_ERROR) + << "Error code: " + << WSAGetLastError(); + + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test + +#endif diff --git a/test/tcp/tcp-socket-send-receive.cc b/test/tcp/tcp-socket-send-receive.cc new file mode 100644 index 00000000..77fe1fe4 --- /dev/null +++ b/test/tcp/tcp-socket-send-receive.cc @@ -0,0 +1,578 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPTest, SocketSendReceiveSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Send and receive data (socket -> accepted). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto e_receive_from_socket = [&]() { + return accepted.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + auto [future_receive_from_socket, k_receive_from_socket] = + PromisifyForTest(e_receive_from_socket()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + k_receive_from_socket.Register(interrupt_receive_from_socket); + + k_send_to_accepted.Start(); + k_receive_from_socket.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + EventLoop::Default().RunUntil(future_receive_from_socket); + + EXPECT_NO_THROW(future_send_to_accepted.get()); + EXPECT_NO_THROW(future_receive_from_socket.get()); + + EXPECT_STREQ(buffer, TCPTest::kTestData); + + // --------------------------------------------------------------------- + // Send and receive data (accepted -> socket). + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_socket; + eventuals::Interrupt interrupt_receive_from_accepted; + + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_socket = [&]() { + return accepted.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_send_to_socket, k_send_to_socket] = + PromisifyForTest(e_send_to_socket()); + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_send_to_socket.Register(interrupt_send_to_socket); + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_send_to_socket.Start(); + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_socket); + EventLoop::Default().RunUntil(future_receive_from_accepted); + + EXPECT_NO_THROW(future_send_to_socket.get()); + EXPECT_NO_THROW(future_receive_from_accepted.get()); + + EXPECT_STREQ(buffer, TCPTest::kTestData); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPTest, SocketSendReceiveClosedFail) { + Socket socket(Protocol::IPV4); + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + // --------------------------------------------------------------------- + // Test Send operation. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + k_send.Start(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_send = &future_send]() { future_send->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); + + // --------------------------------------------------------------------- + // Test Receive operation. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + k_receive.Start(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_receive = &future_receive]() { future_receive->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); +} + + +TEST_F(TCPTest, SocketSendReceiveNotConnectedFail) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return socket.Open(); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Try to send from socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + k_send.Start(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_send = &future_send]() { future_send->get(); }, + ThrowsMessage(StrEq("Socket is not connected"))); + + // --------------------------------------------------------------------- + // Try to receive. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + char buffer[TCPTest::kTestDataSize]; + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + k_receive.Start(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future_receive = &future_receive]() { future_receive->get(); }, + ThrowsMessage(StrEq("Socket is not connected"))); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Send and Receive operations are asynchronous. +TEST_F(TCPTest, SocketSendReceiveInterruptBeforeStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Interrupt: Send data from socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + interrupt_send.Trigger(); + + k_send.Start(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THROW(future_send.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Interrupt: Receive data. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + interrupt_receive.Trigger(); + + k_receive.Start(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THROW(future_receive.get(), eventuals::StoppedException); + + EXPECT_EQ(strlen(buffer), 0); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +// NOTE: we need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Send and Receive operations are asynchronous. +TEST_F(TCPTest, SocketSendReceiveInterruptAfterStart) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Interrupt: Send data from socket. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send; + + auto e_send = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto [future_send, k_send] = PromisifyForTest(e_send()); + + k_send.Register(interrupt_send); + + k_send.Start(); + + interrupt_send.Trigger(); + + EventLoop::Default().RunUntil(future_send); + + EXPECT_THROW(future_send.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Interrupt: Receive data. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive; + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_receive = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_receive, k_receive] = PromisifyForTest(e_receive()); + + k_receive.Register(interrupt_receive); + + k_receive.Start(); + + interrupt_receive.Trigger(); + + EventLoop::Default().RunUntil(future_receive); + + EXPECT_THROW(future_receive.get(), eventuals::StoppedException); + + EXPECT_EQ(strlen(buffer), 0); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/tcp-socket-shutdown.cc b/test/tcp/tcp-socket-shutdown.cc new file mode 100644 index 00000000..3db5a397 --- /dev/null +++ b/test/tcp/tcp-socket-shutdown.cc @@ -0,0 +1,681 @@ +#include "tcp.h" + +namespace eventuals::test { +namespace { + +using testing::StrEq; +using testing::ThrowsMessage; + +using eventuals::ip::tcp::Acceptor; +using eventuals::ip::tcp::Protocol; +using eventuals::ip::tcp::ShutdownType; +using eventuals::ip::tcp::Socket; + +TEST_F(TCPTest, ShutdownSendSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Shutdown socket's send channel. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_shutdown_send; + + auto e_shutdown_send = [&]() { + return socket.Shutdown(ShutdownType::SEND); + }; + + auto [future_shutdown_send, k_shutdown_send] = + PromisifyForTest(e_shutdown_send()); + + k_shutdown_send.Register(interrupt_shutdown_send); + + k_shutdown_send.Start(); + + EventLoop::Default().RunUntil(future_shutdown_send); + + EXPECT_NO_THROW(future_shutdown_send.get()); + + // --------------------------------------------------------------------- + // Try to send data (socket -> accepted). Should fail. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + + k_send_to_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future_send_to_accepted.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Send and receive data (accepted -> socket). Should still work. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_socket; + eventuals::Interrupt interrupt_receive_from_accepted; + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_socket = [&]() { + return accepted.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_send_to_socket, k_send_to_socket] = + PromisifyForTest(e_send_to_socket()); + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_send_to_socket.Register(interrupt_send_to_socket); + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_send_to_socket.Start(); + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_socket); + EventLoop::Default().RunUntil(future_receive_from_accepted); + + EXPECT_NO_THROW(future_send_to_socket.get()); + EXPECT_NO_THROW(future_receive_from_accepted.get()); + + EXPECT_STREQ(buffer, TCPTest::kTestData); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPTest, ShutdownReceiveSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Shutdown socket's receive channel. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_shutdown_send; + + auto e_shutdown_send = [&]() { + return socket.Shutdown(ShutdownType::RECEIVE); + }; + + auto [future_shutdown_send, k_shutdown_send] = + PromisifyForTest(e_shutdown_send()); + + k_shutdown_send.Register(interrupt_shutdown_send); + + k_shutdown_send.Start(); + + EventLoop::Default().RunUntil(future_shutdown_send); + + EXPECT_NO_THROW(future_shutdown_send.get()); + + // --------------------------------------------------------------------- + // Try to receive data (accepted -> socket). Should fail. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive_from_accepted; + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_receive_from_accepted); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future_receive_from_accepted.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Send and receive data (socket -> accepted). Should still work. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto e_receive_from_socket = [&]() { + return accepted.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + auto [future_receive_from_socket, k_receive_from_socket] = + PromisifyForTest(e_receive_from_socket()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + k_receive_from_socket.Register(interrupt_receive_from_socket); + + k_send_to_accepted.Start(); + k_receive_from_socket.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + EventLoop::Default().RunUntil(future_receive_from_socket); + + EXPECT_NO_THROW(future_send_to_accepted.get()); + EXPECT_NO_THROW(future_receive_from_socket.get()); + + EXPECT_STREQ(buffer, TCPTest::kTestData); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPTest, ShutdownBothSuccess) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Shutdown socket's both send and receive channels. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_shutdown_send; + + auto e_shutdown_send = [&]() { + return socket.Shutdown(ShutdownType::BOTH); + }; + + auto [future_shutdown_send, k_shutdown_send] = + PromisifyForTest(e_shutdown_send()); + + k_shutdown_send.Register(interrupt_shutdown_send); + + k_shutdown_send.Start(); + + EventLoop::Default().RunUntil(future_shutdown_send); + + EXPECT_NO_THROW(future_shutdown_send.get()); + + // --------------------------------------------------------------------- + // Try to send data (socket -> accepted). Should fail. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + + k_send_to_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future_send_to_accepted.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Try to receive data (accepted -> socket). Should fail. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_receive_from_accepted; + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_receive_from_accepted); + + // Not using EXPECT_THAT since + // the message depends on the language set in the OS. + EXPECT_THROW(future_receive_from_accepted.get(), std::runtime_error); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + + +TEST_F(TCPTest, ShutdownClosedFail) { + Socket socket(Protocol::IPV4); + + eventuals::Interrupt interrupt; + + auto e = [&]() { + return socket.Shutdown(ShutdownType::BOTH); + }; + + auto [future, k] = PromisifyForTest(e()); + + k.Register(interrupt); + + k.Start(); + + EventLoop::Default().RunUntil(future); + + EXPECT_THAT( + // NOTE: capturing 'future' as a pointer because until C++20 we + // can't capture a "local binding" by reference and there is a + // bug with 'EXPECT_THAT' that forces our lambda to be const so + // if we capture it by copy we can't call 'get()' because that + // is a non-const function. + [future = &future]() { future->get(); }, + ThrowsMessage(StrEq("Socket is closed"))); +} + + +// NOTE: we don't need to do separate tests for +// calling interrupt.Trigger() before and after k.Start() +// since Shutdown operation is not asynchronous. +TEST_F(TCPTest, ShutdownInterrupt) { + // --------------------------------------------------------------------- + // Setup section. + // --------------------------------------------------------------------- + Acceptor acceptor(Protocol::IPV4); + Socket socket(Protocol::IPV4); + Socket accepted(Protocol::IPV4); + + eventuals::Interrupt interrupt_setup; + + auto e_setup = [&]() { + return acceptor.Open() + >> socket.Open() + >> acceptor.Bind(TCPTest::kLocalHostIPV4, TCPTest::kAnyPort) + >> acceptor.Listen(1); + }; + + auto [future_setup, k_setup] = PromisifyForTest(e_setup()); + + k_setup.Register(interrupt_setup); + + k_setup.Start(); + + EventLoop::Default().RunUntil(future_setup); + + EXPECT_NO_THROW(future_setup.get()); + + // --------------------------------------------------------------------- + // Connect to acceptor. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_connect; + eventuals::Interrupt interrupt_accept; + + auto e_connect = [&]() { + return socket.Connect(TCPTest::kLocalHostIPV4, acceptor.ListeningPort()); + }; + + auto e_accept = [&]() { + return acceptor.Accept(accepted); + }; + + auto [future_connect, k_connect] = PromisifyForTest(e_connect()); + auto [future_accept, k_accept] = PromisifyForTest(e_accept()); + + k_connect.Register(interrupt_connect); + k_accept.Register(interrupt_accept); + + k_connect.Start(); + k_accept.Start(); + + EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_accept); + + EXPECT_NO_THROW(future_connect.get()); + EXPECT_NO_THROW(future_accept.get()); + + // --------------------------------------------------------------------- + // Interrupted: Shutdown socket's both send and receive channels. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_shutdown_send; + + auto e_shutdown_send = [&]() { + return socket.Shutdown(ShutdownType::BOTH); + }; + + auto [future_shutdown_send, k_shutdown_send] = + PromisifyForTest(e_shutdown_send()); + + k_shutdown_send.Register(interrupt_shutdown_send); + + k_shutdown_send.Start(); + + interrupt_shutdown_send.Trigger(); + + EventLoop::Default().RunUntil(future_shutdown_send); + + EXPECT_THROW(future_shutdown_send.get(), eventuals::StoppedException); + + // --------------------------------------------------------------------- + // Send and receive data (socket -> accepted). Should still work. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_accepted; + eventuals::Interrupt interrupt_receive_from_socket; + + char buffer[TCPTest::kTestDataSize]; + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_accepted = [&]() { + return socket.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto e_receive_from_socket = [&]() { + return accepted.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_send_to_accepted, k_send_to_accepted] = + PromisifyForTest(e_send_to_accepted()); + auto [future_receive_from_socket, k_receive_from_socket] = + PromisifyForTest(e_receive_from_socket()); + + k_send_to_accepted.Register(interrupt_send_to_accepted); + k_receive_from_socket.Register(interrupt_receive_from_socket); + + k_send_to_accepted.Start(); + k_receive_from_socket.Start(); + + EventLoop::Default().RunUntil(future_send_to_accepted); + EventLoop::Default().RunUntil(future_receive_from_socket); + + EXPECT_NO_THROW(future_send_to_accepted.get()); + EXPECT_NO_THROW(future_receive_from_socket.get()); + + EXPECT_STREQ(buffer, TCPTest::kTestData); + + // --------------------------------------------------------------------- + // Send and receive data (accepted -> socket). Should still work. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_send_to_socket; + eventuals::Interrupt interrupt_receive_from_accepted; + + memset(buffer, 0, sizeof(buffer)); + + auto e_send_to_socket = [&]() { + return accepted.Send(TCPTest::kTestData, TCPTest::kTestDataSize); + }; + + auto e_receive_from_accepted = [&]() { + return socket.Receive(buffer, sizeof(buffer), TCPTest::kTestDataSize); + }; + + auto [future_send_to_socket, k_send_to_socket] = + PromisifyForTest(e_send_to_socket()); + auto [future_receive_from_accepted, k_receive_from_accepted] = + PromisifyForTest(e_receive_from_accepted()); + + k_send_to_socket.Register(interrupt_send_to_socket); + k_receive_from_accepted.Register(interrupt_receive_from_accepted); + + k_send_to_socket.Start(); + k_receive_from_accepted.Start(); + + EventLoop::Default().RunUntil(future_send_to_socket); + EventLoop::Default().RunUntil(future_receive_from_accepted); + + EXPECT_NO_THROW(future_send_to_socket.get()); + EXPECT_NO_THROW(future_receive_from_accepted.get()); + + EXPECT_STREQ(buffer, TCPTest::kTestData); + + // --------------------------------------------------------------------- + // Cleanup section. + // --------------------------------------------------------------------- + eventuals::Interrupt interrupt_cleanup; + + auto e_cleanup = [&]() { + return accepted.Close() + >> acceptor.Close() + >> socket.Close(); + }; + + auto [future_cleanup, k_cleanup] = PromisifyForTest(e_cleanup()); + + k_cleanup.Register(interrupt_cleanup); + + k_cleanup.Start(); + + EventLoop::Default().RunUntil(future_cleanup); + + EXPECT_NO_THROW(future_cleanup.get()); +} + +} // namespace +} // namespace eventuals::test diff --git a/test/tcp/tcp.h b/test/tcp/tcp.h new file mode 100644 index 00000000..54a03d81 --- /dev/null +++ b/test/tcp/tcp.h @@ -0,0 +1,23 @@ +#pragma once + +#include "eventuals/tcp.h" +#include "eventuals/then.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "test/event-loop-test.h" +#include "test/promisify-for-test.h" + +namespace eventuals::test { + +class TCPTest : public eventuals::test::EventLoopTest { + public: + inline static const char* kLocalHostIPV4 = "127.0.0.1"; + inline static const char* kAnyIPV4 = "0.0.0.0"; + inline static const int kAnyPort = 0; + + inline static const char* kTestData = "Hello Eventuals!"; + // +1 for null terminator. + inline static const size_t kTestDataSize = strlen(kTestData) + 1; +}; + +} // namespace eventuals::test From 8164879fd3d704592d3d4006c208b772f309285d Mon Sep 17 00:00:00 2001 From: Nikita Nikolaenko <6870920@gmail.com> Date: Thu, 14 Jul 2022 14:51:57 +0200 Subject: [PATCH 3/7] Run asio event loop inside libuv event loop --- eventuals/event-loop.cc | 35 +++++++++++++++++++++++------------ eventuals/event-loop.h | 15 +++------------ 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/eventuals/event-loop.cc b/eventuals/event-loop.cc index f23e3227..ad0f4b58 100644 --- a/eventuals/event-loop.cc +++ b/eventuals/event-loop.cc @@ -150,12 +150,6 @@ void EventLoop::ConstructDefaultAndRunForeverDetached() { auto thread = std::thread([]() { while (true) { EventLoop::Default().running_ = true; - - EventLoop::Default().io_context().restart(); - EventLoop::Default().io_context().poll(); - - // NOTE: callbacks running in the asio::io_context - // are not considered to be running in the libuv loop. EventLoop::Default().in_event_loop_ = true; // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on @@ -188,18 +182,31 @@ EventLoop::EventLoop() // do about that except 'RunForever()' or application-level // synchronization). uv_check_init(&loop_, &check_); + uv_check_init(&loop_, &asio_check_); check_.data = this; + asio_check_.data = this; uv_check_start(&check_, [](uv_check_t* check) { ((EventLoop*) check->data)->Check(); }); + uv_check_start(&asio_check_, [](uv_check_t* check) { + ((EventLoop*) check->data)->AsioCheck(); + }); + // NOTE: we unreference 'check_' so that when we run the event // loop it's presence doesn't factor into whether or not the loop is // considered alive. uv_unref((uv_handle_t*) &check_); + // TODO: we should consider event loop to be alive + // while this check is running. + // Before we find a better way to determine if there are + // still pending jobs in the asio io_context, + // we will just unreference this check. + uv_unref((uv_handle_t*) &asio_check_); + uv_async_init(&loop_, &async_, nullptr); // NOTE: see comments in 'RunUntil()' as to why we don't unreference @@ -214,6 +221,9 @@ EventLoop::~EventLoop() { uv_check_stop(&check_); uv_close((uv_handle_t*) &check_, nullptr); + uv_check_stop(&asio_check_); + uv_close((uv_handle_t*) &asio_check_, nullptr); + uv_close((uv_handle_t*) &async_, nullptr); // NOTE: ideally we can just run 'uv_run()' once now in order to @@ -295,12 +305,6 @@ EventLoop::~EventLoop() { void EventLoop::RunForever() { while (true) { running_ = true; - - io_context().restart(); - io_context().poll(); - - // NOTE: callbacks running in the asio::io_context - // are not considered to be running in the libuv loop. in_event_loop_ = true; // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on @@ -416,6 +420,13 @@ void EventLoop::Check() { //////////////////////////////////////////////////////////////////////// +void EventLoop::AsioCheck() { + io_context().restart(); + io_context().poll(); +} + +//////////////////////////////////////////////////////////////////////// + } // namespace eventuals //////////////////////////////////////////////////////////////////////// diff --git a/eventuals/event-loop.h b/eventuals/event-loop.h index e4d2c346..e043b3d6 100644 --- a/eventuals/event-loop.h +++ b/eventuals/event-loop.h @@ -473,12 +473,6 @@ class EventLoop final : public Scheduler { auto status = std::future_status::ready; do { running_ = true; - - io_context().restart(); - io_context().poll(); - - // NOTE: callbacks running in the asio::io_context - // are not considered to be running in the libuv loop. in_event_loop_ = true; // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on @@ -495,12 +489,6 @@ class EventLoop final : public Scheduler { void RunWhileWaiters() { do { running_ = true; - - io_context().restart(); - io_context().poll(); - - // NOTE: callbacks running in the asio::io_context - // are not considered to be running in the libuv loop. in_event_loop_ = true; // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on @@ -1062,8 +1050,11 @@ class EventLoop final : public Scheduler { void Check(); + void AsioCheck(); + uv_loop_t loop_ = {}; uv_check_t check_ = {}; + uv_check_t asio_check_ = {}; uv_async_t async_ = {}; asio::io_context io_context_; From b160bb16f1e4e37d44853b69410d44f09fd15a58 Mon Sep 17 00:00:00 2001 From: Nikita Nikolaenko <6870920@gmail.com> Date: Thu, 14 Jul 2022 14:52:32 +0200 Subject: [PATCH 4/7] Reference TCP class fields on all platforms --- eventuals/event-loop.cc | 33 ++++++++------------------------- eventuals/event-loop.h | 4 ++-- eventuals/tcp-acceptor.h | 4 +--- eventuals/tcp-ssl-socket.h | 4 +--- 4 files changed, 12 insertions(+), 33 deletions(-) diff --git a/eventuals/event-loop.cc b/eventuals/event-loop.cc index ad0f4b58..47eba378 100644 --- a/eventuals/event-loop.cc +++ b/eventuals/event-loop.cc @@ -148,20 +148,7 @@ void EventLoop::ConstructDefaultAndRunForeverDetached() { ConstructDefault(); auto thread = std::thread([]() { - while (true) { - EventLoop::Default().running_ = true; - EventLoop::Default().in_event_loop_ = true; - - // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on - // I/O. - uv_run(&EventLoop::Default().loop_, UV_RUN_NOWAIT); - - EventLoop::Default().running_ = false; - EventLoop::Default().in_event_loop_ = false; - } - - // We should never get out of run. - LOG(FATAL) << "unreachable"; + EventLoop::Default().RunForever(); }); thread.detach(); @@ -303,19 +290,15 @@ EventLoop::~EventLoop() { //////////////////////////////////////////////////////////////////////// void EventLoop::RunForever() { - while (true) { - running_ = true; - in_event_loop_ = true; - - // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on - // I/O. - uv_run(&EventLoop::Default().loop_, UV_RUN_NOWAIT); + in_event_loop_ = true; + running_ = true; - running_ = false; - in_event_loop_ = false; - } + // NOTE: we'll truly run forever because handles like 'async_' will + // keep the loop alive forever. + uv_run(&loop_, UV_RUN_DEFAULT); - LOG(FATAL) << "unreachable"; + running_ = false; + in_event_loop_ = false; } //////////////////////////////////////////////////////////////////////// diff --git a/eventuals/event-loop.h b/eventuals/event-loop.h index e043b3d6..a4b12dc9 100644 --- a/eventuals/event-loop.h +++ b/eventuals/event-loop.h @@ -472,8 +472,8 @@ class EventLoop final : public Scheduler { void RunUntil(std::future& future) { auto status = std::future_status::ready; do { - running_ = true; in_event_loop_ = true; + running_ = true; // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on // I/O. @@ -488,8 +488,8 @@ class EventLoop final : public Scheduler { void RunWhileWaiters() { do { - running_ = true; in_event_loop_ = true; + running_ = true; // NOTE: We use 'UV_RUN_NOWAIT' because we don't want to block on // I/O. diff --git a/eventuals/tcp-acceptor.h b/eventuals/tcp-acceptor.h index 86433cd0..e0fbf5b6 100644 --- a/eventuals/tcp-acceptor.h +++ b/eventuals/tcp-acceptor.h @@ -19,13 +19,11 @@ class Acceptor final { : loop_(loop), protocol_(protocol), acceptor_(loop.io_context()) { - // NOTE: Compiling on MacOS produces weird + // NOTE: Compiling this class produces // 'unused-private-field' warnings, hence why this // piece of code is needed. -#ifdef __MACH__ (void) is_listening_; (void) protocol_; -#endif } ~Acceptor() { diff --git a/eventuals/tcp-ssl-socket.h b/eventuals/tcp-ssl-socket.h index c236c114..03149a2d 100644 --- a/eventuals/tcp-ssl-socket.h +++ b/eventuals/tcp-ssl-socket.h @@ -32,12 +32,10 @@ class Socket final : public SocketBase { EventLoop& loop = EventLoop::Default()) : SocketBase(protocol, loop), stream_(loop.io_context(), context.ssl_context_handle()) { - // NOTE: Compiling on MacOS produces weird + // NOTE: Compiling this class produces // 'unused-private-field' warnings, hence why this // piece of code is needed. -#ifdef __MACH__ (void) completed_handshake_; -#endif } Socket(const Socket& that) = delete; From 5af85c1fe39887a2dc57ccec724bf5003e328e20 Mon Sep 17 00:00:00 2001 From: Nikita Nikolaenko <6870920@gmail.com> Date: Thu, 14 Jul 2022 16:36:35 +0200 Subject: [PATCH 5/7] Implement EventLoop::RunUntil for multiple futures --- eventuals/event-loop.h | 27 ++++++++--- test/tcp/ipv6/tcp-socket-bind.cc | 3 +- test/tcp/ipv6/tcp-socket-connect.cc | 6 +-- .../ipv6/tcp-socket-send-receive-winapi.cc | 10 ++-- test/tcp/ipv6/tcp-socket-send-receive.cc | 19 ++++---- test/tcp/ipv6/tcp-socket-shutdown.cc | 32 ++++++------- test/tcp/ssl/tcp-socket-handshake.cc | 29 ++++++------ test/tcp/ssl/tcp-socket-send-receive.cc | 47 ++++++++++--------- test/tcp/tcp-socket-bind.cc | 3 +- test/tcp/tcp-socket-connect.cc | 6 +-- test/tcp/tcp-socket-send-receive-winapi.cc | 10 ++-- test/tcp/tcp-socket-send-receive.cc | 19 ++++---- test/tcp/tcp-socket-shutdown.cc | 32 ++++++------- 13 files changed, 131 insertions(+), 112 deletions(-) diff --git a/eventuals/event-loop.h b/eventuals/event-loop.h index a4b12dc9..6ff43013 100644 --- a/eventuals/event-loop.h +++ b/eventuals/event-loop.h @@ -248,7 +248,8 @@ class EventLoop final : public Scheduler { error_ = uv_timer_start( timer(), [](uv_timer_t* timer) { - auto& continuation = *(Continuation*) timer->data; + auto& continuation = + *(Continuation*) timer->data; CHECK_EQ(timer, continuation.timer()); CHECK_EQ( &continuation, @@ -267,8 +268,10 @@ class EventLoop final : public Scheduler { if (!continuation.error_) { continuation.k_.Start(); } else { - continuation.k_.Fail(std::runtime_error( - uv_strerror(continuation.error_))); + continuation.k_.Fail( + std::runtime_error( + uv_strerror( + continuation.error_))); } }); } @@ -483,7 +486,15 @@ class EventLoop final : public Scheduler { in_event_loop_ = false; status = future.wait_for(std::chrono::nanoseconds::zero()); - } while (status != std::future_status::ready || waiters_.load() != nullptr); + } while ( + status != std::future_status::ready + || waiters_.load() != nullptr); + } + + template + void RunUntil(std::future& future, std::future& futures) { + RunUntil(future); + RunUntil(futures); } void RunWhileWaiters() { @@ -854,7 +865,8 @@ class EventLoop final : public Scheduler { CHECK(!continuation.error_); continuation.error_ = uv_poll_stop(poll); if (status == 0 && !continuation.error_) { - continuation.k_.Body(static_cast(events)); + continuation.k_.Body( + static_cast(events)); } else { continuation.completed_ = true; if (!continuation.error_) { @@ -1268,7 +1280,10 @@ template //////////////////////////////////////////////////////////////////////// template [[nodiscard]] auto EventLoop::Schedule(std::string&& name, E e) { - return _EventLoopSchedule::Composable{std::move(e), this, std::move(name)}; + return _EventLoopSchedule::Composable{ + std::move(e), + this, + std::move(name)}; } //////////////////////////////////////////////////////////////////////// diff --git a/test/tcp/ipv6/tcp-socket-bind.cc b/test/tcp/ipv6/tcp-socket-bind.cc index 165ad0f5..95b8250c 100644 --- a/test/tcp/ipv6/tcp-socket-bind.cc +++ b/test/tcp/ipv6/tcp-socket-bind.cc @@ -180,8 +180,7 @@ TEST_F(TCPIPV6Test, SocketBindWhileConnectedFail) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); diff --git a/test/tcp/ipv6/tcp-socket-connect.cc b/test/tcp/ipv6/tcp-socket-connect.cc index 95595e2e..c16ffc13 100644 --- a/test/tcp/ipv6/tcp-socket-connect.cc +++ b/test/tcp/ipv6/tcp-socket-connect.cc @@ -62,8 +62,7 @@ TEST_F(TCPIPV6Test, SocketConnectToAcceptorSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -143,8 +142,7 @@ TEST_F(TCPIPV6Test, SocketConnectToAcceptorTwiceFail) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); diff --git a/test/tcp/ipv6/tcp-socket-send-receive-winapi.cc b/test/tcp/ipv6/tcp-socket-send-receive-winapi.cc index 27e823d7..265bd128 100644 --- a/test/tcp/ipv6/tcp-socket-send-receive-winapi.cc +++ b/test/tcp/ipv6/tcp-socket-send-receive-winapi.cc @@ -104,7 +104,7 @@ TEST_F(TCPIPV6Test, SocketSendReceiveWinApiSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); @@ -161,7 +161,9 @@ TEST_F(TCPIPV6Test, SocketSendReceiveWinApiSuccess) { k_send_to_winapi.Start(); k_receive_from_socket.Start(); - EventLoop::Default().RunUntil(future_send_to_winapi); + EventLoop::Default().RunUntil( + future_send_to_winapi, + future_receive_from_socket); EXPECT_NO_THROW(future_send_to_winapi.get()); EXPECT_NO_THROW(future_receive_from_socket.get()); @@ -220,7 +222,9 @@ TEST_F(TCPIPV6Test, SocketSendReceiveWinApiSuccess) { k_send_to_socket.Start(); k_receive_from_winapi.Start(); - EventLoop::Default().RunUntil(future_receive_from_winapi); + EventLoop::Default().RunUntil( + future_send_to_socket, + future_receive_from_winapi); EXPECT_NO_THROW(future_send_to_socket.get()); EXPECT_NO_THROW(future_receive_from_winapi.get()); diff --git a/test/tcp/ipv6/tcp-socket-send-receive.cc b/test/tcp/ipv6/tcp-socket-send-receive.cc index c049eef3..b622c37d 100644 --- a/test/tcp/ipv6/tcp-socket-send-receive.cc +++ b/test/tcp/ipv6/tcp-socket-send-receive.cc @@ -62,8 +62,7 @@ TEST_F(TCPIPV6Test, SocketSendReceiveSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -99,8 +98,9 @@ TEST_F(TCPIPV6Test, SocketSendReceiveSuccess) { k_send_to_accepted.Start(); k_receive_from_socket.Start(); - EventLoop::Default().RunUntil(future_send_to_accepted); - EventLoop::Default().RunUntil(future_receive_from_socket); + EventLoop::Default().RunUntil( + future_send_to_accepted, + future_receive_from_socket); EXPECT_NO_THROW(future_send_to_accepted.get()); EXPECT_NO_THROW(future_receive_from_socket.get()); @@ -134,8 +134,9 @@ TEST_F(TCPIPV6Test, SocketSendReceiveSuccess) { k_send_to_socket.Start(); k_receive_from_accepted.Start(); - EventLoop::Default().RunUntil(future_send_to_socket); - EventLoop::Default().RunUntil(future_receive_from_accepted); + EventLoop::Default().RunUntil( + future_send_to_socket, + future_receive_from_accepted); EXPECT_NO_THROW(future_send_to_socket.get()); EXPECT_NO_THROW(future_receive_from_accepted.get()); @@ -377,8 +378,7 @@ TEST_F(TCPIPV6Test, SocketSendReceiveInterruptBeforeStart) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -508,8 +508,7 @@ TEST_F(TCPIPV6Test, SocketSendReceiveInterruptAfterStart) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); diff --git a/test/tcp/ipv6/tcp-socket-shutdown.cc b/test/tcp/ipv6/tcp-socket-shutdown.cc index 5263736f..7255e2d1 100644 --- a/test/tcp/ipv6/tcp-socket-shutdown.cc +++ b/test/tcp/ipv6/tcp-socket-shutdown.cc @@ -63,8 +63,7 @@ TEST_F(TCPIPV6Test, ShutdownSendSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -140,8 +139,9 @@ TEST_F(TCPIPV6Test, ShutdownSendSuccess) { k_send_to_socket.Start(); k_receive_from_accepted.Start(); - EventLoop::Default().RunUntil(future_send_to_socket); - EventLoop::Default().RunUntil(future_receive_from_accepted); + EventLoop::Default().RunUntil( + future_send_to_socket, + future_receive_from_accepted); EXPECT_NO_THROW(future_send_to_socket.get()); EXPECT_NO_THROW(future_receive_from_accepted.get()); @@ -223,8 +223,7 @@ TEST_F(TCPIPV6Test, ShutdownReceiveSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -304,8 +303,9 @@ TEST_F(TCPIPV6Test, ShutdownReceiveSuccess) { k_send_to_accepted.Start(); k_receive_from_socket.Start(); - EventLoop::Default().RunUntil(future_send_to_accepted); - EventLoop::Default().RunUntil(future_receive_from_socket); + EventLoop::Default().RunUntil( + future_send_to_accepted, + future_receive_from_socket); EXPECT_NO_THROW(future_send_to_accepted.get()); EXPECT_NO_THROW(future_receive_from_socket.get()); @@ -387,8 +387,7 @@ TEST_F(TCPIPV6Test, ShutdownBothSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -567,8 +566,7 @@ TEST_F(TCPIPV6Test, ShutdownInterrupt) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -626,8 +624,9 @@ TEST_F(TCPIPV6Test, ShutdownInterrupt) { k_send_to_accepted.Start(); k_receive_from_socket.Start(); - EventLoop::Default().RunUntil(future_send_to_accepted); - EventLoop::Default().RunUntil(future_receive_from_socket); + EventLoop::Default().RunUntil( + future_send_to_accepted, + future_receive_from_socket); EXPECT_NO_THROW(future_send_to_accepted.get()); EXPECT_NO_THROW(future_receive_from_socket.get()); @@ -661,8 +660,9 @@ TEST_F(TCPIPV6Test, ShutdownInterrupt) { k_send_to_socket.Start(); k_receive_from_accepted.Start(); - EventLoop::Default().RunUntil(future_send_to_socket); - EventLoop::Default().RunUntil(future_receive_from_accepted); + EventLoop::Default().RunUntil( + future_send_to_socket, + future_receive_from_accepted); EXPECT_NO_THROW(future_send_to_socket.get()); EXPECT_NO_THROW(future_receive_from_accepted.get()); diff --git a/test/tcp/ssl/tcp-socket-handshake.cc b/test/tcp/ssl/tcp-socket-handshake.cc index b3c4da3a..3405a507 100644 --- a/test/tcp/ssl/tcp-socket-handshake.cc +++ b/test/tcp/ssl/tcp-socket-handshake.cc @@ -168,8 +168,7 @@ TEST_F(TCPSSLTest, HandshakeTwiceFail) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -199,8 +198,9 @@ TEST_F(TCPSSLTest, HandshakeTwiceFail) { k_socket_handshake.Start(); k_accepted_handshake.Start(); - EventLoop::Default().RunUntil(future_socket_handshake); - EventLoop::Default().RunUntil(future_accepted_handshake); + EventLoop::Default().RunUntil( + future_socket_handshake, + future_accepted_handshake); EXPECT_NO_THROW(future_socket_handshake.get()); EXPECT_NO_THROW(future_accepted_handshake.get()); @@ -230,8 +230,9 @@ TEST_F(TCPSSLTest, HandshakeTwiceFail) { k_socket_handshake_second.Start(); k_accepted_handshake_second.Start(); - EventLoop::Default().RunUntil(future_socket_handshake_second); - EventLoop::Default().RunUntil(future_accepted_handshake_second); + EventLoop::Default().RunUntil( + future_socket_handshake_second, + future_accepted_handshake_second); EXPECT_THAT( // NOTE: capturing 'future' as a pointer because until C++20 we @@ -341,8 +342,7 @@ TEST_F(TCPSSLTest, HandshakeInterruptBeforeStart) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -375,8 +375,9 @@ TEST_F(TCPSSLTest, HandshakeInterruptBeforeStart) { k_socket_handshake.Start(); k_accepted_handshake.Start(); - EventLoop::Default().RunUntil(future_socket_handshake); - EventLoop::Default().RunUntil(future_accepted_handshake); + EventLoop::Default().RunUntil( + future_socket_handshake, + future_accepted_handshake); EXPECT_THROW(future_socket_handshake.get(), eventuals::StoppedException); EXPECT_THROW(future_accepted_handshake.get(), eventuals::StoppedException); @@ -462,8 +463,7 @@ TEST_F(TCPSSLTest, HandshakeInterruptAfterStart) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -496,8 +496,9 @@ TEST_F(TCPSSLTest, HandshakeInterruptAfterStart) { interrupt_socket_handshake.Trigger(); interrupt_accepted_handshake.Trigger(); - EventLoop::Default().RunUntil(future_socket_handshake); - EventLoop::Default().RunUntil(future_accepted_handshake); + EventLoop::Default().RunUntil( + future_socket_handshake, + future_accepted_handshake); EXPECT_THROW(future_socket_handshake.get(), eventuals::StoppedException); EXPECT_THROW(future_accepted_handshake.get(), eventuals::StoppedException); diff --git a/test/tcp/ssl/tcp-socket-send-receive.cc b/test/tcp/ssl/tcp-socket-send-receive.cc index 737b4282..93ed2a50 100644 --- a/test/tcp/ssl/tcp-socket-send-receive.cc +++ b/test/tcp/ssl/tcp-socket-send-receive.cc @@ -67,8 +67,7 @@ TEST_F(TCPSSLTest, SocketSendReceiveSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -98,8 +97,9 @@ TEST_F(TCPSSLTest, SocketSendReceiveSuccess) { k_socket_handshake.Start(); k_accepted_handshake.Start(); - EventLoop::Default().RunUntil(future_socket_handshake); - EventLoop::Default().RunUntil(future_accepted_handshake); + EventLoop::Default().RunUntil( + future_socket_handshake, + future_accepted_handshake); EXPECT_NO_THROW(future_socket_handshake.get()); EXPECT_NO_THROW(future_accepted_handshake.get()); @@ -132,8 +132,9 @@ TEST_F(TCPSSLTest, SocketSendReceiveSuccess) { k_send_to_accepted.Start(); k_receive_from_socket.Start(); - EventLoop::Default().RunUntil(future_send_to_accepted); - EventLoop::Default().RunUntil(future_receive_from_socket); + EventLoop::Default().RunUntil( + future_send_to_accepted, + future_receive_from_socket); EXPECT_NO_THROW(future_send_to_accepted.get()); EXPECT_NO_THROW(future_receive_from_socket.get()); @@ -167,8 +168,9 @@ TEST_F(TCPSSLTest, SocketSendReceiveSuccess) { k_send_to_socket.Start(); k_receive_from_accepted.Start(); - EventLoop::Default().RunUntil(future_send_to_socket); - EventLoop::Default().RunUntil(future_receive_from_accepted); + EventLoop::Default().RunUntil( + future_send_to_socket, + future_receive_from_accepted); EXPECT_NO_THROW(future_send_to_socket.get()); EXPECT_NO_THROW(future_receive_from_accepted.get()); @@ -415,8 +417,7 @@ TEST_F(TCPSSLTest, SocketSendReceiveBeforeHandshakeFail) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -449,8 +450,9 @@ TEST_F(TCPSSLTest, SocketSendReceiveBeforeHandshakeFail) { k_send_to_accepted.Start(); k_receive_from_socket.Start(); - EventLoop::Default().RunUntil(future_send_to_accepted); - EventLoop::Default().RunUntil(future_receive_from_socket); + EventLoop::Default().RunUntil( + future_send_to_accepted, + future_receive_from_socket); EXPECT_THAT( // NOTE: capturing 'future' as a pointer because until C++20 we @@ -505,8 +507,9 @@ TEST_F(TCPSSLTest, SocketSendReceiveBeforeHandshakeFail) { k_send_to_socket.Start(); k_receive_from_accepted.Start(); - EventLoop::Default().RunUntil(future_send_to_socket); - EventLoop::Default().RunUntil(future_receive_from_accepted); + EventLoop::Default().RunUntil( + future_send_to_socket, + future_receive_from_accepted); EXPECT_THAT( // NOTE: capturing 'future' as a pointer because until C++20 we @@ -615,8 +618,7 @@ TEST_F(TCPSSLTest, SocketSendReceiveInterruptBeforeStart) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -646,8 +648,9 @@ TEST_F(TCPSSLTest, SocketSendReceiveInterruptBeforeStart) { k_socket_handshake.Start(); k_accepted_handshake.Start(); - EventLoop::Default().RunUntil(future_socket_handshake); - EventLoop::Default().RunUntil(future_accepted_handshake); + EventLoop::Default().RunUntil( + future_socket_handshake, + future_accepted_handshake); EXPECT_NO_THROW(future_socket_handshake.get()); EXPECT_NO_THROW(future_accepted_handshake.get()); @@ -780,8 +783,7 @@ TEST_F(TCPSSLTest, SocketSendReceiveInterruptAfterStart) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -811,8 +813,9 @@ TEST_F(TCPSSLTest, SocketSendReceiveInterruptAfterStart) { k_socket_handshake.Start(); k_accepted_handshake.Start(); - EventLoop::Default().RunUntil(future_socket_handshake); - EventLoop::Default().RunUntil(future_accepted_handshake); + EventLoop::Default().RunUntil( + future_socket_handshake, + future_accepted_handshake); EXPECT_NO_THROW(future_socket_handshake.get()); EXPECT_NO_THROW(future_accepted_handshake.get()); diff --git a/test/tcp/tcp-socket-bind.cc b/test/tcp/tcp-socket-bind.cc index 403c5bac..0369c4c1 100644 --- a/test/tcp/tcp-socket-bind.cc +++ b/test/tcp/tcp-socket-bind.cc @@ -178,8 +178,7 @@ TEST_F(TCPTest, SocketBindWhileConnectedFail) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); diff --git a/test/tcp/tcp-socket-connect.cc b/test/tcp/tcp-socket-connect.cc index cd548fa6..9d1ce28e 100644 --- a/test/tcp/tcp-socket-connect.cc +++ b/test/tcp/tcp-socket-connect.cc @@ -60,8 +60,7 @@ TEST_F(TCPTest, SocketConnectToAcceptorSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -139,8 +138,7 @@ TEST_F(TCPTest, SocketConnectToAcceptorTwiceFail) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); diff --git a/test/tcp/tcp-socket-send-receive-winapi.cc b/test/tcp/tcp-socket-send-receive-winapi.cc index c6d8240f..b836a705 100644 --- a/test/tcp/tcp-socket-send-receive-winapi.cc +++ b/test/tcp/tcp-socket-send-receive-winapi.cc @@ -104,7 +104,7 @@ TEST_F(TCPTest, SocketSendReceiveWinApiSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); @@ -161,7 +161,9 @@ TEST_F(TCPTest, SocketSendReceiveWinApiSuccess) { k_send_to_winapi.Start(); k_receive_from_socket.Start(); - EventLoop::Default().RunUntil(future_send_to_winapi); + EventLoop::Default().RunUntil( + future_send_to_winapi, + future_receive_from_socket); EXPECT_NO_THROW(future_send_to_winapi.get()); EXPECT_NO_THROW(future_receive_from_socket.get()); @@ -220,7 +222,9 @@ TEST_F(TCPTest, SocketSendReceiveWinApiSuccess) { k_send_to_socket.Start(); k_receive_from_winapi.Start(); - EventLoop::Default().RunUntil(future_receive_from_winapi); + EventLoop::Default().RunUntil( + future_send_to_socket, + future_receive_from_winapi); EXPECT_NO_THROW(future_send_to_socket.get()); EXPECT_NO_THROW(future_receive_from_winapi.get()); diff --git a/test/tcp/tcp-socket-send-receive.cc b/test/tcp/tcp-socket-send-receive.cc index 77fe1fe4..4b6d7120 100644 --- a/test/tcp/tcp-socket-send-receive.cc +++ b/test/tcp/tcp-socket-send-receive.cc @@ -60,8 +60,7 @@ TEST_F(TCPTest, SocketSendReceiveSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -94,8 +93,9 @@ TEST_F(TCPTest, SocketSendReceiveSuccess) { k_send_to_accepted.Start(); k_receive_from_socket.Start(); - EventLoop::Default().RunUntil(future_send_to_accepted); - EventLoop::Default().RunUntil(future_receive_from_socket); + EventLoop::Default().RunUntil( + future_send_to_accepted, + future_receive_from_socket); EXPECT_NO_THROW(future_send_to_accepted.get()); EXPECT_NO_THROW(future_receive_from_socket.get()); @@ -129,8 +129,9 @@ TEST_F(TCPTest, SocketSendReceiveSuccess) { k_send_to_socket.Start(); k_receive_from_accepted.Start(); - EventLoop::Default().RunUntil(future_send_to_socket); - EventLoop::Default().RunUntil(future_receive_from_accepted); + EventLoop::Default().RunUntil( + future_send_to_socket, + future_receive_from_accepted); EXPECT_NO_THROW(future_send_to_socket.get()); EXPECT_NO_THROW(future_receive_from_accepted.get()); @@ -370,8 +371,7 @@ TEST_F(TCPTest, SocketSendReceiveInterruptBeforeStart) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -499,8 +499,7 @@ TEST_F(TCPTest, SocketSendReceiveInterruptAfterStart) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); diff --git a/test/tcp/tcp-socket-shutdown.cc b/test/tcp/tcp-socket-shutdown.cc index 3db5a397..3f10c693 100644 --- a/test/tcp/tcp-socket-shutdown.cc +++ b/test/tcp/tcp-socket-shutdown.cc @@ -61,8 +61,7 @@ TEST_F(TCPTest, ShutdownSendSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -138,8 +137,9 @@ TEST_F(TCPTest, ShutdownSendSuccess) { k_send_to_socket.Start(); k_receive_from_accepted.Start(); - EventLoop::Default().RunUntil(future_send_to_socket); - EventLoop::Default().RunUntil(future_receive_from_accepted); + EventLoop::Default().RunUntil( + future_send_to_socket, + future_receive_from_accepted); EXPECT_NO_THROW(future_send_to_socket.get()); EXPECT_NO_THROW(future_receive_from_accepted.get()); @@ -219,8 +219,7 @@ TEST_F(TCPTest, ShutdownReceiveSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -297,8 +296,9 @@ TEST_F(TCPTest, ShutdownReceiveSuccess) { k_send_to_accepted.Start(); k_receive_from_socket.Start(); - EventLoop::Default().RunUntil(future_send_to_accepted); - EventLoop::Default().RunUntil(future_receive_from_socket); + EventLoop::Default().RunUntil( + future_send_to_accepted, + future_receive_from_socket); EXPECT_NO_THROW(future_send_to_accepted.get()); EXPECT_NO_THROW(future_receive_from_socket.get()); @@ -378,8 +378,7 @@ TEST_F(TCPTest, ShutdownBothSuccess) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -556,8 +555,7 @@ TEST_F(TCPTest, ShutdownInterrupt) { k_connect.Start(); k_accept.Start(); - EventLoop::Default().RunUntil(future_connect); - EventLoop::Default().RunUntil(future_accept); + EventLoop::Default().RunUntil(future_connect, future_accept); EXPECT_NO_THROW(future_connect.get()); EXPECT_NO_THROW(future_accept.get()); @@ -612,8 +610,9 @@ TEST_F(TCPTest, ShutdownInterrupt) { k_send_to_accepted.Start(); k_receive_from_socket.Start(); - EventLoop::Default().RunUntil(future_send_to_accepted); - EventLoop::Default().RunUntil(future_receive_from_socket); + EventLoop::Default().RunUntil( + future_send_to_accepted, + future_receive_from_socket); EXPECT_NO_THROW(future_send_to_accepted.get()); EXPECT_NO_THROW(future_receive_from_socket.get()); @@ -647,8 +646,9 @@ TEST_F(TCPTest, ShutdownInterrupt) { k_send_to_socket.Start(); k_receive_from_accepted.Start(); - EventLoop::Default().RunUntil(future_send_to_socket); - EventLoop::Default().RunUntil(future_receive_from_accepted); + EventLoop::Default().RunUntil( + future_send_to_socket, + future_receive_from_accepted); EXPECT_NO_THROW(future_send_to_socket.get()); EXPECT_NO_THROW(future_receive_from_accepted.get()); From 25e6b447bdbd65c5378ef4e0fd934284f66e6b0b Mon Sep 17 00:00:00 2001 From: Nikita Nikolaenko <6870920@gmail.com> Date: Wed, 27 Jul 2022 22:52:10 +0200 Subject: [PATCH 6/7] Check for active handles using io_context::run_one --- eventuals/event-loop.cc | 42 +++++++++++++++++++---------------------- eventuals/event-loop.h | 7 +------ 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/eventuals/event-loop.cc b/eventuals/event-loop.cc index 47eba378..ef719ade 100644 --- a/eventuals/event-loop.cc +++ b/eventuals/event-loop.cc @@ -169,17 +169,13 @@ EventLoop::EventLoop() // do about that except 'RunForever()' or application-level // synchronization). uv_check_init(&loop_, &check_); - uv_check_init(&loop_, &asio_check_); check_.data = this; - asio_check_.data = this; uv_check_start(&check_, [](uv_check_t* check) { - ((EventLoop*) check->data)->Check(); - }); - - uv_check_start(&asio_check_, [](uv_check_t* check) { - ((EventLoop*) check->data)->AsioCheck(); + // Poll ASIO handles before scheduling. + ((EventLoop*) check->data)->AsioPoll(); + ((EventLoop*) check->data)->Check(); // Schedules waiters. }); // NOTE: we unreference 'check_' so that when we run the event @@ -187,13 +183,6 @@ EventLoop::EventLoop() // considered alive. uv_unref((uv_handle_t*) &check_); - // TODO: we should consider event loop to be alive - // while this check is running. - // Before we find a better way to determine if there are - // still pending jobs in the asio io_context, - // we will just unreference this check. - uv_unref((uv_handle_t*) &asio_check_); - uv_async_init(&loop_, &async_, nullptr); // NOTE: see comments in 'RunUntil()' as to why we don't unreference @@ -208,9 +197,6 @@ EventLoop::~EventLoop() { uv_check_stop(&check_); uv_close((uv_handle_t*) &check_, nullptr); - uv_check_stop(&asio_check_); - uv_close((uv_handle_t*) &asio_check_, nullptr); - uv_close((uv_handle_t*) &async_, nullptr); // NOTE: ideally we can just run 'uv_run()' once now in order to @@ -235,14 +221,21 @@ EventLoop::~EventLoop() { static constexpr size_t ITERATIONS = 100000; size_t iterations = ITERATIONS; - auto alive = Alive(); + CHECK(uv_loop_alive(&loop_)) + << "should still have check and async handles to close"; - CHECK(alive) << "should still have check and async handles to close"; + size_t active_handles = 0; do { - alive = uv_run(&loop_, UV_RUN_NOWAIT); + active_handles = uv_run(&loop_, UV_RUN_NOWAIT); - if (alive && --iterations == 0) { + // Is needed for the following 'io_context.run()'. + io_context_.restart(); + + // BLOCKS! Returns 0 ONLY if there are no active handles left. + active_handles += io_context_.run_one(); + + if (active_handles > 0 && --iterations == 0) { std::ostringstream out; out << "destructing EventLoop with active handles:\n"; @@ -280,9 +273,12 @@ EventLoop::~EventLoop() { LOG(WARNING) << out.str(); + // NOTE: there's currently no way for us to print out active + // ASIO handles. + iterations = ITERATIONS; } - } while (alive); + } while (active_handles > 0); CHECK_EQ(uv_loop_close(&loop_), 0); } @@ -403,7 +399,7 @@ void EventLoop::Check() { //////////////////////////////////////////////////////////////////////// -void EventLoop::AsioCheck() { +void EventLoop::AsioPoll() { io_context().restart(); io_context().poll(); } diff --git a/eventuals/event-loop.h b/eventuals/event-loop.h index 6ff43013..46ca0f18 100644 --- a/eventuals/event-loop.h +++ b/eventuals/event-loop.h @@ -529,10 +529,6 @@ class EventLoop final : public Scheduler { template [[nodiscard]] auto Schedule(std::string&& name, E e); - bool Alive() { - return uv_loop_alive(&loop_); - } - bool Running() { return running_.load(); } @@ -1062,11 +1058,10 @@ class EventLoop final : public Scheduler { void Check(); - void AsioCheck(); + void AsioPoll(); uv_loop_t loop_ = {}; uv_check_t check_ = {}; - uv_check_t asio_check_ = {}; uv_async_t async_ = {}; asio::io_context io_context_; From ccd84ce35f4641ac7e63d41ee607425ed4e6d7f9 Mon Sep 17 00:00:00 2001 From: Nikita Nikolaenko <6870920@gmail.com> Date: Mon, 1 Aug 2022 12:28:55 +0200 Subject: [PATCH 7/7] Add explicit to constructors, deduce types for lambdas, rename Data -> Context --- eventuals/tcp-acceptor.h | 202 +++++++++++--------- eventuals/tcp-base.h | 176 ++++++++++-------- eventuals/tcp-socket.h | 188 ++++++++++--------- eventuals/tcp-ssl-context-builder.h | 4 +- eventuals/tcp-ssl-context.h | 13 +- eventuals/tcp-ssl-socket.h | 278 +++++++++++++++------------- 6 files changed, 484 insertions(+), 377 deletions(-) diff --git a/eventuals/tcp-acceptor.h b/eventuals/tcp-acceptor.h index e0fbf5b6..78c09c2a 100644 --- a/eventuals/tcp-acceptor.h +++ b/eventuals/tcp-acceptor.h @@ -15,7 +15,7 @@ namespace tcp { class Acceptor final { public: - Acceptor(Protocol protocol, EventLoop& loop = EventLoop::Default()) + explicit Acceptor(Protocol protocol, EventLoop& loop = EventLoop::Default()) : loop_(loop), protocol_(protocol), acceptor_(loop.io_context()) { @@ -42,7 +42,7 @@ class Acceptor final { [[nodiscard]] auto Bind(std::string&& ip, uint16_t port); - [[nodiscard]] auto Listen(const int backlog); + [[nodiscard]] auto Listen(int backlog); [[nodiscard]] auto Accept(SocketBase& socket); @@ -102,13 +102,17 @@ class Acceptor final { .interruptible() .raises() .context(this) - .start([](auto& acceptor, auto& k, Interrupt::Handler& handler) { + .start([](Acceptor*& acceptor, + auto& k, + std::optional& handler) { asio::post( acceptor->io_context(), [&]() { - if (handler.interrupt().Triggered()) { - k.Stop(); - return; + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + k.Stop(); + return; + } } if (acceptor->IsOpen()) { @@ -144,7 +148,7 @@ class Acceptor final { //////////////////////////////////////////////////////////////////////// [[nodiscard]] inline auto Acceptor::Bind(std::string&& ip, uint16_t port) { - struct Data { + struct Context { Acceptor* acceptor; std::string ip; uint16_t port; @@ -154,22 +158,26 @@ class Acceptor final { Eventual() .interruptible() .raises() - .context(Data{this, std::move(ip), port}) - .start([](auto& data, auto& k, Interrupt::Handler& handler) { + .context(Context{this, std::move(ip), port}) + .start([](Context& context, + auto& k, + std::optional& handler) { asio::post( - data.acceptor->io_context(), + context.acceptor->io_context(), [&]() { - if (handler.interrupt().Triggered()) { - k.Stop(); - return; + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + k.Stop(); + return; + } } - if (!data.acceptor->IsOpen()) { + if (!context.acceptor->IsOpen()) { k.Fail(std::runtime_error("Acceptor is closed")); return; } - if (data.acceptor->is_listening_) { + if (context.acceptor->is_listening_) { k.Fail( std::runtime_error( "Bind call is forbidden " @@ -180,16 +188,16 @@ class Acceptor final { asio::error_code error; asio::ip::tcp::endpoint endpoint; - switch (data.acceptor->protocol_) { + switch (context.acceptor->protocol_) { case Protocol::IPV4: endpoint = asio::ip::tcp::endpoint( - asio::ip::make_address_v4(data.ip, error), - data.port); + asio::ip::make_address_v4(context.ip, error), + context.port); break; case Protocol::IPV6: endpoint = asio::ip::tcp::endpoint( - asio::ip::make_address_v6(data.ip, error), - data.port); + asio::ip::make_address_v6(context.ip, error), + context.port); break; } @@ -199,7 +207,7 @@ class Acceptor final { return; } - data.acceptor->acceptor_handle().bind(endpoint, error); + context.acceptor->acceptor_handle().bind(endpoint, error); if (!error) { k.Start(); @@ -213,7 +221,7 @@ class Acceptor final { //////////////////////////////////////////////////////////////////////// [[nodiscard]] inline auto Acceptor::Listen(const int backlog) { - struct Data { + struct Context { Acceptor* acceptor; const int backlog; }; @@ -222,22 +230,26 @@ class Acceptor final { Eventual() .interruptible() .raises() - .context(Data{this, backlog}) - .start([](auto& data, auto& k, Interrupt::Handler& handler) { + .context(Context{this, backlog}) + .start([](Context& context, + auto& k, + std::optional& handler) { asio::post( - data.acceptor->io_context(), + context.acceptor->io_context(), [&]() { - if (handler.interrupt().Triggered()) { - k.Stop(); - return; + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + k.Stop(); + return; + } } - if (!data.acceptor->IsOpen()) { + if (!context.acceptor->IsOpen()) { k.Fail(std::runtime_error("Acceptor is closed")); return; } - if (data.acceptor->is_listening_) { + if (context.acceptor->is_listening_) { k.Fail( std::runtime_error( "Acceptor is already listening")); @@ -246,20 +258,21 @@ class Acceptor final { asio::error_code error; - data.acceptor->acceptor_handle().listen( - data.backlog, + context.acceptor->acceptor_handle().listen( + context.backlog, error); if (!error) { - data.acceptor->is_listening_ = true; - data.acceptor->port_.store(data.acceptor->acceptor_handle() - .local_endpoint() - .port()); - std::lock_guard lk(data.acceptor->ip_mutex_); - data.acceptor->ip_ = data.acceptor->acceptor_handle() - .local_endpoint() - .address() - .to_string(); + context.acceptor->is_listening_ = true; + context.acceptor->port_.store( + context.acceptor->acceptor_handle() + .local_endpoint() + .port()); + std::lock_guard lk(context.acceptor->ip_mutex_); + context.acceptor->ip_ = context.acceptor->acceptor_handle() + .local_endpoint() + .address() + .to_string(); k.Start(); } else { k.Fail(std::runtime_error(error.message())); @@ -271,7 +284,7 @@ class Acceptor final { //////////////////////////////////////////////////////////////////////// [[nodiscard]] inline auto Acceptor::Accept(SocketBase& socket) { - struct Data { + struct Context { Acceptor* acceptor; SocketBase* socket; @@ -288,68 +301,75 @@ class Acceptor final { Eventual() .interruptible() .raises() - .context(Data{this, &socket}) - .start([](auto& data, auto& k, Interrupt::Handler& handler) { + .context(Context{this, &socket}) + .start([](Context& context, + auto& k, + std::optional& handler) { using K = std::decay_t; - data.k = &k; - - handler.Install([&data]() { - asio::post(data.acceptor->io_context(), [&]() { - K& k = *static_cast(data.k); - - if (!data.started) { - data.completed = true; - k.Stop(); - return; - } else if (!data.completed) { - data.completed = true; - asio::error_code error; - data.acceptor->acceptor_handle().cancel(error); + context.k = &k; - if (!error) { + if (handler.has_value()) { + handler->Install([&context]() { + asio::post(context.acceptor->io_context(), [&]() { + K& k = *static_cast(context.k); + + if (!context.started) { + context.completed = true; k.Stop(); - } else { - k.Fail(std::runtime_error(error.message())); + return; + } else if (!context.completed) { + context.completed = true; + asio::error_code error; + context.acceptor->acceptor_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } } - } + }); }); - }); + } asio::post( - data.acceptor->io_context(), + context.acceptor->io_context(), [&]() { - if (!data.completed) { - if (handler.interrupt().Triggered()) { - data.completed = true; - k.Stop(); - return; + if (!context.completed) { + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + context.completed = true; + k.Stop(); + return; + } } - CHECK(!data.started); - data.started = true; + CHECK(!context.started); + context.started = true; - if (!data.acceptor->IsOpen()) { - data.completed = true; + if (!context.acceptor->IsOpen()) { + context.completed = true; k.Fail(std::runtime_error("Acceptor is closed")); return; } - if (!data.acceptor->is_listening_) { - data.completed = true; + if (!context.acceptor->is_listening_) { + context.completed = true; k.Fail(std::runtime_error("Acceptor is not listening")); return; } - if (data.socket->IsOpen()) { - data.completed = true; + if (context.socket->IsOpen()) { + context.completed = true; k.Fail( std::runtime_error( "Passed socket is not closed")); return; } - if (data.acceptor->protocol_ != data.socket->protocol_) { - data.completed = true; + if (context.acceptor->protocol_ + != context.socket->protocol_) { + context.completed = true; k.Fail( std::runtime_error( "Passed socket's protocol " @@ -357,15 +377,15 @@ class Acceptor final { return; } - data.acceptor->acceptor_handle().async_accept( - data.socket->socket_handle(), + context.acceptor->acceptor_handle().async_accept( + context.socket->socket_handle(), [&](const asio::error_code& error) { - if (!data.completed) { - data.completed = true; + if (!context.completed) { + context.completed = true; if (!error) { - data.socket->is_open_.store(true); - data.socket->is_connected_ = true; + context.socket->is_open_.store(true); + context.socket->is_connected_ = true; k.Start(); } else { k.Fail(std::runtime_error(error.message())); @@ -385,13 +405,17 @@ class Acceptor final { .interruptible() .raises() .context(this) - .start([](auto& acceptor, auto& k, Interrupt::Handler& handler) { + .start([](Acceptor*& acceptor, + auto& k, + std::optional& handler) { asio::post( acceptor->io_context(), [&]() { - if (handler.interrupt().Triggered()) { - k.Stop(); - return; + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + k.Stop(); + return; + } } if (!acceptor->IsOpen()) { diff --git a/eventuals/tcp-base.h b/eventuals/tcp-base.h index a2dbeba3..29fd3f2a 100644 --- a/eventuals/tcp-base.h +++ b/eventuals/tcp-base.h @@ -39,6 +39,18 @@ class SocketBase { public: SocketBase() = delete; + SocketBase(const SocketBase& that) = delete; + // TODO(folming): implement move. + // It should be possible since asio provides move + // operations on their socket implementation. + SocketBase(SocketBase&& that) = delete; + + SocketBase& operator=(const SocketBase& that) = delete; + // TODO(folming): implement move. + // It should be possible since asio provides move + // operations on their socket implementation. + SocketBase& operator=(SocketBase&& that) = delete; + virtual ~SocketBase() { CHECK(!IsOpen()) << "Close the socket before destructing"; } @@ -54,7 +66,7 @@ class SocketBase { // NOTE: It's not possible to implement a virtual auto // method, so we have to omit a Close() method here // and make its implementations in Socket and ssl::Socket. - // TODO: Implement Close() method here instead of being + // TODO(folming): Implement Close() method here instead of being // two different implementations for Socket and ssl::Socket. bool IsOpen() { @@ -62,7 +74,7 @@ class SocketBase { } protected: - SocketBase(Protocol protocol, EventLoop& loop = EventLoop::Default()) + explicit SocketBase(Protocol protocol, EventLoop& loop = EventLoop::Default()) : loop_(loop), protocol_(protocol) {} @@ -94,13 +106,17 @@ class SocketBase { .interruptible() .raises() .context(this) - .start([](auto& socket, auto& k, Interrupt::Handler& handler) { + .start([](SocketBase*& socket, + auto& k, + std::optional& handler) { asio::post( socket->io_context(), [&]() { - if (handler.interrupt().Triggered()) { - k.Stop(); - return; + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + k.Stop(); + return; + } } if (socket->IsOpen()) { @@ -136,7 +152,7 @@ class SocketBase { //////////////////////////////////////////////////////////////////////// [[nodiscard]] inline auto SocketBase::Bind(std::string&& ip, uint16_t port) { - struct Data { + struct Context { SocketBase* socket; std::string ip; uint16_t port; @@ -146,22 +162,26 @@ class SocketBase { Eventual() .interruptible() .raises() - .context(Data{this, std::move(ip), port}) - .start([](auto& data, auto& k, Interrupt::Handler& handler) { + .context(Context{this, std::move(ip), port}) + .start([](Context& context, + auto& k, + std::optional& handler) { asio::post( - data.socket->io_context(), + context.socket->io_context(), [&]() { - if (handler.interrupt().Triggered()) { - k.Stop(); - return; + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + k.Stop(); + return; + } } - if (!data.socket->socket_handle().is_open()) { + if (!context.socket->socket_handle().is_open()) { k.Fail(std::runtime_error("Socket is closed")); return; } - if (data.socket->is_connected_) { + if (context.socket->is_connected_) { k.Fail( std::runtime_error( "Bind call is forbidden " @@ -172,16 +192,16 @@ class SocketBase { asio::error_code error; asio::ip::tcp::endpoint endpoint; - switch (data.socket->protocol_) { + switch (context.socket->protocol_) { case Protocol::IPV4: endpoint = asio::ip::tcp::endpoint( - asio::ip::make_address_v4(data.ip, error), - data.port); + asio::ip::make_address_v4(context.ip, error), + context.port); break; case Protocol::IPV6: endpoint = asio::ip::tcp::endpoint( - asio::ip::make_address_v6(data.ip, error), - data.port); + asio::ip::make_address_v6(context.ip, error), + context.port); break; } @@ -191,7 +211,7 @@ class SocketBase { return; } - data.socket->socket_handle().bind(endpoint, error); + context.socket->socket_handle().bind(endpoint, error); if (!error) { k.Start(); @@ -207,7 +227,7 @@ class SocketBase { [[nodiscard]] inline auto SocketBase::Connect( std::string&& ip, uint16_t port) { - struct Data { + struct Context { SocketBase* socket; std::string ip; uint16_t port; @@ -225,53 +245,59 @@ class SocketBase { Eventual() .interruptible() .raises() - .context(Data{this, std::move(ip), port}) - .start([](auto& data, auto& k, Interrupt::Handler& handler) { + .context(Context{this, std::move(ip), port}) + .start([](Context& context, + auto& k, + std::optional& handler) { using K = std::decay_t; - data.k = &k; + context.k = &k; - handler.Install([&data]() { - asio::post(data.socket->io_context(), [&]() { - K& k = *static_cast(data.k); + if (handler.has_value()) { + handler->Install([&context]() { + asio::post(context.socket->io_context(), [&]() { + K& k = *static_cast(context.k); - if (!data.started) { - data.completed = true; - k.Stop(); - } else if (!data.completed) { - data.completed = true; - asio::error_code error; - data.socket->socket_handle().cancel(error); - - if (!error) { + if (!context.started) { + context.completed = true; k.Stop(); - } else { - k.Fail(std::runtime_error(error.message())); + } else if (!context.completed) { + context.completed = true; + asio::error_code error; + context.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } } - } + }); }); - }); + } asio::post( - data.socket->io_context(), + context.socket->io_context(), [&]() { - if (!data.completed) { - if (handler.interrupt().Triggered()) { - data.completed = true; - k.Stop(); - return; + if (!context.completed) { + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + context.completed = true; + k.Stop(); + return; + } } - CHECK(!data.started); - data.started = true; + CHECK(!context.started); + context.started = true; - if (!data.socket->socket_handle().is_open()) { - data.completed = true; + if (!context.socket->socket_handle().is_open()) { + context.completed = true; k.Fail(std::runtime_error("Socket is closed")); return; } - if (data.socket->is_connected_) { - data.completed = true; + if (context.socket->is_connected_) { + context.completed = true; k.Fail( std::runtime_error( "Socket is already connected")); @@ -281,33 +307,33 @@ class SocketBase { asio::error_code error; asio::ip::tcp::endpoint endpoint; - switch (data.socket->protocol_) { + switch (context.socket->protocol_) { case Protocol::IPV4: endpoint = asio::ip::tcp::endpoint( - asio::ip::make_address_v4(data.ip, error), - data.port); + asio::ip::make_address_v4(context.ip, error), + context.port); break; case Protocol::IPV6: endpoint = asio::ip::tcp::endpoint( - asio::ip::make_address_v6(data.ip, error), - data.port); + asio::ip::make_address_v6(context.ip, error), + context.port); break; } if (error) { - data.completed = true; + context.completed = true; k.Fail(std::runtime_error(error.message())); return; } - data.socket->socket_handle().async_connect( + context.socket->socket_handle().async_connect( endpoint, [&](const asio::error_code& error) { - if (!data.completed) { - data.completed = true; + if (!context.completed) { + context.completed = true; if (!error) { - data.socket->is_connected_ = true; + context.socket->is_connected_ = true; k.Start(); } else { k.Fail(std::runtime_error(error.message())); @@ -322,7 +348,7 @@ class SocketBase { //////////////////////////////////////////////////////////////////////// [[nodiscard]] inline auto SocketBase::Shutdown(ShutdownType shutdown_type) { - struct Data { + struct Context { SocketBase* socket; ShutdownType shutdown_type; }; @@ -331,27 +357,31 @@ class SocketBase { Eventual() .interruptible() .raises() - .context(Data{this, shutdown_type}) - .start([](auto& data, auto& k, Interrupt::Handler& handler) { + .context(Context{this, shutdown_type}) + .start([](Context& context, + auto& k, + std::optional& handler) { asio::post( - data.socket->io_context(), + context.socket->io_context(), [&]() { - if (handler.interrupt().Triggered()) { - k.Stop(); - return; + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + k.Stop(); + return; + } } - if (!data.socket->IsOpen()) { + if (!context.socket->IsOpen()) { k.Fail(std::runtime_error("Socket is closed")); return; } asio::error_code error; - data.socket->socket_handle().shutdown( + context.socket->socket_handle().shutdown( static_cast< asio::socket_base::shutdown_type>( - data.shutdown_type), + context.shutdown_type), error); if (!error) { diff --git a/eventuals/tcp-socket.h b/eventuals/tcp-socket.h index 14dd4901..dc9209bf 100644 --- a/eventuals/tcp-socket.h +++ b/eventuals/tcp-socket.h @@ -12,7 +12,7 @@ namespace tcp { class Socket final : public SocketBase { public: - Socket(Protocol protocol, EventLoop& loop = EventLoop::Default()) + explicit Socket(Protocol protocol, EventLoop& loop = EventLoop::Default()) : SocketBase(protocol, loop), socket_(loop.io_context()) {} @@ -28,6 +28,8 @@ class Socket final : public SocketBase { // operations on their socket implementation. Socket& operator=(Socket&& that) = delete; + ~Socket() override = default; + [[nodiscard]] auto Receive( void* destination, size_t destination_size, @@ -51,7 +53,7 @@ class Socket final : public SocketBase { void* destination, size_t destination_size, size_t bytes_to_read) { - struct Data { + struct Context { Socket* socket; void* destination; size_t destination_size; @@ -70,65 +72,71 @@ class Socket final : public SocketBase { Eventual() .interruptible() .raises() - .context(Data{this, destination, destination_size, bytes_to_read}) - .start([](auto& data, auto& k, Interrupt::Handler& handler) { + .context(Context{this, destination, destination_size, bytes_to_read}) + .start([](Context& context, + auto& k, + std::optional& handler) { using K = std::decay_t; - data.k = &k; - - handler.Install([&data]() { - asio::post(data.socket->io_context(), [&]() { - K& k = *static_cast(data.k); + context.k = &k; - if (!data.started) { - data.completed = true; - k.Stop(); - } else if (!data.completed) { - data.completed = true; - asio::error_code error; - data.socket->socket_handle().cancel(error); + if (handler.has_value()) { + handler->Install([&context]() { + asio::post(context.socket->io_context(), [&]() { + K& k = *static_cast(context.k); - if (!error) { + if (!context.started) { + context.completed = true; k.Stop(); - } else { - k.Fail(std::runtime_error(error.message())); + } else if (!context.completed) { + context.completed = true; + asio::error_code error; + context.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } } - } + }); }); - }); + } asio::post( - data.socket->io_context(), + context.socket->io_context(), [&]() { - if (!data.completed) { - if (handler.interrupt().Triggered()) { - data.completed = true; - k.Stop(); - return; + if (!context.completed) { + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + context.completed = true; + k.Stop(); + return; + } } - CHECK(!data.started); - data.started = true; + CHECK(!context.started); + context.started = true; - if (!data.socket->IsOpen()) { - data.completed = true; + if (!context.socket->IsOpen()) { + context.completed = true; k.Fail(std::runtime_error("Socket is closed")); return; } - if (!data.socket->is_connected_) { - data.completed = true; + if (!context.socket->is_connected_) { + context.completed = true; k.Fail(std::runtime_error("Socket is not connected")); return; } // Do not allow to read more than destination_size. - data.bytes_to_read = std::min( - data.bytes_to_read, - data.destination_size); + context.bytes_to_read = std::min( + context.bytes_to_read, + context.destination_size); // Do not call async_read() if there're 0 bytes to be read. - if (data.bytes_to_read == 0) { - data.completed = true; + if (context.bytes_to_read == 0) { + context.completed = true; k.Start(0); return; } @@ -136,12 +144,14 @@ class Socket final : public SocketBase { // Start receiving. // Will only succeed after the supplied buffer is full. asio::async_read( - data.socket->socket_handle(), - asio::buffer(data.destination, data.bytes_to_read), + context.socket->socket_handle(), + asio::buffer( + context.destination, + context.bytes_to_read), [&](const asio::error_code& error, size_t bytes_transferred) { - if (!data.completed) { - data.completed = true; + if (!context.completed) { + context.completed = true; if (!error) { k.Start(bytes_transferred); @@ -160,7 +170,7 @@ class Socket final : public SocketBase { [[nodiscard]] inline auto Socket::Send( const void* source, size_t source_size) { - struct Data { + struct Context { Socket* socket; const void* source; size_t source_size; @@ -178,61 +188,67 @@ class Socket final : public SocketBase { Eventual() .interruptible() .raises() - .context(Data{this, source, source_size}) - .start([](auto& data, auto& k, Interrupt::Handler& handler) { + .context(Context{this, source, source_size}) + .start([](Context& context, + auto& k, + std::optional& handler) { using K = std::decay_t; - data.k = &k; - - handler.Install([&data]() { - asio::post(data.socket->io_context(), [&]() { - K& k = *static_cast(data.k); + context.k = &k; - if (!data.started) { - data.completed = true; - k.Stop(); - } else if (!data.completed) { - data.completed = true; - asio::error_code error; - data.socket->socket_handle().cancel(error); + if (handler.has_value()) { + handler->Install([&context]() { + asio::post(context.socket->io_context(), [&]() { + K& k = *static_cast(context.k); - if (!error) { + if (!context.started) { + context.completed = true; k.Stop(); - } else { - k.Fail(std::runtime_error(error.message())); + } else if (!context.completed) { + context.completed = true; + asio::error_code error; + context.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } } - } + }); }); - }); + } asio::post( - data.socket->io_context(), + context.socket->io_context(), [&]() { - if (!data.completed) { - if (handler.interrupt().Triggered()) { - data.completed = true; - k.Stop(); - return; + if (!context.completed) { + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + context.completed = true; + k.Stop(); + return; + } } - CHECK(!data.started); - data.started = true; + CHECK(!context.started); + context.started = true; - if (!data.socket->IsOpen()) { - data.completed = true; + if (!context.socket->IsOpen()) { + context.completed = true; k.Fail(std::runtime_error("Socket is closed")); return; } - if (!data.socket->is_connected_) { - data.completed = true; + if (!context.socket->is_connected_) { + context.completed = true; k.Fail(std::runtime_error("Socket is not connected")); return; } // Do not call async_write() // if there're 0 bytes to be sent. - if (data.source_size == 0) { - data.completed = true; + if (context.source_size == 0) { + context.completed = true; k.Start(0); return; } @@ -240,12 +256,12 @@ class Socket final : public SocketBase { // Will only succeed after // writing all of the data to socket. asio::async_write( - data.socket->socket_handle(), - asio::buffer(data.source, data.source_size), + context.socket->socket_handle(), + asio::buffer(context.source, context.source_size), [&](const asio::error_code& error, size_t bytes_transferred) { - if (!data.completed) { - data.completed = true; + if (!context.completed) { + context.completed = true; if (!error) { k.Start(bytes_transferred); @@ -267,13 +283,17 @@ class Socket final : public SocketBase { .interruptible() .raises() .context(this) - .start([](auto& socket, auto& k, Interrupt::Handler& handler) { + .start([](Socket*& socket, + auto& k, + std::optional& handler) { asio::post( socket->io_context(), [&]() { - if (handler.interrupt().Triggered()) { - k.Stop(); - return; + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + k.Stop(); + return; + } } if (!socket->IsOpen()) { diff --git a/eventuals/tcp-ssl-context-builder.h b/eventuals/tcp-ssl-context-builder.h index 8eb39ae9..736cb644 100644 --- a/eventuals/tcp-ssl-context-builder.h +++ b/eventuals/tcp-ssl-context-builder.h @@ -1,6 +1,8 @@ #pragma once +#include "asio/ssl.hpp" #include "eventuals/builder.h" +#include "eventuals/tcp-ssl-context.h" //////////////////////////////////////////////////////////////////////// @@ -151,7 +153,6 @@ class SSLContext::_Builder final : public builder::Builder { public: ~_Builder() override = default; - public: auto ssl_version( SSLVersion ssl_version) && { static_assert(!has_method_, "Duplicate 'ssl_version'"); @@ -178,6 +179,7 @@ class SSLContext::_Builder final : public builder::Builder { std::move(tmp_dh_file_)); } + // This function is used to add one trusted certification authority // from a memory buffer. auto certificate_authority( diff --git a/eventuals/tcp-ssl-context.h b/eventuals/tcp-ssl-context.h index 5a901a4f..eba57f07 100644 --- a/eventuals/tcp-ssl-context.h +++ b/eventuals/tcp-ssl-context.h @@ -1,6 +1,7 @@ #pragma once #include "asio/ssl.hpp" +#include "glog/logging.h" //////////////////////////////////////////////////////////////////////// @@ -61,14 +62,14 @@ enum class SSLVersion { class SSLContext final { public: SSLContext(const SSLContext& that) = delete; - SSLContext(SSLContext&& that) + SSLContext(SSLContext&& that) noexcept : context_(std::move(that.context_)), moved_out_(that.moved_out_) { that.moved_out_ = true; } SSLContext& operator=(const SSLContext& that) = delete; - SSLContext& operator=(SSLContext&& that) { + SSLContext& operator=(SSLContext&& that) noexcept { context_ = std::move(that.context_); moved_out_ = that.moved_out_; that.moved_out_ = true; @@ -76,12 +77,14 @@ class SSLContext final { return *this; } + ~SSLContext() = default; + // Constructs a new ssl::SSLContext "builder" with the default // undefined values. static auto Builder(); private: - SSLContext(SSLVersion ssl_version) + explicit SSLContext(SSLVersion ssl_version) : context_(static_cast(ssl_version)) {} asio::ssl::context& ssl_context_handle() { @@ -127,4 +130,6 @@ class SSLContext final { //////////////////////////////////////////////////////////////////////// -#include "tcp-ssl-context-builder.h" +#include "eventuals/tcp-ssl-context-builder.h" + +//////////////////////////////////////////////////////////////////////// diff --git a/eventuals/tcp-ssl-socket.h b/eventuals/tcp-ssl-socket.h index 03149a2d..546e2ae8 100644 --- a/eventuals/tcp-ssl-socket.h +++ b/eventuals/tcp-ssl-socket.h @@ -50,6 +50,8 @@ class Socket final : public SocketBase { // operations on their socket implementation. Socket& operator=(Socket&& that) = delete; + ~Socket() override = default; + [[nodiscard]] auto Handshake(HandshakeType handshake_type); [[nodiscard]] auto Receive( @@ -80,7 +82,7 @@ class Socket final : public SocketBase { //////////////////////////////////////////////////////////////////////// [[nodiscard]] inline auto Socket::Handshake(HandshakeType handshake_type) { - struct Data { + struct Context { Socket* socket; HandshakeType handshake_type; @@ -97,76 +99,82 @@ class Socket final : public SocketBase { Eventual() .interruptible() .raises() - .context(Data{this, handshake_type}) - .start([](auto& data, auto& k, Interrupt::Handler& handler) { + .context(Context{this, handshake_type}) + .start([](Context& context, + auto& k, + std::optional& handler) { using K = std::decay_t; - data.k = &k; - - handler.Install([&data]() { - asio::post(data.socket->io_context(), [&]() { - K& k = *static_cast(data.k); + context.k = &k; - if (!data.started) { - data.completed = true; - k.Stop(); - } else if (!data.completed) { - data.completed = true; - asio::error_code error; - data.socket->socket_handle().cancel(error); + if (handler.has_value()) { + handler->Install([&context]() { + asio::post(context.socket->io_context(), [&]() { + K& k = *static_cast(context.k); - if (!error) { + if (!context.started) { + context.completed = true; k.Stop(); - } else { - k.Fail(std::runtime_error(error.message())); + } else if (!context.completed) { + context.completed = true; + asio::error_code error; + context.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } } - } + }); }); - }); + } asio::post( - data.socket->io_context(), + context.socket->io_context(), [&]() { - if (!data.completed) { - if (handler.interrupt().Triggered()) { - data.completed = true; - k.Stop(); - return; + if (!context.completed) { + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + context.completed = true; + k.Stop(); + return; + } } - CHECK(!data.started); - data.started = true; + CHECK(!context.started); + context.started = true; - if (!data.socket->IsOpen()) { - data.completed = true; + if (!context.socket->IsOpen()) { + context.completed = true; k.Fail(std::runtime_error("Socket is closed")); return; } - if (!data.socket->is_connected_) { - data.completed = true; + if (!context.socket->is_connected_) { + context.completed = true; k.Fail(std::runtime_error("Socket is not connected")); return; } - if (data.socket->completed_handshake_) { - data.completed = true; + if (context.socket->completed_handshake_) { + context.completed = true; k.Fail( std::runtime_error( "Handshake was already completed")); return; } - data.socket->stream_handle().async_handshake( + context.socket->stream_handle().async_handshake( static_cast< asio::ssl::stream< asio::ip::tcp::socket>::handshake_type>( - data.handshake_type), + context.handshake_type), [&](const asio::error_code& error) { - if (!data.completed) { - data.completed = true; + if (!context.completed) { + context.completed = true; if (!error) { - data.socket->completed_handshake_ = true; + context.socket->completed_handshake_ = true; k.Start(); } else { k.Fail(std::runtime_error(error.message())); @@ -184,7 +192,7 @@ class Socket final : public SocketBase { void* destination, size_t destination_size, size_t bytes_to_read) { - struct Data { + struct Context { Socket* socket; void* destination; size_t destination_size; @@ -203,59 +211,65 @@ class Socket final : public SocketBase { Eventual() .interruptible() .raises() - .context(Data{this, destination, destination_size, bytes_to_read}) - .start([](auto& data, auto& k, Interrupt::Handler& handler) { + .context(Context{this, destination, destination_size, bytes_to_read}) + .start([](Context& context, + auto& k, + std::optional& handler) { using K = std::decay_t; - data.k = &k; + context.k = &k; - handler.Install([&data]() { - asio::post(data.socket->io_context(), [&]() { - K& k = *static_cast(data.k); + if (handler.has_value()) { + handler->Install([&context]() { + asio::post(context.socket->io_context(), [&]() { + K& k = *static_cast(context.k); - if (!data.started) { - data.completed = true; - k.Stop(); - } else if (!data.completed) { - data.completed = true; - asio::error_code error; - data.socket->socket_handle().cancel(error); - - if (!error) { + if (!context.started) { + context.completed = true; k.Stop(); - } else { - k.Fail(std::runtime_error(error.message())); + } else if (!context.completed) { + context.completed = true; + asio::error_code error; + context.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } } - } + }); }); - }); + } asio::post( - data.socket->io_context(), + context.socket->io_context(), [&]() { - if (!data.completed) { - if (handler.interrupt().Triggered()) { - data.completed = true; - k.Stop(); - return; + if (!context.completed) { + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + context.completed = true; + k.Stop(); + return; + } } - CHECK(!data.started); - data.started = true; + CHECK(!context.started); + context.started = true; - if (!data.socket->IsOpen()) { - data.completed = true; + if (!context.socket->IsOpen()) { + context.completed = true; k.Fail(std::runtime_error("Socket is closed")); return; } - if (!data.socket->is_connected_) { - data.completed = true; + if (!context.socket->is_connected_) { + context.completed = true; k.Fail(std::runtime_error("Socket is not connected")); return; } - if (!data.socket->completed_handshake_) { - data.completed = true; + if (!context.socket->completed_handshake_) { + context.completed = true; k.Fail( std::runtime_error( "Must Handshake before trying to Receive")); @@ -263,13 +277,13 @@ class Socket final : public SocketBase { } // Do not allow to read more than destination_size. - data.bytes_to_read = std::min( - data.bytes_to_read, - data.destination_size); + context.bytes_to_read = std::min( + context.bytes_to_read, + context.destination_size); // Do not call async_read() if there're 0 bytes to be read. - if (data.bytes_to_read == 0) { - data.completed = true; + if (context.bytes_to_read == 0) { + context.completed = true; k.Start(0); return; } @@ -277,12 +291,14 @@ class Socket final : public SocketBase { // Start receiving. // Will only succeed after the supplied buffer is full. asio::async_read( - data.socket->stream_handle(), - asio::buffer(data.destination, data.bytes_to_read), + context.socket->stream_handle(), + asio::buffer( + context.destination, + context.bytes_to_read), [&](const asio::error_code& error, size_t bytes_transferred) { - if (!data.completed) { - data.completed = true; + if (!context.completed) { + context.completed = true; if (!error) { k.Start(bytes_transferred); @@ -301,7 +317,7 @@ class Socket final : public SocketBase { [[nodiscard]] inline auto Socket::Send( const void* source, size_t source_size) { - struct Data { + struct Context { Socket* socket; const void* source; size_t source_size; @@ -319,59 +335,65 @@ class Socket final : public SocketBase { Eventual() .interruptible() .raises() - .context(Data{this, source, source_size}) - .start([](auto& data, auto& k, Interrupt::Handler& handler) { + .context(Context{this, source, source_size}) + .start([](Context& context, + auto& k, + std::optional& handler) { using K = std::decay_t; - data.k = &k; + context.k = &k; - handler.Install([&data]() { - asio::post(data.socket->io_context(), [&]() { - K& k = *static_cast(data.k); - - if (!data.started) { - data.completed = true; - k.Stop(); - } else if (!data.completed) { - data.completed = true; - asio::error_code error; - data.socket->socket_handle().cancel(error); + if (handler.has_value()) { + handler->Install([&context]() { + asio::post(context.socket->io_context(), [&]() { + K& k = *static_cast(context.k); - if (!error) { + if (!context.started) { + context.completed = true; k.Stop(); - } else { - k.Fail(std::runtime_error(error.message())); + } else if (!context.completed) { + context.completed = true; + asio::error_code error; + context.socket->socket_handle().cancel(error); + + if (!error) { + k.Stop(); + } else { + k.Fail(std::runtime_error(error.message())); + } } - } + }); }); - }); + } asio::post( - data.socket->io_context(), + context.socket->io_context(), [&]() { - if (!data.completed) { - if (handler.interrupt().Triggered()) { - data.completed = true; - k.Stop(); - return; + if (!context.completed) { + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + context.completed = true; + k.Stop(); + return; + } } - CHECK(!data.started); - data.started = true; + CHECK(!context.started); + context.started = true; - if (!data.socket->IsOpen()) { - data.completed = true; + if (!context.socket->IsOpen()) { + context.completed = true; k.Fail(std::runtime_error("Socket is closed")); return; } - if (!data.socket->is_connected_) { - data.completed = true; + if (!context.socket->is_connected_) { + context.completed = true; k.Fail(std::runtime_error("Socket is not connected")); return; } - if (!data.socket->completed_handshake_) { - data.completed = true; + if (!context.socket->completed_handshake_) { + context.completed = true; k.Fail( std::runtime_error( "Must Handshake before trying to Send")); @@ -380,8 +402,8 @@ class Socket final : public SocketBase { // Do not call async_write() // if there're 0 bytes to be sent. - if (data.source_size == 0) { - data.completed = true; + if (context.source_size == 0) { + context.completed = true; k.Start(0); return; } @@ -389,12 +411,12 @@ class Socket final : public SocketBase { // Will only succeed after // writing all of the data to socket. asio::async_write( - data.socket->stream_handle(), - asio::buffer(data.source, data.source_size), + context.socket->stream_handle(), + asio::buffer(context.source, context.source_size), [&](const asio::error_code& error, size_t bytes_transferred) { - if (!data.completed) { - data.completed = true; + if (!context.completed) { + context.completed = true; if (!error) { k.Start(bytes_transferred); @@ -416,13 +438,17 @@ class Socket final : public SocketBase { .interruptible() .raises() .context(this) - .start([](auto& socket, auto& k, Interrupt::Handler& handler) { + .start([](Socket*& socket, + auto& k, + std::optional& handler) { asio::post( socket->io_context(), [&]() { - if (handler.interrupt().Triggered()) { - k.Stop(); - return; + if (handler.has_value()) { + if (handler->interrupt().Triggered()) { + k.Stop(); + return; + } } if (!socket->IsOpen()) {