diff --git a/CMakeLists.txt b/CMakeLists.txt index 0748800..6a72e1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,21 +58,21 @@ target_link_libraries(playground PRIVATE spdlog::spdlog) enable_testing() add_executable( - tcp_client_test - tests/tcp_client.cpp + tcp_test + tests/tcp.cpp src/tcp.cpp ) target_link_libraries( - tcp_client_test + tcp_test spdlog::spdlog GTest::gtest_main ) target_include_directories( - tcp_client_test + tcp_test PRIVATE src ) include(GoogleTest) -gtest_discover_tests(tcp_client_test) +gtest_discover_tests(tcp_test) diff --git a/src/tcp.cpp b/src/tcp.cpp index d15afa1..21f069a 100644 --- a/src/tcp.cpp +++ b/src/tcp.cpp @@ -63,6 +63,11 @@ NonBlockingServer::NonBlockingServer(ServerSettings settings) : m_settings{settings}, m_serverAddress(get_local_address(settings.port)) { } +NonBlockingServer::~NonBlockingServer() { + close_client_socket(); + close_server_socket(); +} + std::expected NonBlockingServer::start_listening() { /// Create socket @@ -254,6 +259,10 @@ void NonBlockingServer::close_client_socket() { // +NonBlockingClient::~NonBlockingClient() { + close_socket(); +} + [[nodiscard]] std::expected NonBlockingClient::connect(HostString host, uint16_t port) { /// Resolve host @@ -280,14 +289,14 @@ NonBlockingClient::connect(HostString host, uint16_t port) { return std::unexpected{errno}; } - m_startedNewConAttemt = true; + m_startedNewConAttempt = true; return {}; } [[nodiscard]] std::expected NonBlockingClient::get_last_connection_status() { - if (!m_startedNewConAttemt) return m_lastKnownConStatus; + if (!m_startedNewConAttempt) return m_lastKnownConStatus; /// Check if connect operation has been completed @@ -295,7 +304,7 @@ NonBlockingClient::get_last_connection_status() { if (!pendingOpRes) return std::unexpected{pendingOpRes.error()}; if (!pendingOpRes.value()) return EINPROGRESS; - m_startedNewConAttemt = false; + m_startedNewConAttempt = false; /// Check for connection errors @@ -317,6 +326,7 @@ NonBlockingClient::get_last_connection_status() { } void NonBlockingClient::disconnect() { + m_lastKnownConStatus = ENOTCONN; close_socket(); } @@ -325,7 +335,7 @@ NonBlockingClient::send(std::span data) { // TODO: Do we need this? auto conRes = get_last_connection_status(); if (!conRes) return std::unexpected{conRes.error()}; - if (conRes != EISCONN) { + if (conRes.value() != EISCONN) { spdlog::error("tcp::NonBlockingClient: Socket not connected"); return std::unexpected{ENOTCONN}; } @@ -345,7 +355,7 @@ NonBlockingClient::send(std::span data) { bool NonBlockingClient::data_available() { auto conRes = get_last_connection_status(); if (!conRes) return false; - if (conRes != EISCONN) return false; + if (conRes.value() != EISCONN) return false; int ret = poll(&m_pfdIn, 1, 0); if (ret < 0) { @@ -354,7 +364,6 @@ bool NonBlockingClient::data_available() { return false; } - return (m_pfdIn.revents & POLLIN); } @@ -425,6 +434,7 @@ NonBlockingClient::new_error_codes_available() { return std::unexpected{errno}; } + // ret == 0 means the operation timed out, i.e., no new events return (ret != 0); } diff --git a/src/tcp.hpp b/src/tcp.hpp index 2a8bc20..dd4e14a 100644 --- a/src/tcp.hpp +++ b/src/tcp.hpp @@ -39,13 +39,13 @@ struct ServerSettings { class NonBlockingServer { public: NonBlockingServer(ServerSettings settings); + ~NonBlockingServer(); [[nodiscard]] std::expected start_listening(); bool next_client_available(); [[nodiscard]] std::expected accept_next_client(); void disconnect(); - // bool socket_closed() const; [[nodiscard]] std::expected send(std::span data) const; @@ -86,15 +86,16 @@ private: class NonBlockingClient { public: + ~NonBlockingClient(); + // clang-format off [[nodiscard]] std::expected connect(HostString host, uint16_t port); - // clang-format off + // clang-format on void disconnect(); - [[nodiscard]] std::expected - get_last_connection_status(); + [[nodiscard]] std::expected get_last_connection_status(); bool data_available(); @@ -106,8 +107,8 @@ private: pollfd m_pfdOut = {.fd = -1, .events = POLLOUT}; pollfd m_pfdIn = {.fd = -1, .events = POLLIN}; - bool m_startedNewConAttemt = false; - int m_lastKnownConStatus = 0; + bool m_startedNewConAttempt = false; + int m_lastKnownConStatus = ENOTCONN; void close_socket(); diff --git a/tests/tcp.cpp b/tests/tcp.cpp new file mode 100644 index 0000000..468260a --- /dev/null +++ b/tests/tcp.cpp @@ -0,0 +1,168 @@ +#include "tcp.hpp" + +#include + + +TEST(TcpServer, Accept) { + tcp::NonBlockingServer server{{.port = 1234}}; + tcp::NonBlockingClient client; + + auto lisRes = server.start_listening(); + + EXPECT_FALSE(server.next_client_available()); + + auto conRes = client.connect({"localhost"}, 1234); + + EXPECT_TRUE(server.next_client_available()); + + auto accRes = server.accept_next_client(); + + EXPECT_FALSE(server.next_client_available()); +} + +TEST(TcpClient, Connect) { + tcp::NonBlockingServer server{{.port = 1234}}; + tcp::NonBlockingClient client; + + /// Default connection status, i.e., before attempting connection + + auto statRes1 = client.get_last_connection_status(); + if (!statRes1) return; + EXPECT_EQ(statRes1.value(), ENOTCONN); + + /// Connection status when connection is not acknowledged + + auto conRes = client.connect({"localhost"}, 1234); + + auto statRes2 = client.get_last_connection_status(); + if (!statRes2) return; + EXPECT_EQ(statRes2.value(), ECONNREFUSED); + + /// Connection status when connection is acknowledged + + auto lisRes = server.start_listening(); + + auto conRes2 = client.connect({"localhost"}, 1234); + + auto statRes3 = client.get_last_connection_status(); + if (!statRes3) return; + EXPECT_EQ(statRes3.value(), EISCONN); + + /// Connection status changes when reattempting connection + + auto conRes3 = client.connect({"localhost"}, 12345); + + auto statRes4 = client.get_last_connection_status(); + if (!statRes4) return; + EXPECT_EQ(statRes4.value(), ECONNREFUSED); +} + +TEST(TcpClient, Reonnect) { + tcp::NonBlockingServer server1{{.port = 1234}}; + tcp::NonBlockingServer server2{{.port = 2345}}; + tcp::NonBlockingClient client; + + auto lisRes1 = server1.start_listening(); + auto lisRes2 = server2.start_listening(); + + auto conRes1 = client.connect({"localhost"}, 1234); + + auto statRes1 = client.get_last_connection_status(); + if (!statRes1) return; + EXPECT_EQ(statRes1.value(), EISCONN); + + auto conRes2 = client.connect({"localhost"}, 2345); + + auto statRes2 = client.get_last_connection_status(); + if (!statRes2) return; + EXPECT_EQ(statRes2.value(), EISCONN); +} + +TEST(TcpClient, DataAvailable) { + tcp::NonBlockingServer server{{.port = 1234}}; + tcp::NonBlockingClient client; + + EXPECT_FALSE(client.data_available()); + + auto lisRes = server.start_listening(); + auto conRes = client.connect({"localhost"}, 1234); + auto accRes = server.accept_next_client(); + + EXPECT_EQ(client.get_last_connection_status().value(), EISCONN); + EXPECT_FALSE(client.data_available()); + + std::array txMsg = {"Hello, World!"}; + auto sendRes = server.send(std::as_bytes(std::span(txMsg))); + + EXPECT_EQ(sendRes.value(), txMsg.size()); + EXPECT_TRUE(client.data_available()); + + std::array rxBuffer; + auto recvRes = client.recv(std::as_writable_bytes(std::span(rxBuffer))); + EXPECT_EQ(recvRes.value(), txMsg.size()); + + EXPECT_FALSE(client.data_available()); +} + +TEST(TcpServer, DataAvailable) { + tcp::NonBlockingServer server{{.port = 1234}}; + tcp::NonBlockingClient client; + + EXPECT_FALSE(server.data_available()); + + auto lisRes = server.start_listening(); + auto conRes = client.connect({"localhost"}, 1234); + auto accRes = server.accept_next_client(); + + EXPECT_FALSE(server.data_available()); + + std::array txMsg = {"Hello, World!"}; + auto sendRes = client.send(std::as_bytes(std::span(txMsg))); + EXPECT_EQ(sendRes.value(), txMsg.size()); + EXPECT_TRUE(server.data_available()); + + std::array rxBuffer; + auto recvRes = server.recv(std::as_writable_bytes(std::span(rxBuffer))); + EXPECT_EQ(recvRes.value(), txMsg.size()); + EXPECT_FALSE(server.data_available()); +} + +TEST(TcpClientServer, ClientSendServerReceive) { + tcp::NonBlockingServer server{{.port = 1234}}; + tcp::NonBlockingClient client; + + auto lisRes = server.start_listening(); + auto conRes = client.connect({"localhost"}, 1234); + auto accRes = server.accept_next_client(); + + std::array txMsg = {"Hello, World!"}; + auto sendRes = client.send(std::as_bytes(std::span(txMsg))); + + EXPECT_EQ(sendRes.value(), txMsg.size()); + + std::array rxBuffer; + auto recvRes = server.recv(std::as_writable_bytes(std::span(rxBuffer))); + + EXPECT_EQ(recvRes.value(), txMsg.size()); + EXPECT_EQ(std::strcmp(txMsg.data(), rxBuffer.data()), 0); +} + +TEST(TcpClientServer, ClientReceiveServerSend) { + tcp::NonBlockingServer server{{.port = 1234}}; + tcp::NonBlockingClient client; + + auto lisRes = server.start_listening(); + auto conRes = client.connect({"localhost"}, 1234); + auto accRes = server.accept_next_client(); + + std::array txMsg = {"Hello, World!"}; + auto sendRes = server.send(std::as_bytes(std::span(txMsg))); + + EXPECT_EQ(sendRes.value(), txMsg.size()); + + std::array rxBuffer; + auto recvRes = client.recv(std::as_writable_bytes(std::span(rxBuffer))); + + EXPECT_EQ(recvRes.value(), txMsg.size()); + EXPECT_EQ(std::strcmp(txMsg.data(), rxBuffer.data()), 0); +} diff --git a/tests/tcp_client.cpp b/tests/tcp_client.cpp deleted file mode 100644 index c641b77..0000000 --- a/tests/tcp_client.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "tcp.hpp" - -#include - -TEST(TcpClient, Connect) { - tcp::NonBlockingClient client; -} -