ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
network/network.c
Go to the documentation of this file.
1
9#include <ascii-chat/common.h>
12#include <stdint.h>
13#include <errno.h>
14#include <stdbool.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include <ascii-chat/atomic.h>
19#ifndef _WIN32
20#include <sys/time.h>
21#endif
22
23/* ============================================================================
24 * Core Network I/O Operations
25 * ============================================================================
26 * This module provides the fundamental network I/O operations including
27 * socket management, timeouts, and basic send/receive operations.
28 */
29
30// Use network_network_is_test_environment() from network.h
31
37static int network_handle_send_error(int error) {
38 if (error == EAGAIN || error == EWOULDBLOCK) {
39 return 1; // Retry
40 }
41 if (error == EPIPE) {
42 SET_ERRNO_SYS(ERROR_NETWORK, "Connection closed by peer during send");
43 return 0; // Fatal
44 }
45 SET_ERRNO_SYS(ERROR_NETWORK, "send_with_timeout failed: %s", socket_get_error_string());
46 return 0; // Fatal
47}
48
54static int network_handle_recv_error(int error) {
55 // Check for "would block" - retry operation
56 if (socket_is_would_block_error(error)) {
57 return 1; // Retry
58 }
59
60 // Check for signal interruption - recoverable, retry
61 if (error == EINTR) {
62 log_debug("recv interrupted by signal, retrying");
63 return 1; // Retry
64 }
65
66 // Check for invalid/closed socket - fatal
68 SET_ERRNO_SYS(ERROR_NETWORK, "Socket is not a socket (closed by another thread)");
69 return 0; // Fatal
70 }
71
72 SET_ERRNO_SYS(ERROR_NETWORK, "recv_with_timeout failed: %s", socket_get_error_string());
73 return 0; // Fatal
74}
75
81static int network_handle_select_error(int result) {
82 if (result == 0) {
83 // Timeout - this is expected behavior for server waiting for connections
88 return 0; // Not an error, but don't retry
89 }
90
91 // Get error code (cross-platform)
92 int error = socket_get_last_error();
93
94 // Check for signal interruption - recoverable, retry
95 if (error == EINTR) {
96 log_debug("select interrupted by signal, retrying");
97 return 1; // Retry
98 }
99
100 SET_ERRNO_SYS(ERROR_NETWORK, "select failed: %s", socket_get_error_string());
101 return 0; // Fatal
102}
103
112ssize_t send_with_timeout(socket_t sockfd, const void *data, size_t len, uint64_t timeout_ns) {
113 if (sockfd == INVALID_SOCKET_VALUE) {
114 errno = EBADF;
115 return -1;
116 }
117
118 size_t total_sent = 0;
119 const char *data_ptr = (const char *)data;
120
121 while (total_sent < len) {
122 // Calculate chunk size
123 size_t bytes_to_send = len - total_sent;
124 const size_t MAX_CHUNK_SIZE = 65536; // 64KB chunks for reliable TCP transmission
125 if (bytes_to_send > MAX_CHUNK_SIZE) {
126 bytes_to_send = MAX_CHUNK_SIZE;
127 }
128
129 // Set up poll for write timeout (in nanoseconds)
130 struct pollfd pfd;
131 pfd.fd = sockfd;
132 pfd.events = POLLOUT;
133 pfd.revents = 0;
134
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);
137 if (result <= 0) {
138 if (result == 0) {
139 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "send_with_timeout timed out after %llu nanoseconds",
140 (unsigned long long)timeout_ns);
141 return -1;
142 }
143 if (network_handle_select_error(result)) {
144 continue; // Retry
145 }
146 return -1; // Fatal error
147 }
148
149 // Check if socket is ready for writing
150 if (!(pfd.revents & POLLOUT)) {
151 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "send_with_timeout socket not ready for writing after poll");
152 return -1;
153 }
154
155 // Use platform-specific send
156 ssize_t sent = socket_send(sockfd, data_ptr + total_sent, bytes_to_send, 0);
157
158 if (sent < 0) {
159 int error = errno;
160 if (network_handle_send_error(error)) {
161 continue; // Retry
162 }
163 return -1; // Fatal error
164 }
165
166 if (sent > 0) {
167 total_sent += (size_t)sent;
168 }
169 }
170
171 return (ssize_t)total_sent;
172}
173
182ssize_t recv_with_timeout(socket_t sockfd, void *buf, size_t len, uint64_t timeout_ns) {
183 if (sockfd == INVALID_SOCKET_VALUE) {
184 errno = EBADF;
185 return -1;
186 }
187
188 ssize_t total_received = 0;
189 char *data = (char *)buf;
190
191 while (total_received < (ssize_t)len) {
192 // Set up poll for read timeout (in nanoseconds)
193 struct pollfd pfd;
194 pfd.fd = sockfd;
195 pfd.events = POLLIN;
196 pfd.revents = 0;
197
198 // Use provided timeout for recv() calls - don't artificially limit data packets in test mode
199 // Timeouts should only apply to initial handshake, not to receiving data on active connections
200 int result = socket_poll(&pfd, 1, (int64_t)timeout_ns);
201 if (result <= 0) {
202 if (result == 0) {
203 /* A read timeout is the ordinary idle state for signaling sockets.
204 * Do not emit an error on every polling interval. */
209 return -1;
210 }
211 if (network_handle_select_error(result)) {
212 continue; // Retry
213 }
214 return -1; // Fatal error
215 }
216
217 // Poll can return readiness for a hangup or socket error without POLLIN.
218 // Treat those as terminal network errors so callers tear down the dead
219 // connection instead of retrying it as an idle timeout in a tight loop.
220 if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
221 SET_ERRNO(ERROR_NETWORK, "recv_with_timeout poll reported socket flags 0x%x", pfd.revents);
222 return -1;
223 }
224
225 // Other readiness flags without POLLIN are an ordinary no-data result.
226 if (!(pfd.revents & POLLIN)) {
231 return -1;
232 }
233
234 // Calculate how much we still need to receive
235 size_t bytes_to_recv = len - (size_t)total_received;
236 ssize_t received = socket_recv(sockfd, data + total_received, bytes_to_recv, 0);
237
238 if (received < 0) {
239 int error = errno;
240 if (network_handle_recv_error(error)) {
241 continue; // Retry
242 }
243 return -1; // Fatal error
244 }
245
246 if (received == 0) {
247 // Connection closed by peer
248 log_debug("Connection closed by peer during recv");
249 return total_received; // Return what we got so far
250 }
251
252 total_received += received;
253 }
254
255 return total_received;
256}
257
266int accept_with_timeout(socket_t listenfd, struct sockaddr *addr, socklen_t *addrlen, uint64_t timeout_ns) {
267 // Set up poll for accept timeout (in nanoseconds)
268 struct pollfd pfd;
269 pfd.fd = listenfd;
270 pfd.events = POLLIN;
271 pfd.revents = 0;
272
273 int result = socket_poll(&pfd, 1, (int64_t)timeout_ns);
274
275 if (result <= 0) {
276 if (result == 0) {
277 // Timeout is expected behavior for server waiting for connections
282 return -1;
283 }
284
285 if (network_handle_select_error(result)) {
286 return -1; // Don't retry for accept
287 }
288 return -1;
289 }
290
291 // Check if socket is ready
292 if (!(pfd.revents & POLLIN)) {
297 return -1;
298 }
299
300 // Check if socket is still valid before attempting accept
301 if (listenfd == INVALID_SOCKET_VALUE) {
302 SET_ERRNO(ERROR_NETWORK, "accept_with_timeout: listening socket is closed");
303 return -1;
304 }
305
306 socket_t accept_result = socket_accept(listenfd, addr, addrlen, "connection");
307
308 if (accept_result == INVALID_SOCKET_VALUE) {
309 // Check if this is a socket closed error (common during shutdown)
311
313 // During shutdown, don't log this as an error since it's expected behavior
317 } else {
318 SET_ERRNO_SYS(ERROR_NETWORK_BIND, "accept_with_timeout accept failed: %s", socket_get_error_string());
319 }
320 return -1;
321 }
322
323 return (int)accept_result;
324}
325
337 if (sockfd == INVALID_SOCKET_VALUE) {
338 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
339 }
340
341 // Convert nanoseconds to milliseconds for socket-level timeout
342 // Note: socket-level timeouts have ~millisecond granularity on most platforms
343 uint64_t timeout_ms = timeout_ns / (1000 * 1000);
344 if (timeout_ms == 0 && timeout_ns > 0) {
345 timeout_ms = 1; // Ensure at least 1ms for non-zero timeouts
346 }
347
348 // Set both receive and send timeouts using struct timeval
349 struct timeval tv;
350 tv.tv_sec = (time_t)(timeout_ms / 1000);
351 tv.tv_usec = (long)((timeout_ms % 1000) * 1000);
352
353 // Set receive timeout
354 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) {
355 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket receive timeout");
356 }
357
358 // Set send timeout
359 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) != 0) {
360 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket send timeout");
361 }
362
363 return ASCIICHAT_OK;
364}
365
372 if (sockfd == INVALID_SOCKET_VALUE) {
373 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
374 }
376 if (result != 0) {
377 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket keepalive parameters");
378 }
379 return ASCIICHAT_OK;
380}
381
388 if (sockfd == INVALID_SOCKET_VALUE) {
389 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
390 }
391 int result = socket_set_nonblocking(sockfd, true);
392 if (result != 0) {
393 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket non-blocking mode");
394 }
395 return ASCIICHAT_OK;
396}
397
404 if (sockfd == INVALID_SOCKET_VALUE) {
405 return SET_ERRNO_SYS(ERROR_NETWORK, "Invalid socket file descriptor");
406 }
407
408 int failed_options = 0;
409
410 // Attempt to configure 1MB send buffer for optimal frame transmission
411 int send_buffer_size = 1024 * 1024;
412 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &send_buffer_size, sizeof(send_buffer_size)) < 0) {
413 log_warn("Failed to set send buffer size to 1MB: %s", network_error_string());
414 failed_options++;
415 }
416
417 // Attempt to configure 1MB receive buffer for optimal frame reception
418 int recv_buffer_size = 1024 * 1024;
419 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recv_buffer_size, sizeof(recv_buffer_size)) < 0) {
420 log_warn("Failed to set receive buffer size to 1MB: %s", network_error_string());
421 failed_options++;
422 }
423
424 // Attempt to enable TCP_NODELAY to disable Nagle's algorithm for low-latency transmission
425 // This must be set even if buffer configuration fails, as it's essential for real-time video.
426 int tcp_nodelay = 1;
427 if (socket_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(tcp_nodelay)) < 0) {
428 log_warn("Failed to set TCP_NODELAY: %s", network_error_string());
429 failed_options++;
430 }
431
432 // Return error only if ALL options failed
433 if (failed_options >= 3) {
434 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to configure all socket options");
435 }
436
437 return ASCIICHAT_OK;
438}
439
445const char *network_error_string() {
447}
448
457bool connect_with_timeout(socket_t sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_seconds) {
458 if (sockfd == INVALID_SOCKET_VALUE) {
459 errno = EBADF;
460 return false;
461 }
462
463 // Set socket to non-blocking for timeout control
464 if (set_socket_nonblocking(sockfd) != ASCIICHAT_OK) {
465 return false;
466 }
467
468 // Attempt connection
469 int result = connect(sockfd, addr, addrlen);
470
471 if (result == 0) {
472 // Connected immediately
473 if (socket_set_blocking(sockfd) != 0) {
474 return false;
475 }
476 return true;
477 }
478
479 // Check if connection is in progress (expected for non-blocking sockets)
480 int error = socket_get_last_error();
482 log_warn("TCP connection could not start (socket error %d)", error);
483 return false;
484 }
485
486 // Use select to wait for connection with timeout
487 fd_set write_fds;
488 struct timeval timeout;
489
490 socket_fd_zero(&write_fds);
491 socket_fd_set(sockfd, &write_fds);
492
493 timeout.tv_sec = timeout_seconds;
494 timeout.tv_usec = 0;
495
496 result = socket_select(sockfd, NULL, &write_fds, NULL, &timeout);
497
498 if (result <= 0) {
499 log_warn("TCP connection wait failed (result %d, socket error %d)", result,
500 result == 0 ? 0 : socket_get_last_error());
501 return false; // Timeout or error
502 }
503
504 if (!socket_fd_isset(sockfd, &write_fds)) {
505 return false; // Socket not ready
506 }
507
508 // Check if connection was successful
509 int error_code = 0;
510 socklen_t error_len = sizeof(error_code);
511
512 if (socket_getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error_code, &error_len) != 0) {
513 return false;
514 }
515
516 if (error_code != 0) {
517 log_warn("TCP connection failed (socket error %d)", error_code);
518 return false;
519 }
520
521 // Connection successful - restore blocking mode
522 if (socket_set_blocking(sockfd) != 0) {
523 log_warn("Failed to restore socket to blocking mode after connect");
524 // Continue anyway - non-blocking mode works but may cause issues with send/recv
525 }
526
527 return true;
528}
asciichat_error_t error_code
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
⚛️ Atomic operations abstraction layer with debug tracking
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Cross-platform error handling utilities.
unsigned long long uint64_t
Definition common.h:59
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context, returning the error code.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
__thread asciichat_error_t asciichat_errno
Thread-local current error code.
__thread asciichat_error_context_t asciichat_errno_context
Thread-local error context storage.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_NETWORK_BIND
Definition error_codes.h:78
@ ERROR_NETWORK
Definition error_codes.h:77
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:80
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define NS_PER_MS_INT
Definition time.h:156
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.
#define KEEPALIVE_COUNT
Keepalive probe count (8 probes)
#define KEEPALIVE_INTERVAL
Keepalive interval in seconds (10 seconds)
asciichat_error_t set_socket_nonblocking(socket_t sockfd)
Set socket non-blocking.
#define network_is_test_environment()
Check if we're in a test environment.
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.
#define KEEPALIVE_IDLE
Keepalive idle time in seconds (60 seconds)
int socket_set_keepalive_params(socket_t sock, bool enable, int idle, int interval, int count)
Set TCP keepalive parameters.
ssize_t socket_recv(socket_t sock, void *buf, size_t len, int flags)
Receive data from a socket.
void socket_fd_zero(fd_set *set)
Clear an fd_set.
int socket_set_nonblocking(socket_t sock, bool nonblocking)
Set socket to non-blocking mode.
int socket_setsockopt(socket_t sock, int level, int optname, const void *optval, socklen_t optlen)
Set socket option.
#define EINTR
int socket_getsockopt(socket_t sock, int level, int optname, void *optval, socklen_t *optlen)
Get socket option.
#define EWOULDBLOCK
#define ETIMEDOUT
#define EPIPE
int socket_poll(struct pollfd *fds, nfds_t nfds, int64_t timeout_ns)
Poll sockets for events (multiplexed I/O)
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
void socket_fd_set(socket_t sock, fd_set *set)
Add a socket to an fd_set.
int socket_set_blocking(socket_t sock)
Set socket to blocking mode.
bool socket_is_would_block_error(int error_code)
Check if error code indicates "would block" (non-blocking socket would wait)
bool socket_is_in_progress_error(int error_code)
Check if error indicates operation in progress (non-blocking connect)
ssize_t socket_send(socket_t sock, const void *buf, size_t len, int flags)
Send data on a socket.
bool socket_is_invalid_socket_error(int error_code)
Check if error code indicates a closed/invalid socket.
int socket_get_last_error(void)
Get last socket error code.
int socket_select(socket_t max_fd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
Select sockets for I/O readiness.
const char * socket_get_error_string(void)
Get last socket error as string.
socket_t socket_accept(socket_t sock, struct sockaddr *addr, socklen_t *addrlen, const char *name)
Accept an incoming connection.
#define EBADF
int errno
int socket_fd_isset(socket_t sock, fd_set *set)
Check if a socket is in an fd_set.
#define EAGAIN
int socket_t
🌐 Core network I/O operations with timeout support
Cross-platform socket interface for ascii-chat.
ClangTool/LibTooling compatibility shim for stdbool.h.
int system_errno
System errno value (if applicable, 0 otherwise)
bool has_system_error
True if system_errno is valid.
asciichat_error_t code
Error code (asciichat_error_t enum value)