tcp-lib/src/tcp.cpp
2025-04-19 05:20:25 +02:00

443 lines
12 KiB
C++

#include "tcp.hpp"
#include <arpa/inet.h>
#include <cerrno>
#include <cstdint>
#include <expected>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <span>
#include <spdlog/spdlog.h>
#include <sys/socket.h>
#include <sys/types.h>
namespace {
std::expected<sockaddr_in, int> 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(ServerSettings settings)
: m_settings{settings}, m_serverAddress(get_local_address(settings.port)) {
}
NonBlockingServer::~NonBlockingServer() {
close_client_socket();
close_server_socket();
}
std::expected<void, int> NonBlockingServer::start_listening() {
/// Create socket
close_server_socket();
if (auto res = create_socket(); !res) {
return std::unexpected{res.error()};
}
/// Bin socket and start listening
if (::bind(m_serverSocket, (struct sockaddr*)&m_serverAddress,
sizeof(m_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<void, int> NonBlockingServer::accept_next_client() {
disconnect();
int result = ::accept(m_serverSocket, (struct sockaddr*)&m_clientAddress,
&m_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<std::array<char, 16>, int>
NonBlockingServer::get_client_ip_as_string() const {
std::array<char, 16> 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<int, int>
NonBlockingServer::send(std::span<const std::byte> data) const {
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<int, int>
NonBlockingServer::recv(std::span<std::byte> buffer) const {
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<void, int> 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<void, int>
NonBlockingClient::connect(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<int, int>
NonBlockingClient::get_last_connection_status() {
if (!m_startedNewConAttempt) return m_lastKnownConStatus;
/// Check if connect operation has been completed
auto pendingOpRes = new_error_codes_available();
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<int, int>
NonBlockingClient::send(std::span<const std::byte> 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<int, int>
NonBlockingClient::recv(std::span<std::byte> 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<void, int> 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<bool, int>
NonBlockingClient::new_error_codes_available() {
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