ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
network/network.c
Go to the documentation of this file.
1
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>
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#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
67 if (socket_is_invalid_socket_error(error)) {
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
84 asciichat_errno = ERROR_NETWORK_TIMEOUT;
85 asciichat_errno_context.code = ERROR_NETWORK_TIMEOUT;
86 asciichat_errno_context.has_system_error = true;
87 asciichat_errno_context.system_errno = ETIMEDOUT;
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 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);
200 if (result <= 0) {
201 if (result == 0) {
202 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "recv_with_timeout timed out after %llu nanoseconds",
203 (unsigned long long)timeout_ns);
204 return -1;
205 }
206 if (network_handle_select_error(result)) {
207 continue; // Retry
208 }
209 return -1; // Fatal error
210 }
211
212 // Check if socket is ready
213 if (!(pfd.revents & POLLIN)) {
214 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "recv_with_timeout socket not ready after poll");
215 return -1;
216 }
217
218 // Calculate how much we still need to receive
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);
221
222 if (received < 0) {
223 int error = errno;
224 if (network_handle_recv_error(error)) {
225 continue; // Retry
226 }
227 return -1; // Fatal error
228 }
229
230 if (received == 0) {
231 // Connection closed by peer
232 log_debug("Connection closed by peer during recv");
233 return total_received; // Return what we got so far
234 }
235
236 total_received += received;
237 }
238
239 return total_received;
240}
241
250int accept_with_timeout(socket_t listenfd, struct sockaddr *addr, socklen_t *addrlen, uint64_t timeout_ns) {
251 // Set up poll for accept timeout (in nanoseconds)
252 struct pollfd pfd;
253 pfd.fd = listenfd;
254 pfd.events = POLLIN;
255 pfd.revents = 0;
256
257 int result = socket_poll(&pfd, 1, (int64_t)timeout_ns);
258
259 if (result <= 0) {
260 if (result == 0) {
261 // Timeout is expected behavior for server waiting for connections
262 asciichat_errno = ERROR_NETWORK_TIMEOUT;
263 asciichat_errno_context.code = ERROR_NETWORK_TIMEOUT;
264 asciichat_errno_context.has_system_error = true;
265 asciichat_errno_context.system_errno = ETIMEDOUT;
266 return -1;
267 }
268
269 if (network_handle_select_error(result)) {
270 return -1; // Don't retry for accept
271 }
272 return -1;
273 }
274
275 // Check if socket is ready
276 if (!(pfd.revents & POLLIN)) {
277 asciichat_errno = ERROR_NETWORK_TIMEOUT;
278 asciichat_errno_context.code = ERROR_NETWORK_TIMEOUT;
279 asciichat_errno_context.has_system_error = true;
280 asciichat_errno_context.system_errno = ETIMEDOUT;
281 return -1;
282 }
283
284 // Check if socket is still valid before attempting accept
285 if (listenfd == INVALID_SOCKET_VALUE) {
286 SET_ERRNO(ERROR_NETWORK, "accept_with_timeout: listening socket is closed");
287 return -1;
288 }
289
290 socket_t accept_result = socket_accept(listenfd, addr, addrlen);
291
292 if (accept_result == INVALID_SOCKET_VALUE) {
293 // Check if this is a socket closed error (common during shutdown)
294 int error_code = socket_get_last_error();
295
296 if (socket_is_invalid_socket_error(error_code)) {
297 // During shutdown, don't log this as an error since it's expected behavior
298 asciichat_errno = ERROR_NETWORK;
299 asciichat_errno_context.code = ERROR_NETWORK;
300 asciichat_errno_context.has_system_error = false;
301 } else {
302 SET_ERRNO_SYS(ERROR_NETWORK_BIND, "accept_with_timeout accept failed: %s", socket_get_error_string());
303 }
304 return -1;
305 }
306
307 return (int)accept_result;
308}
309
320asciichat_error_t set_socket_timeout(socket_t sockfd, uint64_t timeout_ns) {
321 if (sockfd == INVALID_SOCKET_VALUE) {
322 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
323 }
324
325 // Convert nanoseconds to milliseconds for socket-level timeout
326 // Note: socket-level timeouts have ~millisecond granularity on most platforms
327 uint64_t timeout_ms = timeout_ns / (1000 * 1000);
328 if (timeout_ms == 0 && timeout_ns > 0) {
329 timeout_ms = 1; // Ensure at least 1ms for non-zero timeouts
330 }
331
332 // Set both receive and send timeouts using struct timeval
333 struct timeval tv;
334 tv.tv_sec = (time_t)(timeout_ms / 1000);
335 tv.tv_usec = (long)((timeout_ms % 1000) * 1000);
336
337 // Set receive timeout
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");
340 }
341
342 // Set send 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");
345 }
346
347 return ASCIICHAT_OK;
348}
349
355asciichat_error_t set_socket_keepalive(socket_t sockfd) {
356 if (sockfd == INVALID_SOCKET_VALUE) {
357 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
358 }
359 int result = socket_set_keepalive_params(sockfd, true, KEEPALIVE_IDLE, KEEPALIVE_INTERVAL, KEEPALIVE_COUNT);
360 if (result != 0) {
361 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket keepalive parameters");
362 }
363 return ASCIICHAT_OK;
364}
365
371asciichat_error_t set_socket_nonblocking(socket_t sockfd) {
372 if (sockfd == INVALID_SOCKET_VALUE) {
373 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
374 }
375 int result = socket_set_nonblocking(sockfd, true);
376 if (result != 0) {
377 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket non-blocking mode");
378 }
379 return ASCIICHAT_OK;
380}
381
387asciichat_error_t socket_configure_buffers(socket_t sockfd) {
388 if (sockfd == INVALID_SOCKET_VALUE) {
389 return SET_ERRNO_SYS(ERROR_NETWORK, "Invalid socket file descriptor");
390 }
391
392 int failed_options = 0;
393
394 // Attempt to configure 1MB send buffer for optimal frame transmission
395 int send_buffer_size = 1024 * 1024;
396 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &send_buffer_size, sizeof(send_buffer_size)) < 0) {
397 log_warn("Failed to set send buffer size to 1MB: %s", network_error_string());
398 failed_options++;
399 }
400
401 // Attempt to configure 1MB receive buffer for optimal frame reception
402 int recv_buffer_size = 1024 * 1024;
403 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recv_buffer_size, sizeof(recv_buffer_size)) < 0) {
404 log_warn("Failed to set receive buffer size to 1MB: %s", network_error_string());
405 failed_options++;
406 }
407
408 // Attempt to enable TCP_NODELAY to disable Nagle's algorithm for low-latency transmission
409 // This must be set even if buffer configuration fails, as it's essential for real-time video.
410 int tcp_nodelay = 1;
411 if (socket_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(tcp_nodelay)) < 0) {
412 log_warn("Failed to set TCP_NODELAY: %s", network_error_string());
413 failed_options++;
414 }
415
416 // Return error only if ALL options failed
417 if (failed_options >= 3) {
418 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to configure all socket options");
419 }
420
421 return ASCIICHAT_OK;
422}
423
429const char *network_error_string() {
430 return socket_get_error_string();
431}
432
441bool connect_with_timeout(socket_t sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_seconds) {
442 if (sockfd == INVALID_SOCKET_VALUE) {
443 errno = EBADF;
444 return false;
445 }
446
447 // Set socket to non-blocking for timeout control
448 if (set_socket_nonblocking(sockfd) != ASCIICHAT_OK) {
449 return false;
450 }
451
452 // Attempt connection
453 int result = connect(sockfd, addr, addrlen);
454
455 if (result == 0) {
456 // Connected immediately
457 return true;
458 }
459
460 // Check if connection is in progress (expected for non-blocking sockets)
461 int error = socket_get_last_error();
462 if (!socket_is_in_progress_error(error) && !socket_is_would_block_error(error)) {
463 return false;
464 }
465
466 // Use select to wait for connection with timeout
467 fd_set write_fds;
468 struct timeval timeout;
469
470 socket_fd_zero(&write_fds);
471 socket_fd_set(sockfd, &write_fds);
472
473 timeout.tv_sec = timeout_seconds;
474 timeout.tv_usec = 0;
475
476 result = socket_select(sockfd, NULL, &write_fds, NULL, &timeout);
477
478 if (result <= 0) {
479 return false; // Timeout or error
480 }
481
482 if (!socket_fd_isset(sockfd, &write_fds)) {
483 return false; // Socket not ready
484 }
485
486 // Check if connection was successful
487 int error_code = 0;
488 socklen_t error_len = sizeof(error_code);
489
490 if (socket_getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error_code, &error_len) != 0) {
491 return false;
492 }
493
494 if (error_code != 0) {
495 return false;
496 }
497
498 // Connection successful - restore blocking mode
499 if (socket_set_blocking(sockfd) != 0) {
500 log_warn("Failed to restore socket to blocking mode after connect");
501 // Continue anyway - non-blocking mode works but may cause issues with send/recv
502 }
503
504 return true;
505}
asciichat_error_t error_code
__thread asciichat_error_t asciichat_errno
__thread asciichat_error_context_t asciichat_errno_context
int socket_t
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.