// (C) 2024 by Folkert van Heusden // Released under MIT license #include "gen.h" #if defined(ESP32) #include #endif #if defined(ESP32) #include #include #include #include #include #elif defined(_WIN32) // from https://stackoverflow.com/questions/12765743/implicit-declaration-of-function-getaddrinfo-on-mingw #define _NTDDI_VERSION_FROM_WIN32_WINNT2(ver) ver##0000 #define _NTDDI_VERSION_FROM_WIN32_WINNT(ver) _NTDDI_VERSION_FROM_WIN32_WINNT2(ver) #ifndef _WIN32_WINNT # define _WIN32_WINNT 0x501 #endif #ifndef NTDDI_VERSION # define NTDDI_VERSION _NTDDI_VERSION_FROM_WIN32_WINNT(_WIN32_WINNT) #endif #include #include #else #include #include #include #include #endif #include #if IS_POSIX #include #include #include #include #endif #include "comm_tcp_socket_client.h" #include "log.h" #include "utils.h" comm_tcp_socket_client::comm_tcp_socket_client(const std::string & host, const int port) : host(host), port(port) { } comm_tcp_socket_client::~comm_tcp_socket_client() { stop_flag = true; if (th) { th->join(); delete th; } } bool comm_tcp_socket_client::begin() { th = new std::thread(std::ref(*this)); return true; } bool comm_tcp_socket_client::is_connected() { std::unique_lock lck(cfd_lock); return cfd != INVALID_SOCKET; } bool comm_tcp_socket_client::has_data() { std::unique_lock lck(cfd_lock); #if defined(_WIN32) WSAPOLLFD fds[] { { cfd, POLLIN, 0 } }; int rc = WSAPoll(fds, 1, 0); #else pollfd fds[] { { cfd, POLLIN, 0 } }; int rc = poll(fds, 1, 0); #endif return rc == 1; } uint8_t comm_tcp_socket_client::get_byte() { int use_fd = -1; { std::unique_lock lck(cfd_lock); use_fd = cfd; } uint8_t c = 0; if (read(use_fd, &c, 1) <= 0) { DOLOG(warning, false, "comm_tcp_socket_client::get_byte: failed"); std::unique_lock lck(cfd_lock); close(cfd); cfd = INVALID_SOCKET; } return c; } void comm_tcp_socket_client::send_data(const uint8_t *const in, const size_t n) { const uint8_t *p = in; size_t len = n; while(len > 0) { std::unique_lock lck(cfd_lock); int rc = write(cfd, p, len); if (rc <= 0) { // TODO error checking DOLOG(warning, false, "comm_tcp_socket_client::send_data: failed"); close(cfd); cfd = INVALID_SOCKET; break; } p += rc; len -= rc; } } void comm_tcp_socket_client::operator()() { set_thread_name("kek:COMMTCPC"); DOLOG(info, true, "TCP comm (client) thread started for %s:%d", host.c_str(), port); while(!stop_flag) { myusleep(101000l); std::unique_lock lck(cfd_lock); if (cfd != INVALID_SOCKET) continue; addrinfo *res = nullptr; addrinfo hints { }; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; char port_str[8] { 0 }; snprintf(port_str, sizeof port_str, "%u", port); int rc = getaddrinfo(host.c_str(), port_str, &hints, &res); if (rc != 0) { #ifdef ESP32 DOLOG(ll_error, true, "comm_tcp_socket_client: cannot resolve \"%s\":%s", host.c_str(), port_str); #else DOLOG(ll_error, true, "comm_tcp_socket_client: cannot resolve \"%s\":%s: %s", host.c_str(), port_str, gai_strerror(rc)); #endif myusleep(101000l); continue; } for(addrinfo *p = res; p != NULL; p = p->ai_next) { if ((cfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) continue; if (::connect(cfd, p->ai_addr, p->ai_addrlen) == -1) { close(cfd); cfd = INVALID_SOCKET; DOLOG(ll_error, true, "comm_tcp_socket_client: cannot connect"); continue; } break; } freeaddrinfo(res); if (cfd != INVALID_SOCKET) set_nodelay(cfd); } DOLOG(info, true, "comm_tcp_socket_client thread terminating"); close(cfd); } JsonDocument comm_tcp_socket_client::serialize() const { JsonDocument j; j["comm-backend-type"] = "tcp-client"; j["host"] = host; j["port"] = port; return j; } comm_tcp_socket_client *comm_tcp_socket_client::deserialize(const JsonVariantConst j) { comm_tcp_socket_client *r = new comm_tcp_socket_client(j["host"].as(), j["port"].as()); return r; }