795 lines
22 KiB
C
795 lines
22 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
#include "network.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/select.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
|
|
/* ============================================================= */
|
|
/* Internal Structures */
|
|
/* ============================================================= */
|
|
|
|
struct sw_socket {
|
|
int fd;
|
|
sw_conn_state state;
|
|
char remote_addr[64];
|
|
uint16_t remote_port;
|
|
char local_addr[64];
|
|
uint16_t local_port;
|
|
uint64_t bytes_sent;
|
|
uint64_t bytes_received;
|
|
uint64_t last_activity;
|
|
bool is_server;
|
|
};
|
|
|
|
struct sw_socket_set {
|
|
fd_set read_fds;
|
|
fd_set write_fds;
|
|
fd_set error_fds;
|
|
fd_set result_read;
|
|
fd_set result_write;
|
|
fd_set result_error;
|
|
int max_fd;
|
|
sw_socket** sockets;
|
|
int num_sockets;
|
|
int capacity;
|
|
};
|
|
|
|
/* Thread-local error message */
|
|
static __thread char last_error[256] = {0};
|
|
|
|
static void set_error(const char* msg) {
|
|
snprintf(last_error, sizeof(last_error), "%s: %s", msg, strerror(errno));
|
|
}
|
|
|
|
static uint64_t current_time(void) {
|
|
return (uint64_t)time(NULL);
|
|
}
|
|
|
|
/* ============================================================= */
|
|
/* Server Operations */
|
|
/* ============================================================= */
|
|
|
|
sw_socket* sw_server_create(uint16_t port) {
|
|
sw_socket* sock = calloc(1, sizeof(sw_socket));
|
|
if (!sock) {
|
|
set_error("Failed to allocate socket");
|
|
return NULL;
|
|
}
|
|
|
|
sock->fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sock->fd < 0) {
|
|
set_error("Failed to create socket");
|
|
free(sock);
|
|
return NULL;
|
|
}
|
|
|
|
/* Allow address reuse */
|
|
int opt = 1;
|
|
setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
|
|
|
struct sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
addr.sin_port = htons(port);
|
|
|
|
if (bind(sock->fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
|
set_error("Failed to bind socket");
|
|
close(sock->fd);
|
|
free(sock);
|
|
return NULL;
|
|
}
|
|
|
|
if (listen(sock->fd, SW_MAX_PENDING) < 0) {
|
|
set_error("Failed to listen on socket");
|
|
close(sock->fd);
|
|
free(sock);
|
|
return NULL;
|
|
}
|
|
|
|
sock->is_server = true;
|
|
sock->state = SW_CONN_CONNECTED;
|
|
sock->local_port = port;
|
|
snprintf(sock->local_addr, sizeof(sock->local_addr), "0.0.0.0");
|
|
sock->last_activity = current_time();
|
|
|
|
return sock;
|
|
}
|
|
|
|
sw_socket* sw_server_accept(sw_socket* server, int timeout_sec) {
|
|
if (!server || !server->is_server) {
|
|
set_error("Invalid server socket");
|
|
return NULL;
|
|
}
|
|
|
|
/* Wait for connection with timeout */
|
|
fd_set fds;
|
|
FD_ZERO(&fds);
|
|
FD_SET(server->fd, &fds);
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = timeout_sec;
|
|
tv.tv_usec = 0;
|
|
|
|
int ret = select(server->fd + 1, &fds, NULL, NULL, &tv);
|
|
if (ret < 0) {
|
|
set_error("Select failed");
|
|
return NULL;
|
|
}
|
|
if (ret == 0) {
|
|
/* Timeout - not an error */
|
|
return NULL;
|
|
}
|
|
|
|
struct sockaddr_in client_addr;
|
|
socklen_t addr_len = sizeof(client_addr);
|
|
int client_fd = accept(server->fd, (struct sockaddr*)&client_addr, &addr_len);
|
|
if (client_fd < 0) {
|
|
set_error("Accept failed");
|
|
return NULL;
|
|
}
|
|
|
|
sw_socket* client = calloc(1, sizeof(sw_socket));
|
|
if (!client) {
|
|
close(client_fd);
|
|
set_error("Failed to allocate client socket");
|
|
return NULL;
|
|
}
|
|
|
|
client->fd = client_fd;
|
|
client->state = SW_CONN_CONNECTED;
|
|
client->remote_port = ntohs(client_addr.sin_port);
|
|
inet_ntop(AF_INET, &client_addr.sin_addr, client->remote_addr, sizeof(client->remote_addr));
|
|
client->last_activity = current_time();
|
|
|
|
/* Enable TCP_NODELAY for lower latency */
|
|
int flag = 1;
|
|
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
|
|
|
|
server->last_activity = current_time();
|
|
|
|
return client;
|
|
}
|
|
|
|
sw_socket* sw_server_accept_nonblock(sw_socket* server) {
|
|
if (!server || !server->is_server) return NULL;
|
|
|
|
/* Set non-blocking temporarily */
|
|
int flags = fcntl(server->fd, F_GETFL, 0);
|
|
fcntl(server->fd, F_SETFL, flags | O_NONBLOCK);
|
|
|
|
struct sockaddr_in client_addr;
|
|
socklen_t addr_len = sizeof(client_addr);
|
|
int client_fd = accept(server->fd, (struct sockaddr*)&client_addr, &addr_len);
|
|
|
|
/* Restore blocking mode */
|
|
fcntl(server->fd, F_SETFL, flags);
|
|
|
|
if (client_fd < 0) {
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
/* No pending connection - not an error */
|
|
return NULL;
|
|
}
|
|
set_error("Accept failed");
|
|
return NULL;
|
|
}
|
|
|
|
sw_socket* client = calloc(1, sizeof(sw_socket));
|
|
if (!client) {
|
|
close(client_fd);
|
|
return NULL;
|
|
}
|
|
|
|
client->fd = client_fd;
|
|
client->state = SW_CONN_CONNECTED;
|
|
client->remote_port = ntohs(client_addr.sin_port);
|
|
inet_ntop(AF_INET, &client_addr.sin_addr, client->remote_addr, sizeof(client->remote_addr));
|
|
client->last_activity = current_time();
|
|
|
|
int flag = 1;
|
|
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
|
|
|
|
return client;
|
|
}
|
|
|
|
/* ============================================================= */
|
|
/* Client Operations */
|
|
/* ============================================================= */
|
|
|
|
sw_socket* sw_client_connect(const char* host, uint16_t port, int timeout_sec) {
|
|
if (!host) {
|
|
set_error("Invalid host");
|
|
return NULL;
|
|
}
|
|
|
|
sw_socket* sock = calloc(1, sizeof(sw_socket));
|
|
if (!sock) {
|
|
set_error("Failed to allocate socket");
|
|
return NULL;
|
|
}
|
|
|
|
sock->fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sock->fd < 0) {
|
|
set_error("Failed to create socket");
|
|
free(sock);
|
|
return NULL;
|
|
}
|
|
|
|
/* Resolve hostname */
|
|
struct hostent* he = gethostbyname(host);
|
|
if (!he) {
|
|
set_error("Failed to resolve hostname");
|
|
close(sock->fd);
|
|
free(sock);
|
|
return NULL;
|
|
}
|
|
|
|
struct sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
memcpy(&addr.sin_addr, he->h_addr_list[0], he->h_length);
|
|
addr.sin_port = htons(port);
|
|
|
|
inet_ntop(AF_INET, &addr.sin_addr, sock->remote_addr, sizeof(sock->remote_addr));
|
|
sock->remote_port = port;
|
|
sock->state = SW_CONN_CONNECTING;
|
|
|
|
/* Set non-blocking for connect with timeout */
|
|
int flags = fcntl(sock->fd, F_GETFL, 0);
|
|
fcntl(sock->fd, F_SETFL, flags | O_NONBLOCK);
|
|
|
|
int ret = connect(sock->fd, (struct sockaddr*)&addr, sizeof(addr));
|
|
if (ret < 0 && errno != EINPROGRESS) {
|
|
set_error("Connect failed");
|
|
close(sock->fd);
|
|
free(sock);
|
|
return NULL;
|
|
}
|
|
|
|
/* Wait for connection with timeout */
|
|
fd_set fds;
|
|
FD_ZERO(&fds);
|
|
FD_SET(sock->fd, &fds);
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = timeout_sec;
|
|
tv.tv_usec = 0;
|
|
|
|
ret = select(sock->fd + 1, NULL, &fds, NULL, &tv);
|
|
if (ret <= 0) {
|
|
set_error(ret == 0 ? "Connection timed out" : "Select failed");
|
|
close(sock->fd);
|
|
free(sock);
|
|
return NULL;
|
|
}
|
|
|
|
/* Check for connection error */
|
|
int error = 0;
|
|
socklen_t len = sizeof(error);
|
|
getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &error, &len);
|
|
if (error != 0) {
|
|
errno = error;
|
|
set_error("Connection failed");
|
|
close(sock->fd);
|
|
free(sock);
|
|
return NULL;
|
|
}
|
|
|
|
/* Restore blocking mode */
|
|
fcntl(sock->fd, F_SETFL, flags);
|
|
|
|
sock->state = SW_CONN_CONNECTED;
|
|
sock->last_activity = current_time();
|
|
|
|
/* Enable TCP_NODELAY */
|
|
int flag = 1;
|
|
setsockopt(sock->fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
|
|
|
|
return sock;
|
|
}
|
|
|
|
bool sw_client_reconnect(sw_socket* sock, int timeout_sec) {
|
|
if (!sock || sock->is_server) return false;
|
|
|
|
if (sock->fd >= 0) {
|
|
close(sock->fd);
|
|
}
|
|
|
|
sock->fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sock->fd < 0) {
|
|
set_error("Failed to create socket");
|
|
sock->state = SW_CONN_ERROR;
|
|
return false;
|
|
}
|
|
|
|
struct sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
inet_pton(AF_INET, sock->remote_addr, &addr.sin_addr);
|
|
addr.sin_port = htons(sock->remote_port);
|
|
|
|
sock->state = SW_CONN_CONNECTING;
|
|
|
|
/* Same connection logic as sw_client_connect */
|
|
int flags = fcntl(sock->fd, F_GETFL, 0);
|
|
fcntl(sock->fd, F_SETFL, flags | O_NONBLOCK);
|
|
|
|
int ret = connect(sock->fd, (struct sockaddr*)&addr, sizeof(addr));
|
|
if (ret < 0 && errno != EINPROGRESS) {
|
|
set_error("Reconnect failed");
|
|
sock->state = SW_CONN_ERROR;
|
|
return false;
|
|
}
|
|
|
|
fd_set fds;
|
|
FD_ZERO(&fds);
|
|
FD_SET(sock->fd, &fds);
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = timeout_sec;
|
|
tv.tv_usec = 0;
|
|
|
|
ret = select(sock->fd + 1, NULL, &fds, NULL, &tv);
|
|
if (ret <= 0) {
|
|
set_error(ret == 0 ? "Reconnection timed out" : "Select failed");
|
|
sock->state = SW_CONN_ERROR;
|
|
return false;
|
|
}
|
|
|
|
int error = 0;
|
|
socklen_t len = sizeof(error);
|
|
getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &error, &len);
|
|
if (error != 0) {
|
|
errno = error;
|
|
set_error("Reconnection failed");
|
|
sock->state = SW_CONN_ERROR;
|
|
return false;
|
|
}
|
|
|
|
fcntl(sock->fd, F_SETFL, flags);
|
|
|
|
sock->state = SW_CONN_CONNECTED;
|
|
sock->last_activity = current_time();
|
|
|
|
int flag = 1;
|
|
setsockopt(sock->fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
|
|
|
|
return true;
|
|
}
|
|
|
|
/* ============================================================= */
|
|
/* Common Operations */
|
|
/* ============================================================= */
|
|
|
|
void sw_socket_close(sw_socket* sock) {
|
|
if (!sock) return;
|
|
if (sock->fd >= 0) {
|
|
close(sock->fd);
|
|
}
|
|
free(sock);
|
|
}
|
|
|
|
sw_socket_info sw_socket_get_info(sw_socket* sock) {
|
|
sw_socket_info info = {0};
|
|
if (!sock) return info;
|
|
|
|
info.fd = sock->fd;
|
|
info.state = sock->state;
|
|
strncpy(info.remote_addr, sock->remote_addr, sizeof(info.remote_addr) - 1);
|
|
info.remote_port = sock->remote_port;
|
|
info.bytes_sent = sock->bytes_sent;
|
|
info.bytes_received = sock->bytes_received;
|
|
info.last_activity = sock->last_activity;
|
|
|
|
return info;
|
|
}
|
|
|
|
bool sw_socket_is_connected(sw_socket* sock) {
|
|
return sock && sock->state == SW_CONN_CONNECTED;
|
|
}
|
|
|
|
int sw_socket_fd(sw_socket* sock) {
|
|
return sock ? sock->fd : -1;
|
|
}
|
|
|
|
/* ============================================================= */
|
|
/* Message I/O */
|
|
/* ============================================================= */
|
|
|
|
bool sw_send_msg(sw_socket* sock, uint8_t* msg) {
|
|
if (!sock || !msg || sock->state != SW_CONN_CONNECTED) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t total = sw_msg_total_size(msg);
|
|
size_t sent = 0;
|
|
|
|
while (sent < total) {
|
|
ssize_t n = send(sock->fd, msg + sent, total - sent, MSG_NOSIGNAL);
|
|
if (n < 0) {
|
|
if (errno == EINTR) continue;
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
/* Would block - wait and retry */
|
|
fd_set fds;
|
|
FD_ZERO(&fds);
|
|
FD_SET(sock->fd, &fds);
|
|
struct timeval tv = {SW_IO_TIMEOUT, 0};
|
|
if (select(sock->fd + 1, NULL, &fds, NULL, &tv) <= 0) {
|
|
set_error("Send timeout");
|
|
sock->state = SW_CONN_ERROR;
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
set_error("Send failed");
|
|
sock->state = SW_CONN_ERROR;
|
|
return false;
|
|
}
|
|
if (n == 0) {
|
|
set_error("Connection closed");
|
|
sock->state = SW_CONN_DISCONNECTED;
|
|
return false;
|
|
}
|
|
sent += n;
|
|
}
|
|
|
|
sock->bytes_sent += total;
|
|
sock->last_activity = current_time();
|
|
return true;
|
|
}
|
|
|
|
int sw_send_bytes(sw_socket* sock, const void* data, size_t len) {
|
|
if (!sock || !data || sock->state != SW_CONN_CONNECTED) {
|
|
return -1;
|
|
}
|
|
|
|
ssize_t n = send(sock->fd, data, len, MSG_NOSIGNAL);
|
|
if (n > 0) {
|
|
sock->bytes_sent += n;
|
|
sock->last_activity = current_time();
|
|
} else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
|
|
set_error("Send failed");
|
|
sock->state = SW_CONN_ERROR;
|
|
}
|
|
|
|
return (int)n;
|
|
}
|
|
|
|
uint8_t* sw_recv_msg(sw_socket* sock, int timeout_sec) {
|
|
if (!sock || sock->state != SW_CONN_CONNECTED) {
|
|
return NULL;
|
|
}
|
|
|
|
/* First, receive header */
|
|
uint8_t header_buf[SW_HEADER_SIZE];
|
|
size_t received = 0;
|
|
|
|
time_t start = time(NULL);
|
|
|
|
while (received < SW_HEADER_SIZE) {
|
|
/* Check timeout */
|
|
if (time(NULL) - start > timeout_sec) {
|
|
set_error("Receive timeout");
|
|
return NULL;
|
|
}
|
|
|
|
fd_set fds;
|
|
FD_ZERO(&fds);
|
|
FD_SET(sock->fd, &fds);
|
|
struct timeval tv = {1, 0}; /* 1 second intervals */
|
|
|
|
int ret = select(sock->fd + 1, &fds, NULL, NULL, &tv);
|
|
if (ret < 0) {
|
|
if (errno == EINTR) continue;
|
|
set_error("Select failed");
|
|
sock->state = SW_CONN_ERROR;
|
|
return NULL;
|
|
}
|
|
if (ret == 0) continue; /* Timeout, loop again */
|
|
|
|
ssize_t n = recv(sock->fd, header_buf + received, SW_HEADER_SIZE - received, 0);
|
|
if (n < 0) {
|
|
if (errno == EINTR || errno == EAGAIN) continue;
|
|
set_error("Receive failed");
|
|
sock->state = SW_CONN_ERROR;
|
|
return NULL;
|
|
}
|
|
if (n == 0) {
|
|
set_error("Connection closed");
|
|
sock->state = SW_CONN_DISCONNECTED;
|
|
return NULL;
|
|
}
|
|
received += n;
|
|
}
|
|
|
|
/* Validate header */
|
|
sw_msg_header* hdr = (sw_msg_header*)header_buf;
|
|
if (hdr->magic != SW_PROTOCOL_MAGIC) {
|
|
set_error("Invalid message magic");
|
|
return NULL;
|
|
}
|
|
if (hdr->length > SW_MAX_MESSAGE_SIZE - SW_HEADER_SIZE) {
|
|
set_error("Message too large");
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate full message buffer */
|
|
uint32_t total = SW_HEADER_SIZE + hdr->length;
|
|
uint8_t* msg = malloc(total);
|
|
if (!msg) {
|
|
set_error("Failed to allocate message buffer");
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(msg, header_buf, SW_HEADER_SIZE);
|
|
|
|
/* Receive payload */
|
|
received = 0;
|
|
while (received < hdr->length) {
|
|
if (time(NULL) - start > timeout_sec) {
|
|
set_error("Receive timeout");
|
|
free(msg);
|
|
return NULL;
|
|
}
|
|
|
|
fd_set fds;
|
|
FD_ZERO(&fds);
|
|
FD_SET(sock->fd, &fds);
|
|
struct timeval tv = {1, 0};
|
|
|
|
int ret = select(sock->fd + 1, &fds, NULL, NULL, &tv);
|
|
if (ret < 0) {
|
|
if (errno == EINTR) continue;
|
|
set_error("Select failed");
|
|
sock->state = SW_CONN_ERROR;
|
|
free(msg);
|
|
return NULL;
|
|
}
|
|
if (ret == 0) continue;
|
|
|
|
ssize_t n = recv(sock->fd, msg + SW_HEADER_SIZE + received,
|
|
hdr->length - received, 0);
|
|
if (n < 0) {
|
|
if (errno == EINTR || errno == EAGAIN) continue;
|
|
set_error("Receive failed");
|
|
sock->state = SW_CONN_ERROR;
|
|
free(msg);
|
|
return NULL;
|
|
}
|
|
if (n == 0) {
|
|
set_error("Connection closed");
|
|
sock->state = SW_CONN_DISCONNECTED;
|
|
free(msg);
|
|
return NULL;
|
|
}
|
|
received += n;
|
|
}
|
|
|
|
sock->bytes_received += total;
|
|
sock->last_activity = current_time();
|
|
|
|
return msg;
|
|
}
|
|
|
|
int sw_recv_bytes(sw_socket* sock, void* buf, size_t len, int timeout_sec) {
|
|
if (!sock || !buf || sock->state != SW_CONN_CONNECTED) {
|
|
return -1;
|
|
}
|
|
|
|
fd_set fds;
|
|
FD_ZERO(&fds);
|
|
FD_SET(sock->fd, &fds);
|
|
struct timeval tv = {timeout_sec, 0};
|
|
|
|
int ret = select(sock->fd + 1, &fds, NULL, NULL, &tv);
|
|
if (ret < 0) {
|
|
set_error("Select failed");
|
|
return -1;
|
|
}
|
|
if (ret == 0) {
|
|
return 0; /* Timeout */
|
|
}
|
|
|
|
ssize_t n = recv(sock->fd, buf, len, 0);
|
|
if (n > 0) {
|
|
sock->bytes_received += n;
|
|
sock->last_activity = current_time();
|
|
} else if (n == 0) {
|
|
sock->state = SW_CONN_DISCONNECTED;
|
|
} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
|
set_error("Receive failed");
|
|
sock->state = SW_CONN_ERROR;
|
|
}
|
|
|
|
return (int)n;
|
|
}
|
|
|
|
bool sw_data_available(sw_socket* sock) {
|
|
if (!sock || sock->fd < 0) return false;
|
|
|
|
fd_set fds;
|
|
FD_ZERO(&fds);
|
|
FD_SET(sock->fd, &fds);
|
|
struct timeval tv = {0, 0}; /* Non-blocking check */
|
|
|
|
return select(sock->fd + 1, &fds, NULL, NULL, &tv) > 0;
|
|
}
|
|
|
|
/* ============================================================= */
|
|
/* Multiplexing */
|
|
/* ============================================================= */
|
|
|
|
sw_socket_set* sw_socket_set_new(void) {
|
|
sw_socket_set* set = calloc(1, sizeof(sw_socket_set));
|
|
if (!set) return NULL;
|
|
|
|
set->capacity = 16;
|
|
set->sockets = calloc(set->capacity, sizeof(sw_socket*));
|
|
if (!set->sockets) {
|
|
free(set);
|
|
return NULL;
|
|
}
|
|
|
|
FD_ZERO(&set->read_fds);
|
|
FD_ZERO(&set->write_fds);
|
|
FD_ZERO(&set->error_fds);
|
|
set->max_fd = -1;
|
|
|
|
return set;
|
|
}
|
|
|
|
void sw_socket_set_free(sw_socket_set* set) {
|
|
if (!set) return;
|
|
free(set->sockets);
|
|
free(set);
|
|
}
|
|
|
|
void sw_socket_set_add(sw_socket_set* set, sw_socket* sock) {
|
|
if (!set || !sock || sock->fd < 0) return;
|
|
|
|
/* Expand if needed */
|
|
if (set->num_sockets >= set->capacity) {
|
|
int new_cap = set->capacity * 2;
|
|
sw_socket** new_sockets = realloc(set->sockets, new_cap * sizeof(sw_socket*));
|
|
if (!new_sockets) return;
|
|
set->sockets = new_sockets;
|
|
set->capacity = new_cap;
|
|
}
|
|
|
|
set->sockets[set->num_sockets++] = sock;
|
|
FD_SET(sock->fd, &set->read_fds);
|
|
FD_SET(sock->fd, &set->error_fds);
|
|
|
|
if (sock->fd > set->max_fd) {
|
|
set->max_fd = sock->fd;
|
|
}
|
|
}
|
|
|
|
void sw_socket_set_remove(sw_socket_set* set, sw_socket* sock) {
|
|
if (!set || !sock) return;
|
|
|
|
for (int i = 0; i < set->num_sockets; i++) {
|
|
if (set->sockets[i] == sock) {
|
|
/* Remove from array */
|
|
memmove(&set->sockets[i], &set->sockets[i + 1],
|
|
(set->num_sockets - i - 1) * sizeof(sw_socket*));
|
|
set->num_sockets--;
|
|
|
|
/* Remove from fd_sets */
|
|
if (sock->fd >= 0) {
|
|
FD_CLR(sock->fd, &set->read_fds);
|
|
FD_CLR(sock->fd, &set->write_fds);
|
|
FD_CLR(sock->fd, &set->error_fds);
|
|
}
|
|
|
|
/* Recalculate max_fd */
|
|
set->max_fd = -1;
|
|
for (int j = 0; j < set->num_sockets; j++) {
|
|
if (set->sockets[j]->fd > set->max_fd) {
|
|
set->max_fd = set->sockets[j]->fd;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int sw_socket_set_wait(sw_socket_set* set, int timeout_ms) {
|
|
if (!set || set->num_sockets == 0) return 0;
|
|
|
|
/* Copy fd_sets since select modifies them */
|
|
set->result_read = set->read_fds;
|
|
set->result_write = set->write_fds;
|
|
set->result_error = set->error_fds;
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = timeout_ms / 1000;
|
|
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
|
|
|
return select(set->max_fd + 1, &set->result_read, &set->result_write,
|
|
&set->result_error, &tv);
|
|
}
|
|
|
|
bool sw_socket_set_is_readable(sw_socket_set* set, sw_socket* sock) {
|
|
if (!set || !sock || sock->fd < 0) return false;
|
|
return FD_ISSET(sock->fd, &set->result_read);
|
|
}
|
|
|
|
bool sw_socket_set_is_writable(sw_socket_set* set, sw_socket* sock) {
|
|
if (!set || !sock || sock->fd < 0) return false;
|
|
return FD_ISSET(sock->fd, &set->result_write);
|
|
}
|
|
|
|
bool sw_socket_set_has_error(sw_socket_set* set, sw_socket* sock) {
|
|
if (!set || !sock || sock->fd < 0) return false;
|
|
return FD_ISSET(sock->fd, &set->result_error);
|
|
}
|
|
|
|
/* ============================================================= */
|
|
/* Utility */
|
|
/* ============================================================= */
|
|
|
|
const char* sw_socket_error(void) {
|
|
return last_error;
|
|
}
|
|
|
|
bool sw_resolve_host(const char* hostname, char* ip_out, size_t ip_len) {
|
|
if (!hostname || !ip_out) return false;
|
|
|
|
struct hostent* he = gethostbyname(hostname);
|
|
if (!he) return false;
|
|
|
|
inet_ntop(AF_INET, he->h_addr_list[0], ip_out, ip_len);
|
|
return true;
|
|
}
|
|
|
|
bool sw_get_local_ip(char* ip_out, size_t ip_len) {
|
|
if (!ip_out) return false;
|
|
|
|
char hostname[256];
|
|
if (gethostname(hostname, sizeof(hostname)) < 0) {
|
|
return false;
|
|
}
|
|
|
|
return sw_resolve_host(hostname, ip_out, ip_len);
|
|
}
|
|
|
|
bool sw_set_nonblocking(sw_socket* sock, bool nonblocking) {
|
|
if (!sock || sock->fd < 0) return false;
|
|
|
|
int flags = fcntl(sock->fd, F_GETFL, 0);
|
|
if (flags < 0) return false;
|
|
|
|
if (nonblocking) {
|
|
flags |= O_NONBLOCK;
|
|
} else {
|
|
flags &= ~O_NONBLOCK;
|
|
}
|
|
|
|
return fcntl(sock->fd, F_SETFL, flags) >= 0;
|
|
}
|
|
|
|
bool sw_set_nodelay(sw_socket* sock, bool nodelay) {
|
|
if (!sock || sock->fd < 0) return false;
|
|
int flag = nodelay ? 1 : 0;
|
|
return setsockopt(sock->fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)) >= 0;
|
|
}
|
|
|
|
bool sw_set_keepalive(sw_socket* sock, bool keepalive) {
|
|
if (!sock || sock->fd < 0) return false;
|
|
int flag = keepalive ? 1 : 0;
|
|
return setsockopt(sock->fd, SOL_SOCKET, SO_KEEPALIVE, &flag, sizeof(flag)) >= 0;
|
|
}
|