ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
network.c
Go to the documentation of this file.
1
8#include "network.h"
9#include "common.h"
10#include "asciichat_errno.h"
11#include "platform/socket.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 <stdatomic.h>
19
20#ifndef _WIN32
21#include <netinet/tcp.h>
22#endif
23
24/* ============================================================================
25 * Core Network I/O Operations
26 * ============================================================================
27 * This module provides the fundamental network I/O operations including
28 * socket management, timeouts, and basic send/receive operations.
29 */
30
31// Use network_network_is_test_environment() from network.h
32
40static ssize_t network_platform_send(socket_t sockfd, const void *data, size_t len) {
41#ifdef _WIN32
42 // Windows send() expects int for length and const char* for buffer
43 if (len > INT_MAX) {
44 len = INT_MAX;
45 }
46 int raw_sent = send(sockfd, (const char *)data, (int)len, 0);
47
48 // Check for SOCKET_ERROR before casting to avoid corruption
49 ssize_t sent;
50 if (raw_sent == SOCKET_ERROR) {
51 sent = -1;
52 // On Windows, use WSAGetLastError() and save to WSA error field
53 errno = EIO; // Set a generic errno, but save the real WSA error
54 } else {
55 sent = (ssize_t)raw_sent;
56 // CORRUPTION DETECTION: Check Windows send() return value
57 if (raw_sent > (int)len) {
58 SET_ERRNO(ERROR_INVALID_STATE, "CRITICAL: Windows send() returned more than requested: raw_sent=%d > len=%zu",
59 raw_sent, len);
60 }
61 }
62 return sent;
63#elif defined(MSG_NOSIGNAL)
64 return send(sockfd, data, len, MSG_NOSIGNAL);
65#else
66 // macOS doesn't have MSG_NOSIGNAL, but we ignore SIGPIPE signal instead
67 return send(sockfd, data, len, 0);
68#endif
69}
70
78static ssize_t network_platform_recv(socket_t sockfd, void *buf, size_t len) {
79#ifdef _WIN32
80 int raw_received = recv(sockfd, (char *)buf, (int)len, 0);
81
82 if (raw_received == SOCKET_ERROR) {
83 return -1;
84 }
85 return (ssize_t)raw_received;
86#else
87 return recv(sockfd, buf, len, 0);
88#endif
89}
90
96static const char *network_get_error_string(int error) {
97#ifdef _WIN32
98 // For socket errors, use socket-specific function
99 (void)error; // Windows doesn't use the error parameter
101#else
102 // For standard errno, use platform abstraction
103 return SAFE_STRERROR(error);
104#endif
105}
106
112static int network_handle_send_error(int error) {
113 if (error == EAGAIN || error == EWOULDBLOCK) {
114 return 1; // Retry
115 }
116 if (error == EPIPE) {
117 SET_ERRNO_SYS(ERROR_NETWORK, "Connection closed by peer during send");
118 return 0; // Fatal
119 }
120 SET_ERRNO_SYS(ERROR_NETWORK, "send_with_timeout failed: %s", network_get_error_string(error));
121 return 0; // Fatal
122}
123
129static int network_handle_recv_error(int error) {
130#ifdef _WIN32
131 if (error == WSAEWOULDBLOCK) {
132 return 1; // Retry
133 }
134 if (error == WSAEINTR) {
135 // Signal interrupted the call - this is recoverable, just retry
136 log_debug("recv interrupted by signal, retrying");
137 return 1; // Retry
138 }
139#else
140 if (error == EAGAIN || error == EWOULDBLOCK) {
141 return 1; // Retry
142 }
143 if (error == EINTR) {
144 // Signal interrupted the call - this is recoverable, just retry
145 log_debug("recv interrupted by signal, retrying");
146 return 1; // Retry
147 }
148 if (error == EBADF) {
149 // Socket is not a socket (WSAENOTSOCK on Windows) - socket was closed
150 SET_ERRNO_SYS(ERROR_NETWORK, "Socket is not a socket (closed by another thread)");
151 return 0; // Fatal
152 }
153#endif
154 SET_ERRNO_SYS(ERROR_NETWORK, "recv_with_timeout failed: %s", network_get_error_string(error));
155 return 0; // Fatal
156}
157
163static int network_handle_select_error(int result) {
164 if (result == 0) {
165 // Timeout - this is expected behavior for server waiting for connections
170 return 0; // Not an error, but don't retry
171 }
172
173#ifdef _WIN32
174 int error = WSAGetLastError();
175 if (error == WSAEINTR) {
176 // Signal interrupted the call - this is recoverable, just retry
177 log_debug("select interrupted by signal, retrying");
178 return 1; // Retry
179 }
180 SET_ERRNO_SYS(ERROR_NETWORK, "select failed: %s", network_get_error_string(error));
181#else
182 if (errno == EINTR) {
183 // Signal interrupted the call - this is recoverable, just retry
184 log_debug("select interrupted by signal, retrying");
185 return 1; // Retry
186 }
187 SET_ERRNO_SYS(ERROR_NETWORK, "select failed: %s", network_get_error_string(errno));
188#endif
189 return 0; // Fatal
190}
191
200ssize_t send_with_timeout(socket_t sockfd, const void *data, size_t len, int timeout_seconds) {
201 if (sockfd == INVALID_SOCKET_VALUE) {
202 errno = EBADF;
203 return -1;
204 }
205
206 size_t total_sent = 0;
207 const char *data_ptr = (const char *)data;
208
209 while (total_sent < len) {
210 // Calculate chunk size
211 size_t bytes_to_send = len - total_sent;
212 const size_t MAX_CHUNK_SIZE = 65536; // 64KB chunks for reliable TCP transmission
213 if (bytes_to_send > MAX_CHUNK_SIZE) {
214 bytes_to_send = MAX_CHUNK_SIZE;
215 }
216
217 // Set up select for write timeout
218 fd_set write_fds;
219 struct timeval timeout;
220
221 socket_fd_zero(&write_fds);
222 socket_fd_set(sockfd, &write_fds);
223
224 timeout.tv_sec = network_is_test_environment() ? 1 : timeout_seconds;
225 timeout.tv_usec = 0;
226
227 int result = socket_select(sockfd, NULL, &write_fds, NULL, &timeout);
228 if (result <= 0) {
229 if (result == 0) {
230 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "send_with_timeout timed out after %d seconds", timeout_seconds);
231 return -1;
232 }
233 if (network_handle_select_error(result)) {
234 continue; // Retry
235 }
236 return -1; // Fatal error
237 }
238
239 // Check if socket is ready for writing
240 if (!socket_fd_isset(sockfd, &write_fds)) {
241 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "send_with_timeout socket not ready for writing after select");
242 return -1;
243 }
244
245 // Use platform-specific send
246 ssize_t sent = network_platform_send(sockfd, data_ptr + total_sent, bytes_to_send);
247
248 if (sent < 0) {
249 int error = errno;
250 if (network_handle_send_error(error)) {
251 continue; // Retry
252 }
253 return -1; // Fatal error
254 }
255
256 if (sent > 0) {
257 total_sent += (size_t)sent;
258 }
259 }
260
261 return (ssize_t)total_sent;
262}
263
272ssize_t recv_with_timeout(socket_t sockfd, void *buf, size_t len, int timeout_seconds) {
273 if (sockfd == INVALID_SOCKET_VALUE) {
274 log_error("NETWORK_DEBUG: recv_with_timeout called with INVALID_SOCKET_VALUE");
275 errno = EBADF;
276 return -1;
277 }
278
279 fd_set read_fds;
280 struct timeval timeout;
281 ssize_t total_received = 0;
282 char *data = (char *)buf;
283
284 while (total_received < (ssize_t)len) {
285 // Set up select for read timeout
286 socket_fd_zero(&read_fds);
287 socket_fd_set(sockfd, &read_fds);
288
289 timeout.tv_sec = network_is_test_environment() ? 1 : timeout_seconds;
290 timeout.tv_usec = 0;
291
292 int result = socket_select(sockfd, &read_fds, NULL, NULL, &timeout);
293 if (result <= 0) {
294 if (result == 0) {
295 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "recv_with_timeout timed out after %d seconds", timeout_seconds);
296 return -1;
297 }
298 if (network_handle_select_error(result)) {
299 continue; // Retry
300 }
301 return -1; // Fatal error
302 }
303
304 // Check if socket is ready
305 if (!socket_fd_isset(sockfd, &read_fds)) {
306 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "recv_with_timeout socket not ready after select");
307 return -1;
308 }
309
310 // Calculate how much we still need to receive
311 size_t bytes_to_recv = len - (size_t)total_received;
312 ssize_t received = network_platform_recv(sockfd, data + total_received, bytes_to_recv);
313
314 if (received < 0) {
315 int error = errno;
316 log_error("NETWORK_DEBUG: network_platform_recv failed with error %d (errno=%d), sockfd=%d, buf=%p, len=%zu",
317 received, error, sockfd, data + total_received, bytes_to_recv);
318 if (network_handle_recv_error(error)) {
319 log_debug("NETWORK_DEBUG: retrying after error %d", error);
320 continue; // Retry
321 }
322 log_error("NETWORK_DEBUG: fatal error %d, giving up", error);
323 return -1; // Fatal error
324 }
325
326 if (received == 0) {
327 // Connection closed by peer
328 log_debug("Connection closed by peer during recv");
329 return total_received; // Return what we got so far
330 }
331
332 total_received += received;
333 }
334
335 return total_received;
336}
337
346int accept_with_timeout(socket_t listenfd, struct sockaddr *addr, socklen_t *addrlen, int timeout_seconds) {
347 fd_set read_fds;
348 struct timeval timeout;
349
350 socket_fd_zero(&read_fds);
351 socket_fd_set(listenfd, &read_fds);
352
353 timeout.tv_sec = timeout_seconds;
354 timeout.tv_usec = 0;
355
356 int result = socket_select(listenfd, &read_fds, NULL, NULL, &timeout);
357
358 if (result <= 0) {
359 if (result == 0) {
360 // Timeout is expected behavior for server waiting for connections
365 return -1;
366 }
367
368 if (network_handle_select_error(result)) {
369 return -1; // Don't retry for accept
370 }
371 return -1;
372 }
373
374 // Check if socket is ready
375 if (!socket_fd_isset(listenfd, &read_fds)) {
380 return -1;
381 }
382
383 // Check if socket is still valid before attempting accept
384 if (listenfd == INVALID_SOCKET_VALUE) {
385 SET_ERRNO(ERROR_NETWORK, "accept_with_timeout: listening socket is closed");
386 return -1;
387 }
388
389 socket_t accept_result = socket_accept(listenfd, addr, addrlen);
390
391 if (accept_result == INVALID_SOCKET_VALUE) {
392 // Check if this is a socket closed error (common during shutdown)
393#ifdef _WIN32
394 // On Windows, use WSAGetLastError() instead of errno
395 int error_code = WSAGetLastError();
396 // Windows-specific error codes for closed/invalid sockets
397 // WSAENOTSOCK = 10038, WSAEBADF = 10009, WSAENOTCONN = 10057
398 if (error_code == WSAENOTSOCK || error_code == WSAEBADF || error_code == WSAENOTCONN) {
399 // During shutdown, don't log this as an error since it's expected behavior
403 } else {
404 // Save Windows socket error to WSA error field for debugging
405 errno = EIO; // Set a generic errno
406#ifdef NDEBUG
408#else
410#endif
411 }
412#else
413 // POSIX error codes for closed/invalid sockets
414 int error_code = errno;
415 if (error_code == EBADF || error_code == ENOTSOCK) {
416 // During shutdown, don't log this as an error since it's expected behavior
420 } else {
421 SET_ERRNO_SYS(ERROR_NETWORK_BIND, "accept_with_timeout accept failed: %s", network_get_error_string(errno));
422 }
423#endif
424 return -1;
425 }
426
427 return (int)accept_result;
428}
429
436asciichat_error_t set_socket_timeout(socket_t sockfd, int timeout_seconds) {
437 if (sockfd == INVALID_SOCKET_VALUE) {
438 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
439 }
440
441 struct timeval timeout;
442 timeout.tv_sec = timeout_seconds;
443 timeout.tv_usec = 0;
444
445 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
446 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket receive timeout");
447 }
448
449 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) {
450 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket send timeout");
451 }
452
453 return ASCIICHAT_OK;
454}
455
462 if (sockfd == INVALID_SOCKET_VALUE) {
463 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
464 }
466 if (result != 0) {
467 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket keepalive parameters");
468 }
469 return ASCIICHAT_OK;
470}
471
478 if (sockfd == INVALID_SOCKET_VALUE) {
479 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
480 }
481 int result = socket_set_nonblocking(sockfd, true);
482 if (result != 0) {
483 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket non-blocking mode");
484 }
485 return ASCIICHAT_OK;
486}
487
494 if (sockfd == INVALID_SOCKET_VALUE) {
495 return SET_ERRNO_SYS(ERROR_NETWORK, "Invalid socket file descriptor");
496 }
497
498 int failed_options = 0;
499
500 // Attempt to configure 1MB send buffer for optimal frame transmission
501 int send_buffer_size = 1024 * 1024;
502 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &send_buffer_size, sizeof(send_buffer_size)) < 0) {
503 log_warn("Failed to set send buffer size to 1MB: %s", network_error_string());
504 failed_options++;
505 }
506
507 // Attempt to configure 1MB receive buffer for optimal frame reception
508 int recv_buffer_size = 1024 * 1024;
509 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recv_buffer_size, sizeof(recv_buffer_size)) < 0) {
510 log_warn("Failed to set receive buffer size to 1MB: %s", network_error_string());
511 failed_options++;
512 }
513
514 // Attempt to enable TCP_NODELAY to disable Nagle's algorithm for low-latency transmission
515 // CRITICAL: This must be set even if buffer configuration fails, as it's essential for real-time video
516 int tcp_nodelay = 1;
517 if (socket_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(tcp_nodelay)) < 0) {
518 log_warn("Failed to set TCP_NODELAY: %s", network_error_string());
519 failed_options++;
520 }
521
522 // Return error only if ALL options failed
523 if (failed_options >= 3) {
524 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to configure all socket options");
525 }
526
527 return ASCIICHAT_OK;
528}
529
535const char *network_error_string() {
537}
538
547bool connect_with_timeout(socket_t sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_seconds) {
548 if (sockfd == INVALID_SOCKET_VALUE) {
549 errno = EBADF;
550 return false;
551 }
552
553 // Set socket to non-blocking for timeout control
554 if (set_socket_nonblocking(sockfd) != ASCIICHAT_OK) {
555 return false;
556 }
557
558 // Attempt connection
559 int result = connect(sockfd, addr, addrlen);
560
561 if (result == 0) {
562 // Connected immediately
563 return true;
564 }
565
566#ifdef _WIN32
567 int error = WSAGetLastError();
568 if (error != WSAEWOULDBLOCK && error != WSAEINPROGRESS) {
569 return false;
570 }
571#else
572 if (errno != EINPROGRESS && errno != EWOULDBLOCK) {
573 return false;
574 }
575#endif
576
577 // Use select to wait for connection with timeout
578 fd_set write_fds;
579 struct timeval timeout;
580
581 socket_fd_zero(&write_fds);
582 socket_fd_set(sockfd, &write_fds);
583
584 timeout.tv_sec = timeout_seconds;
585 timeout.tv_usec = 0;
586
587 result = socket_select(sockfd, NULL, &write_fds, NULL, &timeout);
588
589 if (result <= 0) {
590 return false; // Timeout or error
591 }
592
593 if (!socket_fd_isset(sockfd, &write_fds)) {
594 return false; // Socket not ready
595 }
596
597 // Check if connection was successful
598 int error_code = 0;
599 socklen_t error_len = sizeof(error_code);
600
601 if (socket_getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error_code, &error_len) != 0) {
602 return false;
603 }
604
605 if (error_code != 0) {
606 return false;
607 }
608
609 // Connection successful - restore blocking mode
610 if (socket_set_blocking(sockfd) != 0) {
611 log_warn("Failed to restore socket to blocking mode after connect");
612 // Continue anyway - non-blocking mode works but may cause issues with send/recv
613 }
614
615 return true;
616}
asciichat_error_t error_code
⚠️‼️ Error and/or exit() when things go bad.
#define SAFE_STRERROR(errnum)
Definition common.h:385
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
__thread asciichat_error_t asciichat_errno
Thread-local current error code.
void asciichat_set_errno_with_wsa_error(asciichat_error_t code, const char *file, int line, const char *function, int wsa_error)
Set error code with Windows socket error (WSA error)
__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:46
@ ERROR_INVALID_STATE
@ ERROR_NETWORK_BIND
Definition error_codes.h:70
@ ERROR_NETWORK
Definition error_codes.h:69
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:72
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_debug(...)
Log a DEBUG message.
asciichat_error_t set_socket_timeout(socket_t sockfd, int timeout_seconds)
Set socket timeout.
Definition network.c:436
asciichat_error_t set_socket_keepalive(socket_t sockfd)
Set socket keepalive.
Definition network.c:461
#define KEEPALIVE_COUNT
Keepalive probe count (8 probes)
Definition network.h:186
#define KEEPALIVE_INTERVAL
Keepalive interval in seconds (10 seconds)
Definition network.h:177
asciichat_error_t set_socket_nonblocking(socket_t sockfd)
Set socket non-blocking.
Definition network.c:477
ssize_t send_with_timeout(socket_t sockfd, const void *data, size_t len, int timeout_seconds)
Send data with timeout using chunked transmission.
Definition network.c:200
#define network_is_test_environment()
Check if we're in a test environment.
Definition network.h:147
bool connect_with_timeout(socket_t sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_seconds)
Connect with timeout.
Definition network.c:547
int accept_with_timeout(socket_t listenfd, struct sockaddr *addr, socklen_t *addrlen, int timeout_seconds)
Accept connection with timeout.
Definition network.c:346
const char * network_error_string()
Get human-readable error string for network errors.
Definition network.c:535
asciichat_error_t socket_configure_buffers(socket_t sockfd)
Configure socket buffers and TCP_NODELAY for optimal performance.
Definition network.c:493
#define KEEPALIVE_IDLE
Keepalive idle time in seconds (60 seconds)
Definition network.h:168
ssize_t recv_with_timeout(socket_t sockfd, void *buf, size_t len, int timeout_seconds)
Receive data with timeout.
Definition network.c:272
int socket_set_keepalive_params(socket_t sock, bool enable, int idle, int interval, int count)
Set TCP keepalive parameters.
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.
int socket_t
Socket handle type (POSIX: int)
Definition socket.h:50
#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
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:52
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.
socket_t socket_accept(socket_t sock, struct sockaddr *addr, socklen_t *addrlen)
Accept an incoming connection.
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.
#define EBADF
int errno
int socket_fd_isset(socket_t sock, fd_set *set)
Check if a socket is in an fd_set.
#define ENOTSOCK
#define EAGAIN
🌐 Core network I/O operations with timeout support
Cross-platform socket interface for ascii-chat.
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)