8#include <ascii-chat/network/network.h>
9#include <ascii-chat/common.h>
10#include <ascii-chat/asciichat_errno.h>
11#include <ascii-chat/platform/socket.h>
37static int network_handle_send_error(
int error) {
38 if (error == EAGAIN || error == EWOULDBLOCK) {
42 SET_ERRNO_SYS(ERROR_NETWORK,
"Connection closed by peer during send");
45 SET_ERRNO_SYS(ERROR_NETWORK,
"send_with_timeout failed: %s", socket_get_error_string());
54static int network_handle_recv_error(
int error) {
56 if (socket_is_would_block_error(error)) {
62 log_debug(
"recv interrupted by signal, retrying");
67 if (socket_is_invalid_socket_error(error)) {
68 SET_ERRNO_SYS(ERROR_NETWORK,
"Socket is not a socket (closed by another thread)");
72 SET_ERRNO_SYS(ERROR_NETWORK,
"recv_with_timeout failed: %s", socket_get_error_string());
81static int network_handle_select_error(
int result) {
92 int error = socket_get_last_error();
96 log_debug(
"select interrupted by signal, retrying");
100 SET_ERRNO_SYS(ERROR_NETWORK,
"select failed: %s", socket_get_error_string());
113 if (sockfd == INVALID_SOCKET_VALUE) {
118 size_t total_sent = 0;
119 const char *data_ptr = (
const char *)data;
121 while (total_sent < len) {
123 size_t bytes_to_send = len - total_sent;
124 const size_t MAX_CHUNK_SIZE = 65536;
125 if (bytes_to_send > MAX_CHUNK_SIZE) {
126 bytes_to_send = MAX_CHUNK_SIZE;
132 pfd.events = POLLOUT;
135 int64_t effective_timeout_ns = network_is_test_environment() ? (100LL * NS_PER_MS_INT) : (int64_t)timeout_ns;
136 int result = socket_poll(&pfd, 1, effective_timeout_ns);
139 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT,
"send_with_timeout timed out after %llu nanoseconds",
140 (
unsigned long long)timeout_ns);
143 if (network_handle_select_error(result)) {
150 if (!(pfd.revents & POLLOUT)) {
151 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT,
"send_with_timeout socket not ready for writing after poll");
156 ssize_t sent = socket_send(sockfd, data_ptr + total_sent, bytes_to_send, 0);
160 if (network_handle_send_error(error)) {
167 total_sent += (size_t)sent;
171 return (ssize_t)total_sent;
183 if (sockfd == INVALID_SOCKET_VALUE) {
188 ssize_t total_received = 0;
189 char *data = (
char *)buf;
191 while (total_received < (ssize_t)len) {
198 int64_t effective_timeout_ns = network_is_test_environment() ? (1LL * NS_PER_SEC_INT) : (int64_t)timeout_ns;
199 int result = socket_poll(&pfd, 1, effective_timeout_ns);
202 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT,
"recv_with_timeout timed out after %llu nanoseconds",
203 (
unsigned long long)timeout_ns);
206 if (network_handle_select_error(result)) {
213 if (!(pfd.revents & POLLIN)) {
214 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT,
"recv_with_timeout socket not ready after poll");
219 size_t bytes_to_recv = len - (size_t)total_received;
220 ssize_t received = socket_recv(sockfd, data + total_received, bytes_to_recv, 0);
224 if (network_handle_recv_error(error)) {
232 log_debug(
"Connection closed by peer during recv");
233 return total_received;
236 total_received += received;
239 return total_received;
257 int result = socket_poll(&pfd, 1, (int64_t)timeout_ns);
269 if (network_handle_select_error(result)) {
276 if (!(pfd.revents & POLLIN)) {
285 if (listenfd == INVALID_SOCKET_VALUE) {
286 SET_ERRNO(ERROR_NETWORK,
"accept_with_timeout: listening socket is closed");
290 socket_t accept_result = socket_accept(listenfd, addr, addrlen);
292 if (accept_result == INVALID_SOCKET_VALUE) {
296 if (socket_is_invalid_socket_error(
error_code)) {
302 SET_ERRNO_SYS(ERROR_NETWORK_BIND,
"accept_with_timeout accept failed: %s", socket_get_error_string());
307 return (
int)accept_result;
321 if (sockfd == INVALID_SOCKET_VALUE) {
322 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid socket file descriptor");
327 uint64_t timeout_ms = timeout_ns / (1000 * 1000);
328 if (timeout_ms == 0 && timeout_ns > 0) {
334 tv.tv_sec = (time_t)(timeout_ms / 1000);
335 tv.tv_usec = (long)((timeout_ms % 1000) * 1000);
338 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv)) != 0) {
339 return SET_ERRNO_SYS(ERROR_NETWORK,
"Failed to set socket receive timeout");
343 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv,
sizeof(tv)) != 0) {
344 return SET_ERRNO_SYS(ERROR_NETWORK,
"Failed to set socket send timeout");
356 if (sockfd == INVALID_SOCKET_VALUE) {
357 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid socket file descriptor");
359 int result = socket_set_keepalive_params(sockfd,
true, KEEPALIVE_IDLE, KEEPALIVE_INTERVAL, KEEPALIVE_COUNT);
361 return SET_ERRNO_SYS(ERROR_NETWORK,
"Failed to set socket keepalive parameters");
372 if (sockfd == INVALID_SOCKET_VALUE) {
373 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid socket file descriptor");
375 int result = socket_set_nonblocking(sockfd,
true);
377 return SET_ERRNO_SYS(ERROR_NETWORK,
"Failed to set socket non-blocking mode");
388 if (sockfd == INVALID_SOCKET_VALUE) {
389 return SET_ERRNO_SYS(ERROR_NETWORK,
"Invalid socket file descriptor");
392 int failed_options = 0;
395 int send_buffer_size = 1024 * 1024;
396 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &send_buffer_size,
sizeof(send_buffer_size)) < 0) {
402 int recv_buffer_size = 1024 * 1024;
403 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recv_buffer_size,
sizeof(recv_buffer_size)) < 0) {
411 if (socket_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay,
sizeof(tcp_nodelay)) < 0) {
417 if (failed_options >= 3) {
418 return SET_ERRNO_SYS(ERROR_NETWORK,
"Failed to configure all socket options");
430 return socket_get_error_string();
442 if (sockfd == INVALID_SOCKET_VALUE) {
453 int result = connect(sockfd, addr, addrlen);
461 int error = socket_get_last_error();
462 if (!socket_is_in_progress_error(error) && !socket_is_would_block_error(error)) {
468 struct timeval timeout;
470 socket_fd_zero(&write_fds);
471 socket_fd_set(sockfd, &write_fds);
473 timeout.tv_sec = timeout_seconds;
476 result = socket_select(sockfd, NULL, &write_fds, NULL, &timeout);
482 if (!socket_fd_isset(sockfd, &write_fds)) {
490 if (socket_getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &
error_code, &error_len) != 0) {
499 if (socket_set_blocking(sockfd) != 0) {
500 log_warn(
"Failed to restore socket to blocking mode after connect");
asciichat_error_t error_code
__thread asciichat_error_t asciichat_errno
__thread asciichat_error_context_t asciichat_errno_context
asciichat_error_t set_socket_timeout(socket_t sockfd, uint64_t timeout_ns)
Set socket timeout.
asciichat_error_t set_socket_keepalive(socket_t sockfd)
Set socket keepalive.
ssize_t recv_with_timeout(socket_t sockfd, void *buf, size_t len, uint64_t timeout_ns)
Receive data with timeout.
asciichat_error_t set_socket_nonblocking(socket_t sockfd)
Set socket non-blocking.
bool connect_with_timeout(socket_t sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_seconds)
Connect with timeout.
int accept_with_timeout(socket_t listenfd, struct sockaddr *addr, socklen_t *addrlen, uint64_t timeout_ns)
Accept connection with timeout.
ssize_t send_with_timeout(socket_t sockfd, const void *data, size_t len, uint64_t timeout_ns)
Send data with timeout using chunked transmission.
const char * network_error_string()
Get human-readable error string for network errors.
asciichat_error_t socket_configure_buffers(socket_t sockfd)
Configure socket buffers and TCP_NODELAY for optimal performance.