// POSIX-реализация платформенного слоя (Linux). #include "ayla/platform/platform.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fgl::plat { uint64_t now_ms() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return static_cast(ts.tv_sec) * 1000u + static_cast(ts.tv_nsec) / 1000000u; } bool random(uint8_t* buf, size_t len) { size_t done = 0; while (done < len) { ssize_t n = ::getrandom(buf + done, len - done, 0); if (n < 0) { if (errno == EINTR) continue; return false; } done += static_cast(n); } return true; } namespace { struct ThreadStart { void (*fn)(void*); void* ctx; }; void* thread_trampoline(void* arg) { auto* start = static_cast(arg); ThreadStart tmp = *start; delete start; tmp.fn(tmp.ctx); return nullptr; } } // namespace bool thread_create(void (*fn)(void*), void* ctx, const char* name, uint32_t stack_bytes, ThreadId* out_id) { auto* start = new (std::nothrow) ThreadStart{fn, ctx}; if (start == nullptr) return false; pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); if (stack_bytes > 0) { if (pthread_attr_setstacksize(&attr, stack_bytes) != 0) { // glibc отвергает < PTHREAD_STACK_MIN; остаётся дефолт (больше — не меньше) // логируем только: ядро запрашивает >= PTHREAD_STACK_MIN. } } int rc = pthread_create(&tid, &attr, thread_trampoline, start); pthread_attr_destroy(&attr); if (rc != 0) { delete start; return false; } pthread_setname_np(tid, name != nullptr ? name : "fgl"); if (out_id != nullptr) { *out_id = reinterpret_cast(tid); } else { pthread_detach(tid); } return true; } void thread_join(ThreadId id) { auto tid = reinterpret_cast(id); pthread_join(tid, nullptr); } void sleep_ms(uint32_t ms) { struct timespec req; req.tv_sec = ms / 1000; req.tv_nsec = static_cast(ms % 1000) * 1000000L; while (nanosleep(&req, &req) == -1 && errno == EINTR) { } } int tcp_listen(uint16_t port) { int fd = ::socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) return -1; int one = 1; ::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); struct sockaddr_in addr {}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); if (::bind(fd, reinterpret_cast(&addr), sizeof(addr)) < 0 || ::listen(fd, 4) < 0) { ::close(fd); return -1; } return fd; } uint16_t tcp_local_port(int fd) { struct sockaddr_in addr {}; socklen_t addrlen = sizeof(addr); if (::getsockname(fd, reinterpret_cast(&addr), &addrlen) != 0) { return 0; } return ntohs(addr.sin_port); } int tcp_accept(int listen_fd, uint32_t* peer_ip, uint16_t* peer_port) { struct sockaddr_in addr {}; socklen_t addrlen = sizeof(addr); int fd = ::accept(listen_fd, reinterpret_cast(&addr), &addrlen); if (fd < 0) return -1; int one = 1; ::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); if (peer_ip != nullptr) *peer_ip = ntohl(addr.sin_addr.s_addr); if (peer_port != nullptr) *peer_port = ntohs(addr.sin_port); return fd; } int tcp_connect(const char* host, uint16_t port, uint32_t timeout_ms) { struct addrinfo hints {}; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; struct addrinfo* list = nullptr; if (::getaddrinfo(host, nullptr, &hints, &list) != 0 || list == nullptr) { return -1; } int fd = -1; for (struct addrinfo* ai = list; ai != nullptr; ai = ai->ai_next) { auto* addr = reinterpret_cast(ai->ai_addr); addr->sin_port = htons(port); fd = ::socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (fd < 0) continue; // Неблокирующее подключение + poll для таймаута. int flags = ::fcntl(fd, F_GETFL, 0); ::fcntl(fd, F_SETFL, flags | O_NONBLOCK); int rc = ::connect(fd, ai->ai_addr, ai->ai_addrlen); if (rc == 0) { ::fcntl(fd, F_SETFL, flags); break; } if (errno == EINPROGRESS) { struct pollfd pfd {fd, POLLOUT, 0}; if (::poll(&pfd, 1, static_cast(timeout_ms)) > 0) { int soerr = 0; socklen_t slen = sizeof(soerr); ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &soerr, &slen); if (soerr == 0) { ::fcntl(fd, F_SETFL, flags); break; } } } ::close(fd); fd = -1; } ::freeaddrinfo(list); return fd; } bool local_ip_for(const char* host, char* out, size_t out_cap) { struct addrinfo hints {}; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; struct addrinfo* list = nullptr; if (::getaddrinfo(host, "80", &hints, &list) != 0 || list == nullptr) { return false; } int fd = ::socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { ::freeaddrinfo(list); return false; } bool ok = ::connect(fd, list->ai_addr, list->ai_addrlen) == 0; struct sockaddr_in local {}; socklen_t slen = sizeof(local); if (ok && ::getsockname(fd, reinterpret_cast(&local), &slen) == 0) { const char* s = inet_ntop(AF_INET, &local.sin_addr, out, static_cast(out_cap)); ok = s != nullptr; } else { ok = false; } ::freeaddrinfo(list); ::close(fd); return ok; } long tcp_send(int fd, const void* buf, size_t len) { const uint8_t* p = static_cast(buf); size_t done = 0; while (done < len) { long n = ::send(fd, p + done, len - done, MSG_NOSIGNAL); if (n < 0) { if (errno == EINTR) continue; return -1; } done += static_cast(n); } return static_cast(done); } long tcp_recv(int fd, void* buf, size_t len) { for (;;) { long n = ::recv(fd, buf, len, 0); if (n < 0 && errno == EINTR) continue; return n; } } bool tcp_set_timeout(int fd, uint32_t rx_ms, uint32_t tx_ms) { struct timeval tv {}; tv.tv_sec = rx_ms / 1000; tv.tv_usec = static_cast(rx_ms % 1000) * 1000L; if (::setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) return false; tv.tv_sec = tx_ms / 1000; tv.tv_usec = static_cast(tx_ms % 1000) * 1000L; return ::setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == 0; } bool tcp_poll_readable(int fd, uint32_t timeout_ms) { struct pollfd pfd {fd, POLLIN, 0}; return ::poll(&pfd, 1, static_cast(timeout_ms)) > 0; } bool tcp_shutdown(int fd) { return ::shutdown(fd, SHUT_RDWR) == 0; } void tcp_close(int fd) { if (fd >= 0) ::close(fd); } } // namespace fgl::plat