#include "tcp.hpp" // C stdlib includes #include #include #include // Library includes #include namespace { std::expected resolve_remote_address(const char* host, uint16_t port) { hostent* server = gethostbyname(host); if (server == nullptr) { spdlog::error("tcp: get_host_by_name() failed with h_errno={}", h_errno); return std::unexpected{h_errno}; } sockaddr_in address{}; address.sin_family = AF_INET; address.sin_port = htons(port); bcopy((char*)server->h_addr, (char*)&address.sin_addr.s_addr, server->h_length); return address; } inline auto get_local_address(uint16_t port) -> sockaddr_in { sockaddr_in address{}; address.sin_addr.s_addr = htonl(INADDR_ANY); address.sin_family = AF_INET; address.sin_port = htons(port); return address; } } // namespace namespace tcp { // // // Non blocking server // // NonBlockingServer::~NonBlockingServer() { close_client_socket(); close_server_socket(); } std::expected NonBlockingServer::start_listening(uint16_t port) { /// Create socket close_server_socket(); if (auto res = create_socket(); !res) { return std::unexpected{res.error()}; } /// Bin socket and start listening const struct sockaddr_in serverAddress = get_local_address(port); if (::bind(m_serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) != 0) { spdlog::error("tcp::NonBlockingServer: bind() failed with errno={}", errno); close(m_serverSocket); return std::unexpected{errno}; }; if (::listen(m_serverSocket, 1) != 0) { spdlog::error("tcp::NonBlockingServer: listen() failed with errno={}", errno); close(m_serverSocket); return std::unexpected{errno}; } return {}; } bool NonBlockingServer::next_client_available() { int ret = poll(&m_serverPfdIn, 1, 0); if (ret < 0) { spdlog::error("tcp::NonBlockingServer: poll() failed with errno={}", errno); return false; } return (m_serverPfdIn.revents & POLLIN); } [[nodiscard]] std::expected NonBlockingServer::accept_next_client() { disconnect(); socklen_t clientAddrLen = sizeof(m_clientAddress); int result = ::accept(m_serverSocket, (struct sockaddr*)&m_clientAddress, &clientAddrLen); if (result < 0) return std::unexpected{errno}; m_clientSocket = result; m_clientPfdIn.fd = m_clientSocket; return {}; } void NonBlockingServer::disconnect() { close_client_socket(); } // TODO: Do we need this? // bool NonBlockingServer::socket_closed() const { // } [[nodiscard]] std::expected, int> NonBlockingServer::get_client_ip_as_string() { std::array addrStr; if (inet_ntop(AF_INET, &((struct sockaddr_in*)&m_clientAddress)->sin_addr, addrStr.data(), addrStr.size()) == nullptr) { spdlog::error( "tcp::NonBlockingServer: inet_ntop() failed with errno={}", errno); return std::unexpected{errno}; } // if (inet_ntoa_r(((struct sockaddr_in*)&m_clientAddress)->sin_addr, // addrStr.data(), addrStr.size()) == nullptr) { // return std::unexpected{errno}; // } return addrStr; } [[nodiscard]] std::expected NonBlockingServer::send(std::span data) { const int bytesWritten = ::send(m_clientSocket, (const char*)data.data(), data.size(), 0); if (bytesWritten <= 0) { spdlog::error("tcp::NonBlockingServer: send() failed with errno={}", errno); return std::unexpected{errno}; } return bytesWritten; } [[nodiscard]] std::expected NonBlockingServer::recv(std::span buffer) { const int bytesReceived = ::recv(m_clientSocket, (char*)buffer.data(), buffer.size(), 0); if (bytesReceived < 0) { spdlog::error("tcp::NonBlockingServer: recv() failed with errno={}", errno); return std::unexpected{errno}; } return {bytesReceived}; } bool NonBlockingServer::data_available() { int ret = poll(&m_clientPfdIn, 1, 0); if (ret < 0) { spdlog::error("tcp::NonBlockingServer: poll() failed with errno={}", errno); return false; } return (m_clientPfdIn.revents & POLLIN); } [[nodiscard]] std::expected NonBlockingServer::create_socket() { m_serverSocket = socket(AF_INET, SOCK_STREAM, 0); if (m_serverSocket < 1) { spdlog::error("tcp::NonBlockingServer: socket() failed with errno={}", errno); return std::unexpected{errno}; } m_serverPfdIn.fd = m_serverSocket; auto currentFlags = fcntl(m_serverSocket, F_GETFL, 0); if (currentFlags == -1) { spdlog::error("tcp::NonBlockingServer: fcntl() failed with errno={}", errno); close_server_socket(); return std::unexpected{errno}; } if (fcntl(m_serverSocket, F_SETFL, (currentFlags) | O_NONBLOCK) == -1) { spdlog::error("tcp::NonBlockingServer: fcntl() failed with errno={}", errno); close_server_socket(); return std::unexpected{errno}; } int flag = 1; if (::setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) < 0) { spdlog::error( "tcp::NonBlockingServer: setsockopt() failed with errno={}", errno); close_server_socket(); return std::unexpected{errno}; } return {}; } void NonBlockingServer::close_server_socket() { if (shutdown(m_serverSocket, 0) < 0) { spdlog::debug("tcp::NonBlockingServer: shutdown() failed with errno={}", errno); } if (close(m_serverSocket) < 0) { spdlog::debug("tcp::NonBlockingServer: close() failed with errno={}", errno); } m_serverSocket = -1; m_serverPfdIn.fd = -1; } void NonBlockingServer::close_client_socket() { if (shutdown(m_clientSocket, 0) < 0) { spdlog::debug("tcp::NonBlockingServer: shutdown() failed with errno={}", errno); } if (close(m_clientSocket) < 0) { spdlog::debug("tcp::NonBlockingServer: close() failed with errno={}", errno); } m_clientSocket = -1; m_clientPfdIn.fd = -1; } // // // Non blocking client // // NonBlockingClient::~NonBlockingClient() { close_socket(); } [[nodiscard]] std::expected NonBlockingClient::connect(const HostString& host, uint16_t port) { /// Resolve host auto addrRes = resolve_remote_address(host.data(), port); if (!addrRes) return std::unexpected{addrRes.error()}; sockaddr_in serverAddress = addrRes.value(); /// Create socket close_socket(); if (auto res = create_socket(); !res) { return std::unexpected{res.error()}; } /// Connect int connectRes = ::connect(m_socket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)); if (connectRes < 0 && errno != EINPROGRESS) { spdlog::error("tcp::NonBlockingClient: connect() failed with errno={}", errno); return std::unexpected{errno}; } m_startedNewConAttempt = true; return {}; } [[nodiscard]] std::expected NonBlockingClient::get_last_connection_status() { if (!m_startedNewConAttempt) return m_lastKnownConStatus; /// Check if connect operation has been completed auto pendingOpRes = socket_has_new_output_event(); if (!pendingOpRes) return std::unexpected{pendingOpRes.error()}; if (!pendingOpRes.value()) return EINPROGRESS; m_startedNewConAttempt = false; /// Check for connection errors int err = 0; socklen_t len = sizeof(err); if (getsockopt(m_socket, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { spdlog::error( "tcp::NonBlockingClient: getsockopt() failed with errno={}", errno); return std::unexpected{errno}; } if (err == 0) { m_lastKnownConStatus = EISCONN; } else { m_lastKnownConStatus = err; } return m_lastKnownConStatus; } void NonBlockingClient::disconnect() { m_lastKnownConStatus = ENOTCONN; close_socket(); } [[nodiscard]] std::expected 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.value() != EISCONN) { spdlog::error("tcp::NonBlockingClient: Socket not connected"); return std::unexpected{ENOTCONN}; } const int bytesWritten = ::send(m_socket, (const char*)data.data(), data.size(), 0); if (bytesWritten <= 0) { spdlog::error("tcp::NonBlockingClient: send() failed with errno={}", errno); return std::unexpected{errno}; } return bytesWritten; } bool NonBlockingClient::data_available() { auto conRes = get_last_connection_status(); if (!conRes) return false; if (conRes.value() != EISCONN) return false; int ret = poll(&m_pfdIn, 1, 0); if (ret < 0) { spdlog::error("tcp::NonBlockingClient: poll() failed with errno={}", errno); return false; } return (m_pfdIn.revents & POLLIN); } [[nodiscard]] std::expected NonBlockingClient::recv(std::span buffer) { const int bytesReceived = ::recv(m_socket, (char*)buffer.data(), buffer.size(), 0); if (bytesReceived < 0) { spdlog::error("tcp::NonBlockingClient: recv() failed with errno={}", errno); return std::unexpected{errno}; } return {bytesReceived}; } void NonBlockingClient::close_socket() { if (shutdown(m_socket, 0) < 0) { spdlog::debug("tcp::NonBlockingClient: shutdown() failed with errno={}", errno); } if (close(m_socket) < 0) { spdlog::debug("tcp::NonBlockingClient: close() failed with errno={}", errno); } m_socket = -1; m_pfdOut.fd = -1; m_pfdIn.fd = -1; } [[nodiscard]] std::expected NonBlockingClient::create_socket() { m_socket = socket(AF_INET, SOCK_STREAM, 0); if (m_socket < 1) { spdlog::error("tcp::NonBlockingClient: socket() failed with errno={}", errno); return std::unexpected{errno}; } m_pfdOut.fd = m_socket; m_pfdIn.fd = m_socket; auto currentFlags = fcntl(m_socket, F_GETFL, 0); if (currentFlags == -1) { spdlog::error("tcp::NonBlockingClient: fcntl() failed with errno={}", errno); close_socket(); return std::unexpected{errno}; } if (fcntl(m_socket, F_SETFL, (currentFlags) | O_NONBLOCK) == -1) { spdlog::error("tcp::NonBlockingClient: fcntl() failed with errno={}", errno); close_socket(); return std::unexpected{errno}; } return {}; } [[nodiscard]] std::expected NonBlockingClient::socket_has_new_output_event() { int ret = poll(&m_pfdOut, 1, 0); if (ret < 0) { spdlog::error("tcp::NonBlockingClient: poll() failed with errno={}", errno); return std::unexpected{errno}; } // ret == 0 means the operation timed out, i.e., no new events return (ret != 0); } } // namespace tcp