//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® module; #include #include #include #include #include #include #include #include #include #include #include #include #include module Crafter.Network:ClientTCP_impl; import :ClientTCP; import Crafter.Thread; import std; using namespace Crafter; ClientTCP::ClientTCP(int socketid) : socketid(socketid) { } ClientTCP::ClientTCP(const char* hostName, std::uint16_t port) { // getaddrinfo rather than gethostbyname: the latter is not thread safe, // and it signals failure by returning null — which was then dereferenced // straight into a crash on any unresolvable host. addrinfo hints{}; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; addrinfo* resolved = nullptr; const std::string service = std::to_string(port); const int status = getaddrinfo(hostName, service.c_str(), &hints, &resolved); if (status != 0 || resolved == nullptr) { throw std::runtime_error(std::string("Could not resolve host '") + hostName + "': " + gai_strerror(status)); } host = nullptr; serv_addr = *reinterpret_cast(resolved->ai_addr); freeaddrinfo(resolved); Connect(); } ClientTCP::ClientTCP(std::string hostName, std::uint16_t port): ClientTCP(hostName.c_str(), port) { } // The moved-from object gives up ownership; the socket itself stays open — // closing it here (as this used to) meant moving a ClientTCP silently // dropped the connection it was carrying. ClientTCP::ClientTCP(ClientTCP&& other) noexcept : socketid(other.socketid) { other.socketid = -1; } ClientTCP::~ClientTCP() { if(socketid != -1) { shutdown(socketid, SHUT_RDWR); close(socketid); } } void ClientTCP::Connect() { if((socketid = socket(AF_INET, SOCK_STREAM, 0)) == -1){ throw std::runtime_error(std::string("Could not open socket: ") + std::strerror(errno)); } if(connect(socketid,(sockaddr*)&serv_addr, sizeof(sockaddr)) == -1){ // Report the failure instead of handing back a socket that is not // connected to anything — every later send/recv on it would fail // with a far less obvious error. const std::string reason = std::strerror(errno); close(socketid); socketid = -1; throw std::runtime_error("Could not connect to server: " + reason); } } void ClientTCP::Stop() { shutdown(socketid, SHUT_RDWR); close(socketid); socketid = -1; } void ClientTCP::Send(const void* buffer, std::uint32_t size) const { // send() is free to accept less than it was offered, and does so // routinely once a buffer outgrows the socket's send buffer — a // multi-megabyte HTTP body, say. Loop until it is all handed over. const char* data = reinterpret_cast(buffer); std::uint32_t sent = 0; while (sent < size) { const auto status = send(socketid, data + sent, size - sent, MSG_NOSIGNAL); if (status == 0) { throw SocketClosedException(); } else if (status < 0) { if (errno == EINTR) continue; throw std::runtime_error(std::strerror(errno)); } sent += static_cast(status); } } std::vector ClientTCP::RecieveSync(std::uint32_t bufferSize) const { std::vector totalBuffer(bufferSize); int read = recv(socketid, totalBuffer.data(), bufferSize, 0); if(read < 0) { throw std::runtime_error(std::strerror(errno)); } else if(read == 0) { throw SocketClosedException(); } return totalBuffer; } int ClientTCP::RecieveSync(std::uint32_t bufferSize, void* buffer) const { return recv(socketid, reinterpret_cast(buffer), bufferSize, 0); } std::vector ClientTCP::RecieveSync() const { int count; ioctl(socketid, FIONREAD, &count); if(count == 0){ count = 1024; } std::vector buffer(count); int read = recv(socketid, buffer.data(), count, 0); if(read < 0) { throw std::runtime_error(std::strerror(errno)); } else if(read == 0) { throw SocketClosedException(); } else if(count != read) { buffer.resize(read); } return buffer; } std::vector ClientTCP::RecieveUntilCloseSync() const { int count; ioctl(socketid, FIONREAD, &count); if(count == 0){ count = 1024; } std::vector buffer(count); int read = recv(socketid, buffer.data(), count, 0); if(read < 0) { throw std::runtime_error(std::strerror(errno)); } else if(read == 0) { throw SocketClosedException(); } while(true) { ioctl(socketid, FIONREAD, &count); if(count == 0){ count = 1024; } unsigned int oldSize = buffer.size(); buffer.resize(buffer.size()+count); int read = recv(socketid, buffer.data()+oldSize, count, 0); if(read < 0) { throw std::runtime_error(std::strerror(errno)); } else if(read == 0) { return buffer; } else if(count != read) { buffer.resize(read); } } return buffer; } std::vector ClientTCP::RecieveUntilFullSync(std::uint32_t bufferSize) const { std::vector buffer(bufferSize); int read = 0; while(read < bufferSize) { int newRead = recv(socketid, buffer.data()+read, bufferSize-read, 0); if(newRead < 0) { throw std::runtime_error(std::strerror(errno)); } else if(newRead == 0) { throw SocketClosedException(); } read+=newRead; } buffer.resize(read); return buffer; } void ClientTCP::RecieveAsync(std::uint32_t bufferSize, std::function)> recieveCallback) const { ThreadPool::Enqueue([recieveCallback, this, bufferSize](){ recieveCallback(this->RecieveSync(bufferSize)); }); } void ClientTCP::RecieveAsync(std::uint32_t bufferSize, std::function recieveCallback, char* buffer) const { ThreadPool::Enqueue([recieveCallback, this, bufferSize, buffer](){ recieveCallback(this->RecieveSync(bufferSize, buffer)); }); } void ClientTCP::RecieveUntilFullAsync(std::uint32_t bufferSize, std::function)> recieveCallback) const { ThreadPool::Enqueue([recieveCallback, this, bufferSize](){ recieveCallback(this->RecieveUntilFullSync(bufferSize)); }); } void ClientTCP::RecieveAsync(std::function)> recieveCallback) const { ThreadPool::Enqueue([this, recieveCallback](){ recieveCallback(this->RecieveSync()); }); } void ClientTCP::RecieveUntilCloseAsync(std::function)> recieveCallback) const { ThreadPool::Enqueue([this, recieveCallback](){ recieveCallback(this->RecieveUntilCloseSync()); }); }