diff --git a/Base/c_Types.h b/Base/c_Types.h index 2e40152..6b323b1 100644 --- a/Base/c_Types.h +++ b/Base/c_Types.h @@ -49,6 +49,11 @@ #include #endif /*INCLUDED_CTYPE_H*/ +#ifndef INCLUDED_ASSERT_H +#define INCLUDED_ASSERT_H +#include +#endif /*INCLUDED_ASSERT_H*/ + /* ------------------------------------------------------------------------------------------------------------------ */ @@ -113,6 +118,7 @@ typedef int c_err_t; #define C_ERR_EMPTY (-5) #define C_ERR_FULL (-6) #define C_ERR_EXIST (-7) +#define C_ERR_CLOSED (-8) #define C_SUCCESS C_ERR_OK #define C_ERR_NOTFOUND C_ERR_FAIL diff --git a/Foundation/c_AddrInfo.c b/Foundation/c_AddrInfo.c new file mode 100644 index 0000000..84bb70b --- /dev/null +++ b/Foundation/c_AddrInfo.c @@ -0,0 +1,30 @@ +#include + +c_err_t c_AddrInfo_Init(c_AddrInfo_t* self, + int family, + int socktype, + int flags, + const char* server, const char* port) { + + struct addrinfo hints={0}; + hints.ai_family = family; + hints.ai_socktype = socktype; + hints.ai_flags = flags; + + int err = getaddrinfo(server, port, &hints, &self->addrinfo_p); + if (err != 0) { + self->addrinfo_p = NULL; + return C_ERR_FAIL; + } + + return C_ERR_OK; +} + +void c_AddrInfo_Destroy(c_AddrInfo_t* self) { + if (!self || !self->addrinfo_p) { + return; + } + freeaddrinfo(self->addrinfo_p); + self->addrinfo_p = NULL; +} + diff --git a/Foundation/c_AddrInfo.h b/Foundation/c_AddrInfo.h new file mode 100644 index 0000000..dce1f2c --- /dev/null +++ b/Foundation/c_AddrInfo.h @@ -0,0 +1,60 @@ +#ifndef INCLUDED_C_ADDRINFO_H +#define INCLUDED_C_ADDRINFO_H + +#ifndef INCLUDED_C_SOCKET_H +#include +#endif /*INCLUDED_C_SOCKET_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + struct addrinfo* addrinfo_p; +}c_AddrInfo_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_AddrInfo_Init(c_AddrInfo_t* self, + int family, + int socktype, + int flags, + const char* server, const char* port); + +void c_AddrInfo_Destroy(c_AddrInfo_t* self); + +C_STATIC_FORCE_INLINE +c_err_t c_AddrInfo_GetNext(c_AddrInfo_t* self, c_AddrInfo_t* next){ + if (!self || !self->addrinfo_p) return C_ERR_PARAM; + if (next) { + next->addrinfo_p = self->addrinfo_p->ai_next; + } + return C_ERR_OK; +} + +C_STATIC_FORCE_INLINE +int c_AddrInfo_GetFlags(c_AddrInfo_t* self) { + if (!self || !self->addrinfo_p) return -1; + return self->addrinfo_p->ai_flags; +} + +C_STATIC_FORCE_INLINE +int c_AddrInfo_GetFamily(c_AddrInfo_t* self) { + if (!self || !self->addrinfo_p) return -1; + return self->addrinfo_p->ai_family; +} + +C_STATIC_FORCE_INLINE +int c_AddrInfo_GetSockType(c_AddrInfo_t* self) { + if (!self || !self->addrinfo_p) return -1; + return self->addrinfo_p->ai_socktype; +} + +C_STATIC_FORCE_INLINE +int c_AddrInfo_GetProtocol(c_AddrInfo_t* self) { + if (!self || !self->addrinfo_p) return -1; + return self->addrinfo_p->ai_protocol; +} + +#endif /*INCLUDED_C_ADDRINFO_H*/ diff --git a/Foundation/c_AddrInfo.t.c b/Foundation/c_AddrInfo.t.c new file mode 100644 index 0000000..534d79b --- /dev/null +++ b/Foundation/c_AddrInfo.t.c @@ -0,0 +1,33 @@ +#include "c_Test.h" +#include "c_AddrInfo.h" + +TEST_CASE(test_addrinfo_lookup_and_getters){ + c_Socket_Init(); + + c_AddrInfo_t info; + /* Look up local loopback configuration boundaries over a sample port string */ + c_err_t err = c_AddrInfo_Init(&info, AF_INET, SOCK_STREAM, AI_PASSIVE, "127.0.0.1", "8080"); + ASSERT_INT_EQ(C_ERR_OK, err); + ASSERT_TRUE(info.addrinfo_p != NULL); + + /* Test getter properties mapping */ + ASSERT_INT_EQ(AF_INET, c_AddrInfo_GetFamily(&info)); + ASSERT_INT_EQ(SOCK_STREAM, c_AddrInfo_GetSockType(&info)); + ASSERT_TRUE(c_AddrInfo_GetFlags(&info) != -1); + + /* Test navigation list iterator lookups */ + c_AddrInfo_t next_node; + err = c_AddrInfo_GetNext(&info, &next_node); + ASSERT_INT_EQ(C_ERR_OK, err); + + c_AddrInfo_Destroy(&info); + + c_Socket_Destroy(); +} + +int main(void) { + TEST_START(AddrInfo_Resolution_Suite); + RUN_TEST(test_addrinfo_lookup_and_getters); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Foundation/c_SockAddr.c b/Foundation/c_SockAddr.c new file mode 100644 index 0000000..6424478 --- /dev/null +++ b/Foundation/c_SockAddr.c @@ -0,0 +1,68 @@ +#include + +c_err_t c_SockAddr_Init(c_SockAddr_t* self, int family, const char* ip, uint16_t port) { + if (!self ) return C_ERR_PARAM; + + /* Wipe the target memory layout clear up front to prevent uninitialized garbage bits */ + memset(self, 0, sizeof(c_SockAddr_t)); + + /* 1. Parse and initialize as an IPv4 Address */ + if (family == AF_INET) { + self->ipv4.sin_family = AF_INET; + self->ipv4.sin_port = htons(port); + self->size = sizeof(self->ipv4); + if (ip) { + if (inet_pton(AF_INET, ip, &(self->ipv4.sin_addr)) == 1) { + return C_ERR_OK; + }else { + return C_ERR_FAIL; /* Invalid IPv4 string format */ + } + }else { + self->ipv4.sin_addr.s_addr = htonl(INADDR_ANY); + } + return C_ERR_OK; + } + + /* 2. Parse and initialize as an IPv6 Address */ + if (family == AF_INET6) { + self->ipv6.sin6_family = AF_INET6; + self->ipv6.sin6_port = htons(port); + self->size = sizeof(self->ipv6); + + if (ip) { + if (inet_pton(AF_INET6, ip, &(self->ipv6.sin6_addr)) == 1) { + return C_ERR_OK; + }else { + return C_ERR_FAIL;/* Invalid IPv6 string format */ + } + }else { + self->ipv6.sin6_addr = in6addr_any; + } + + return C_ERR_OK; + } + + /* 3. Return an parameter error if an unsupported address family parameter is requested */ + return C_ERR_PARAM; +} + +c_err_t c_SockAddr_ToString(const c_SockAddr_t* self, char* out_str, c_size_t str_cap) { + if (!self || !out_str || str_cap == 0) return C_ERR_PARAM; + + const void* src_addr = NULL; + int family = self->ipv4.sin_family; + + if (family == AF_INET) { + src_addr = &(self->ipv4.sin_addr); + } else if (family == AF_INET6) { + src_addr = &(self->ipv6.sin6_addr); + } else { + return C_ERR_FAIL; /* Unknown address family variant */ + } + + if (inet_ntop(family, src_addr, out_str, (size_t)str_cap) == NULL) { + return C_ERR_OUTOFBOUND; /* String buffer capacity limit was too small */ + } + + return C_ERR_OK; +} diff --git a/Foundation/c_SockAddr.h b/Foundation/c_SockAddr.h new file mode 100644 index 0000000..ccddac3 --- /dev/null +++ b/Foundation/c_SockAddr.h @@ -0,0 +1,64 @@ +#ifndef INCLUDED_C_SOCKADDR_H +#define INCLUDED_C_SOCKADDR_H + +#ifndef INCLUDED_C_SOCKET_H +#include +#endif /*INCLUDED_C_SOCKET_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + union { + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + struct sockaddr_storage storage; + }; + c_size_t size; +}c_SockAddr_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_SockAddr_Init(c_SockAddr_t* self, int family, const char* ip, uint16_t port) ; + +/** + * @brief Gets the address family (AF_INET or AF_INET6) from the sockaddr variant. + * @return The address family integer, or -1 on parameter error. + */ +C_STATIC_FORCE_INLINE +int c_SockAddr_GetFamily(const c_SockAddr_t* self) { + if (!self) return -1; + /* Since both structure variations map the sa_family / sin_family field + at byte offset 0, reading from ipv4 is universally safe across families */ + return (int)self->ipv4.sin_family; +} + +/** + * @brief Safely returns the port number (in host byte order) from the sockaddr variant. + * @return The port integer (0 to 65535), or 0 on error. + */ +C_STATIC_FORCE_INLINE +uint16_t c_SockAddr_GetPort(const c_SockAddr_t* self) { + if (!self) return 0; + if (self->ipv4.sin_family == AF_INET) { + return ntohs(self->ipv4.sin_port); + } else if (self->ipv4.sin_family == AF_INET6) { + return ntohs(self->ipv6.sin6_port); + } + return 0; +} + + +/** + * @brief Converts the embedded binary IP address to a safe human-readable text string. + * @param out_str Pre-allocated destination character string buffer. + * @param str_cap Capacity ceiling limit of out_str (Recommended min size: INET6_ADDRSTRLEN). + */ +c_err_t c_SockAddr_ToString(const c_SockAddr_t* self, char* out_str, c_size_t str_cap); + + + +#endif /*INCLUDED_C_SOCKADDR_H*/ diff --git a/Foundation/c_SockAddr.t.c b/Foundation/c_SockAddr.t.c new file mode 100644 index 0000000..51e566e --- /dev/null +++ b/Foundation/c_SockAddr.t.c @@ -0,0 +1,81 @@ +#include "c_Test.h" +#include "c_SockAddr.h" +#include "c_SockAddrUtil.h" + +#if defined(_WIN32) || defined(_WIN64) + #pragma comment(lib, "ws2_32.lib") +#endif + +TEST_CASE(test_sockaddr_variant_extraction_and_string) { + c_Socket_Init(); + + c_AddrInfo_t addr; + /* Look up explicit local target constraints loopback */ + c_err_t err = c_AddrInfo_Init(&addr, AF_INET, SOCK_STREAM, 0, "127.0.0.1", "9090"); + ASSERT_INT_EQ(C_ERR_OK, err); + + /* 1. Extract raw byte structure layouts into our custom anonymous union container */ + c_SockAddr_t sock_addr; + err = c_SockAddrUtil_FromAddrInfo(&sock_addr, &addr); + ASSERT_INT_EQ(C_ERR_OK, err); + + /* 2. Validate getter transformations */ + ASSERT_INT_EQ(AF_INET, c_SockAddr_GetFamily(&sock_addr)); + ASSERT_INT_EQ(9090, (int)c_SockAddr_GetPort(&sock_addr)); + + /* 3. Re-render structural bits to string format */ + char ip_str[64]; + err = c_SockAddr_ToString(&sock_addr, ip_str, sizeof(ip_str)); + ASSERT_INT_EQ(C_ERR_OK, err); + ASSERT_INT_EQ(0, strcmp("127.0.0.1", ip_str)); + + c_AddrInfo_Destroy(&addr); + + c_Socket_Destroy(); +} + +TEST_CASE(test_sockaddr_explicit_family_initialization) { + c_Socket_Init(); + + c_SockAddr_t addr; + c_err_t err; + + /* -------------------------------------------------------------------------------------------------------------- */ + /* Case A: Verify explicit IPv4 initialization and dynamic getters */ + err = c_SockAddr_Init(&addr, AF_INET, "127.0.0.1", 8080); + ASSERT_INT_EQ(C_ERR_OK, err); + ASSERT_INT_EQ(AF_INET, c_SockAddr_GetFamily(&addr)); + ASSERT_INT_EQ(8080, (int)c_SockAddr_GetPort(&addr)); + + char out_ip_v4[INET_ADDRSTRLEN]; + err = c_SockAddr_ToString(&addr, out_ip_v4, sizeof(out_ip_v4)); + ASSERT_INT_EQ(C_ERR_OK, err); + ASSERT_INT_EQ(0, strcmp("127.0.0.1", out_ip_v4)); + + /* -------------------------------------------------------------------------------------------------------------- */ + /* Case B: Verify explicit IPv6 initialization and loopback string decoding */ + err = c_SockAddr_Init(&addr, AF_INET6, "::1", 9090); + ASSERT_INT_EQ(C_ERR_OK, err); + ASSERT_INT_EQ(AF_INET6, c_SockAddr_GetFamily(&addr)); + ASSERT_INT_EQ(9090, (int)c_SockAddr_GetPort(&addr)); + + char out_ip_v6[INET6_ADDRSTRLEN]; + err = c_SockAddr_ToString(&addr, out_ip_v6, sizeof(out_ip_v6)); + ASSERT_INT_EQ(C_ERR_OK, err); + ASSERT_INT_EQ(0, strcmp("::1", out_ip_v6)); + + /* -------------------------------------------------------------------------------------------------------------- */ + /* Case C: Verify that incorrect cross-family inputs gracefully yield execution failures */ + err = c_SockAddr_Init(&addr, AF_INET, "::1", 80); /* Passing IPv6 loopback string to IPv4 family parser */ + ASSERT_INT_EQ(C_ERR_FAIL, err); + + c_Socket_Destroy(); +} + +int main(void) { + TEST_START(SockAddr_Variant_Suite); + RUN_TEST(test_sockaddr_variant_extraction_and_string); + RUN_TEST(test_sockaddr_explicit_family_initialization); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Foundation/c_SockAddrUtil.c b/Foundation/c_SockAddrUtil.c new file mode 100644 index 0000000..89c1380 --- /dev/null +++ b/Foundation/c_SockAddrUtil.c @@ -0,0 +1 @@ +#include diff --git a/Foundation/c_SockAddrUtil.h b/Foundation/c_SockAddrUtil.h new file mode 100644 index 0000000..780d26a --- /dev/null +++ b/Foundation/c_SockAddrUtil.h @@ -0,0 +1,35 @@ +#ifndef INCLUDED_C_SOCKADDRUTIL_H +#define INCLUDED_C_SOCKADDRUTIL_H + +#ifndef INCLUDED_C_SOCKADDR_H +#include +#endif /*INCLUDED_C_SOCKADDR_H*/ + + +#ifndef INCLUDED_C_ADDRINFO_H +#include +#endif /*INCLUDED_C_ADDRINFO_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +/** + * @brief Extends extraction mapping from a resolved c_AddrInfo_t instance. + */ +C_STATIC_FORCE_INLINE c_err_t c_SockAddrUtil_FromAddrInfo(c_SockAddr_t* sock_addr, const c_AddrInfo_t* addr) { + if (!sock_addr || !addr || !addr->addrinfo_p) return C_ERR_PARAM; + + size_t len = addr->addrinfo_p->ai_addrlen; + if (addr->addrinfo_p->ai_family == AF_INET && len <= sizeof(struct sockaddr_in)) { + memcpy(&sock_addr->ipv4, addr->addrinfo_p->ai_addr, len); + return C_ERR_OK; + } else if (addr->addrinfo_p->ai_family == AF_INET6 && len <= sizeof(struct sockaddr_in6)) { + memcpy(&sock_addr->ipv6, addr->addrinfo_p->ai_addr, len); + return C_ERR_OK; + } + return C_ERR_FAIL; +} + +#endif /*INCLUDED_C_SOCKADDRUTIL_H*/ diff --git a/Foundation/c_Socket.h b/Foundation/c_Socket.h index 04e0216..6713e69 100644 --- a/Foundation/c_Socket.h +++ b/Foundation/c_Socket.h @@ -5,36 +5,139 @@ #include #endif /*INCLUDED_C_TYPES_H*/ +#ifndef INCLUDED_STDIO_H +#define INCLUDED_STDIO_H +#include +#endif /*INCLUDED_STDIO_H*/ + +#ifndef INCLUDED_STDLIB_H +#define INCLUDED_STDLIB_H +#include +#endif /*INCLUDED_STDLIB_H*/ /* ------------------------------------------------------------------------------------------------------------------ */ /* */ #if defined(_WIN32) || defined(_WIN64) - #include - typedef SOCKET c_socket_t; - #define C_INVALID_SOCKET INVALID_SOCKET - #define C_SOCKET_ERROR SOCKET_ERROR - #define c_Socket_Close(x) closesocket(x) - C_STATIC_FORCE_INLINE - void c_Socket_Init(void) { - WSADATA wsa; WSAStartup(MAKEWORD(2,2), &wsa); - } +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 +#endif + +#ifndef INCLUDED_WINSOCK2_H +#define INCLUDED_WINSOCK2_H +#include +#endif /*INCLUDED_WINSOCK2_H*/ + +#ifndef INCLUDED_IPHLPAPI_H +#define INCLUDED_IPHLPAPI_H +#include +#endif /*INCLUDED_IPHLPAPI_H*/ + +#ifndef INCLUDED_WS2TCPIP_H +#define INCLUDED_WS2TCPIP_H +#include +#endif /*INCLUDED_WS2TCPIP_H*/ + + +typedef SOCKET c_socket_t; + +#define C_INVALID_SOCKET INVALID_SOCKET +#define C_SOCKET_ERROR SOCKET_ERROR + +#define c_Socket_Close(x) (closesocket(x), (x)=C_INVALID_SOCKET) +#define c_Socket_IsValid(s) ((s)!=INVALID_SOCKET) +#define c_Socket_GetErrno() (WSAGetLastError()) + +C_STATIC_FORCE_INLINE +void c_Socket_Init(void) { + WSADATA wsa; + int err = WSAStartup(MAKEWORD(2,2), &wsa); + assert(err==0); +} + +C_STATIC_FORCE_INLINE +void c_Socket_Destroy(void) { + WSACleanup(); +} + +#else /* other platform */ + +#ifndef INCLUDED_SYS_TYPES_H +#define INCLUDED_SYS_TYPES_H +#include +#endif /*INCLUDED_SYS_TYPES_H*/ + +#ifndef INCLUDED_SYS_SOCKET_H +#define INCLUDED_SYS_SOCKET_H +#include +#endif /*INCLUDED_SYS_SOCKET_H*/ + +#ifndef INCLUDED_NETDB_H +#define INCLUDED_NETDB_H +#include +#endif /*INCLUDED_NETDB_H*/ + +#ifndef INCLUDED_NETINET_IN_H +#define INCLUDED_NETINET_IN_H +#include +#endif /*INCLUDED_NETINET_IN_H*/ + +#ifndef INCLUDED_ARPA_INET_H +#define INCLUDED_ARPA_INET_H +#include +#endif /*INCLUDED_ARPA_INET_H*/ + +#ifndef INCLUDED_UNISTD_H +#define INCLUDED_UNISTD_H +#include +#endif /*INCLUDED_UNISTD_H*/ + +#ifndef INCLUDED_ERRNO_H +#define INCLUDED_ERRNO_H +#include +#endif /*INCLUDED_ERRNO_H*/ + + + + +typedef int c_socket_t; + +#define C_INVALID_SOCKET (-1) +#define C_SOCKET_ERROR (-1) + +#define c_Socket_IsValid(s) ((s)>=0) +#define c_Socket_Close(x) (close(x), (x)=C_INVALID_SOCKET) +#define c_Socket_GetErrno() (errno) +#define c_Socket_Init() +#define c_Socket_Destroy() - C_STATIC_FORCE_INLINE - void c_Socket_Destroy(void) { - WSACleanup(); - } -#else - typedef int c_socket_t; - #define C_INVALID_SOCKET (-1) - #define C_SOCKET_ERROR (-1) - #define c_Socket_Close(x) close(x) - #define c_Socket_Init() - #define c_Socket_Destroy() #endif +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +void c_Socket_SetReuseAddr(c_socket_t s, c_bool_t is_reuse) { + int reuse = is_reuse?1:0; +#if defined(_WIN32) || defined(_WIN64) + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)); +#else + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); +#endif +} + +C_STATIC_FORCE_INLINE +void c_Socket_SetIPV6Only(c_socket_t s, c_bool_t is_IPV6only) { + int ipv6only = is_IPV6only?1:0; +#if defined(_WIN32) || defined(_WIN64) + setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&ipv6only, sizeof(ipv6only)); +#else + setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only)); +#endif +} + #endif /*INCLUDED_C_SOCKET_H*/ diff --git a/Foundation/c_SocketUtil.c b/Foundation/c_SocketUtil.c new file mode 100644 index 0000000..98dc0e9 --- /dev/null +++ b/Foundation/c_SocketUtil.c @@ -0,0 +1 @@ +#include diff --git a/Foundation/c_SocketUtil.h b/Foundation/c_SocketUtil.h new file mode 100644 index 0000000..c0db00f --- /dev/null +++ b/Foundation/c_SocketUtil.h @@ -0,0 +1,140 @@ +#ifndef INCLUDED_C_SOCKETUTIL_H +#define INCLUDED_C_SOCKETUTIL_H + +#ifndef INCLUDED_C_SOCKET_H +#include +#endif /*INCLUDED_C_SOCKET_H*/ + +#ifndef INCLUDED_C_ADDRINFO_H +#include +#endif /*INCLUDED_C_ADDRINFO_H*/ + + +#ifndef INCLUDED_C_SOCKADDR_H +#include +#endif /*INCLUDED_C_SOCKADDR_H*/ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +c_socket_t c_SocketUtil_CreateFromAddrInfo(c_AddrInfo_t* addr_info) { + if (!addr_info) return C_INVALID_SOCKET; + return socket(c_AddrInfo_GetFamily(addr_info), + c_AddrInfo_GetSockType(addr_info), + c_AddrInfo_GetProtocol(addr_info)); +} + +C_STATIC_FORCE_INLINE +c_err_t c_SocketUtil_BindWithAddrInfo(c_socket_t s, c_AddrInfo_t* addr_info) { + if (!c_Socket_IsValid(s) || !addr_info) return C_ERR_PARAM; + if (bind(s, addr_info->addrinfo_p->ai_addr, (int)addr_info->addrinfo_p->ai_addrlen) < 0) { + return C_ERR_FAIL; + } + return C_ERR_OK; +} + +C_STATIC_FORCE_INLINE +c_err_t c_SocketUtil_BindWithSockAddr(c_socket_t s, c_SockAddr_t* sock_addr) { + if (!c_Socket_IsValid(s) || !sock_addr) return C_ERR_PARAM; + if (bind(s, (struct sockaddr*)&sock_addr->storage, (socklen_t)sock_addr->size) < 0) { + return C_ERR_FAIL; + } + return C_ERR_OK; +} + +C_STATIC_FORCE_INLINE +c_socket_t c_SocketUtil_Accept(c_socket_t s, c_SockAddr_t* sock_addr) { + if (!c_Socket_IsValid(s) || !sock_addr) return C_INVALID_SOCKET; + memset(sock_addr, 0, sizeof(c_SockAddr_t)); + socklen_t client_len = sizeof(sock_addr->storage); + c_socket_t socket_client = accept(s, (struct sockaddr*) &sock_addr->storage, &client_len); + if (!c_Socket_IsValid(socket_client)) return C_INVALID_SOCKET; + sock_addr->size = client_len; + return socket_client; +} + +C_STATIC_FORCE_INLINE +c_err_t c_SocketUtil_GetNameInfoForSockAddr(c_SockAddr_t* sock_addr, char* host, c_size_t host_len, char* port, c_size_t port_len) { + if (!sock_addr) return C_ERR_PARAM; + + if ((host && host_len > 0) && (!port || port_len==0)) { + int err = getnameinfo((struct sockaddr*)&sock_addr->storage, (socklen_t)sock_addr->size, + host, host_len, NULL, 0, NI_NUMERICHOST); + if (err!=0) { + return C_ERR_FAIL; + } + }else if ((!host || host_len == 0) && (port && port_len >0)) { + int err = getnameinfo((struct sockaddr*)&sock_addr->storage, (socklen_t)sock_addr->size + , NULL, 0, + port, port_len, + NI_NUMERICSERV); + if (err!=0) { + return C_ERR_FAIL; + } + }else { + int err = getnameinfo((struct sockaddr*)&sock_addr->storage, (socklen_t)sock_addr->size + , host, host_len + , port, port_len + , NI_NUMERICHOST|NI_NUMERICSERV); + if (err!=0) { + return C_ERR_FAIL; + } + } + return C_ERR_OK; +} + + +C_STATIC_FORCE_INLINE +c_err_t c_SocketUtil_Send(c_socket_t s, void* data, size_t data_len, c_size_t* send_size) { + if (!c_Socket_IsValid(s) || !data) return C_ERR_PARAM; + ssize_t err = send(s, (const char*)data, (int)data_len, 0); + if (err==0) return C_ERR_CLOSED; + if (err<0) return C_ERR_FAIL; + if (send_size) *send_size = err; + return C_ERR_OK; +} + +C_STATIC_FORCE_INLINE +c_err_t c_SocketUtil_GetNameInfoForSocket(c_socket_t s, char* host, c_size_t host_len + , char* port, c_size_t port_len) { + if (!c_Socket_IsValid(s) ) return C_ERR_PARAM; + + struct sockaddr_storage peer_addr; + socklen_t addr_len = sizeof(peer_addr); + + // 2. 获取对方的套接字地址信息 + if (getpeername(s, (struct sockaddr*)&peer_addr, &addr_len) == -1) { + return C_ERR_FAIL; + } + + if (host && host_len > 0 && (!port || port_len==0)) { + int err = getnameinfo((struct sockaddr*)&peer_addr, addr_len, + host, host_len, NULL, 0, NI_NUMERICHOST); + if (err!=0) { + return C_ERR_FAIL; + } + }else if (port && port_len > 0 && (!host || host_len==0)) { + int err = getnameinfo((struct sockaddr*)&peer_addr, addr_len, + NULL, 0, + port, port_len, + NI_NUMERICSERV); + if (err!=0) { + return C_ERR_FAIL; + } + }else { + int err = getnameinfo((struct sockaddr*)&peer_addr, addr_len, + host, host_len, + port, port_len, + NI_NUMERICHOST|NI_NUMERICSERV); + if (err!=0) { + return C_ERR_FAIL; + } + } + + return C_ERR_OK; +} + +#endif /*INCLUDED_C_SOCKETUTIL_H*/ diff --git a/Foundation/c_Socket_TimeSvr.t.c b/Foundation/c_Socket_TimeSvr.t.c new file mode 100644 index 0000000..eb9881c --- /dev/null +++ b/Foundation/c_Socket_TimeSvr.t.c @@ -0,0 +1,85 @@ +#include "c_Socket.h" +#include +#include +#include "c_AddrInfo.h" +#include "c_SockAddrUtil.h" +#include "c_SocketUtil.h" + +int main(int argc, char** argv){ + c_Socket_Init(); + + printf("Configuring local address...\n"); + + c_AddrInfo_t bindAddr; + c_AddrInfo_Init(&bindAddr, AF_INET, SOCK_STREAM, AI_PASSIVE, 0, "8080"); + + + printf("Creating socket...\n"); + c_socket_t socket_listen = c_SocketUtil_CreateFromAddrInfo(&bindAddr); + + if (!c_Socket_IsValid(socket_listen)) { + fprintf(stderr, "socket() failed. (%d)\n", c_Socket_GetErrno()); + return 1; + } + + printf("Binding socket to local address...\n"); + if (c_SocketUtil_BindWithAddrInfo(socket_listen, &bindAddr) !=C_ERR_OK) { + fprintf(stderr, "bind() failed. (%d)\n", c_Socket_GetErrno()); + return 1; + } + + c_AddrInfo_Destroy(&bindAddr); + + + printf("Listening...\n"); + if (listen(socket_listen, 10) < 0) { + fprintf(stderr, "listen() failed. (%d)\n", c_Socket_GetErrno()); + return 1; + } + + printf("Waiting for connection...\n"); + c_SockAddr_t client_address; + c_socket_t socket_client = c_SocketUtil_Accept(socket_listen, &client_address); + if (!c_Socket_IsValid(socket_client)) { + fprintf(stderr, "accept() failed. (%d)\n", c_Socket_GetErrno()); + return 1; + } + + printf("Client is connected... "); + char address_buffer[100]; + c_SocketUtil_GetNameInfoForSockAddr(&client_address, address_buffer, sizeof(address_buffer), 0, 0); + printf("%s\n", address_buffer); + + printf("Reading request...\n"); + char request[1024]; + int bytes_received = recv(socket_client, request, 1024, 0); + printf("Received %d bytes.\n", bytes_received); + + printf("Sending response...\n"); + const char *response = + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Content-Type: text/plain\r\n\r\n" + "Local time is: "; + int bytes_sent = send(socket_client, response, strlen(response), 0); + printf("Sent %d of %d bytes.\n", bytes_sent, (int)strlen(response)); + + time_t timer; + time(&timer); + char *time_msg = ctime(&timer); + bytes_sent = send(socket_client, time_msg, strlen(time_msg), 0); + printf("Sent %d of %d bytes.\n", bytes_sent, (int)strlen(time_msg)); + + printf("Closing connection...\n"); + c_Socket_Close(socket_client); + + + printf("Closing listening socket...\n"); + c_Socket_Close(socket_listen); + + c_Socket_Destroy(); + + printf("Finished.\n"); + + return 0; +} diff --git a/Foundation/c_TcpPollReactor.c b/Foundation/c_TcpPollReactor.c new file mode 100644 index 0000000..a2ced06 --- /dev/null +++ b/Foundation/c_TcpPollReactor.c @@ -0,0 +1,170 @@ +#include + +#if !defined(_WIN32) && !defined(_WIN64) + #include +#endif + +typedef struct { + c_TcpReactor_t base; + struct pollfd* fds; + c_size_t count; + c_size_t capacity; + c_Allocator_t allocator; +} c_PollReactorImpl_t; + +/* (Adheres to the O(1) packing allocations logic established previously) */ +static c_err_t Poll_Add(c_TcpReactor_t* self, c_socket_t sock, uint32_t events) { + c_PollReactorImpl_t* impl = (c_PollReactorImpl_t*)self; + if (impl->count >= impl->capacity) { + c_size_t old_cap = impl->capacity; + c_size_t new_cap = old_cap == 0 ? 16 : old_cap * 2; + struct pollfd* new_fds = (struct pollfd*)c_Allocator_Realloc(&impl->allocator, impl->fds, old_cap * sizeof(struct pollfd), new_cap * sizeof(struct pollfd)); + if (!new_fds) return C_ERR_NOMEM; + impl->fds = new_fds; + impl->capacity = new_cap; + } + impl->fds[impl->count].fd = (int)sock; + impl->fds[impl->count].events = (short)(events ? POLLIN : 0); + impl->fds[impl->count].revents = 0; + impl->count++; + return C_SUCCESS; +} + +static c_err_t Poll_Remove(c_TcpReactor_t* self, c_socket_t sock) { + c_PollReactorImpl_t* impl = (c_PollReactorImpl_t*)self; + for (c_size_t i = 0; i < impl->count; ++i) { + if (impl->fds[i].fd == (int)sock) { + impl->fds[i] = impl->fds[impl->count - 1]; /* O(1) tail-swap */ + impl->count--; + return C_SUCCESS; + } + } + return C_ERR_NOTFOUND; +} + +static c_err_t Poll_Poll(c_TcpReactor_t* self, long timeout_ms, c_TcpServer_t* server, void* args) { + c_PollReactorImpl_t* impl = (c_PollReactorImpl_t*)self; + if (impl->count == 0 || !server->is_running) return C_SUCCESS; + +#if defined(_WIN32) || defined(_WIN64) + int ret = WSAPoll(impl->fds, (ULONG)impl->count, timeout_ms); +#else + int ret = poll(impl->fds, (nfds_t)impl->count, timeout_ms); +#endif + + if (ret < 0) { + if (server->fnOnError) { + server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_POLL, args); + } + } + if (ret == 0) return C_SUCCESS; + + for (c_size_t i = impl->count; i > 0; --i) { + c_size_t idx = i - 1; + struct pollfd* current_fd = &impl->fds[idx]; + + if (current_fd->revents == 0) continue; + + if (current_fd->fd == (int)server->listen_sock) { + if (current_fd->revents & POLLIN) { + c_SockAddr_t peer_addr; + c_socket_t client = c_SocketUtil_Accept(server->listen_sock, &peer_addr); + + if (c_Socket_IsValid(client)) { + bool keep = true; + if (server->fnOnConnect) { + keep = server->fnOnConnect(server, client, &peer_addr, args); + } + + if (keep) { + Poll_Add(self, client, 1); + } else { + c_Socket_Close(client); + } + }else if (server->fnOnError) { + server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_ACCEPT, args); + } + }else if (current_fd->revents & (POLLERR | POLLHUP)) { + if (server->fnOnError) { + server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_POLLERR, args); + } + } + ret--; + } + else { + c_socket_t client_sock = (c_socket_t)current_fd->fd; + + if (current_fd->revents & (POLLIN | POLLHUP | POLLERR)) { + c_bool_t keep_alive = C_TRUE; + + /* Trigger error logging if a structural socket error bit is present */ + if (current_fd->revents & POLLERR) { + if (server->fnOnError) { + server->fnOnError(server, client_sock, C_TCPSERVER_ERR_ON_POLLERR, args); + } + keep_alive = C_FALSE; + } + + if (keep_alive) { + char peek_buf; +#if defined(_WIN32) || defined(_WIN64) + int peek_res = recv(client_sock, &peek_buf, 1, MSG_PEEK); +#else + ssize_t peek_res = recv(client_sock, &peek_buf, 1, MSG_PEEK); +#endif + + if (peek_res == 0 || peek_res == C_SOCKET_ERROR) { + keep_alive = C_FALSE; + } else if (server->fnOnRequest) { + keep_alive = server->fnOnRequest(server, client_sock, args); + } + } + + if (!keep_alive) { + Poll_Remove(self, client_sock); + + /* Fire the explicitly realigned c_TcpServer_OnDisconnect_t call hook */ + if (server->fnOnDisconnect) { + server->fnOnDisconnect(server, client_sock, args); + } + + c_Socket_Close(client_sock); + } + } + ret--; + } + + if (ret == 0) break; + } + return C_SUCCESS; +} + +/* (The rest of the driver functions stay identically compiled) */ +static void Poll_Destroy(c_TcpReactor_t* self) { + c_PollReactorImpl_t* impl = (c_PollReactorImpl_t*)self; + c_Allocator_t alloc = impl->allocator; + for (c_size_t i = 0; i < impl->count; ++i) { + c_socket_t sock = (c_socket_t)impl->fds[i].fd; + c_Socket_Close(sock); + } + if (impl->fds) c_Allocator_Free(&alloc, impl->fds); + c_Allocator_Free(&alloc, impl); +} + +static const c_TcpReactorVtbl_t g_PollReactorVtbl = { Poll_Add, Poll_Remove, Poll_Poll, Poll_Destroy }; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_TcpPollReactor_Create(c_TcpReactor_t** out_reactor, c_Allocator_t* allocator) { + if (!out_reactor) return C_ERR_PARAM; + c_Allocator_t alloc = allocator ? *allocator : c_DefaultAllocator; + c_PollReactorImpl_t* impl = (c_PollReactorImpl_t*)c_Allocator_Calloc(&alloc, 1, sizeof(c_PollReactorImpl_t)); + if (!impl) return C_ERR_NOMEM; + impl->base.vtbl = &g_PollReactorVtbl; + impl->allocator = alloc; + *out_reactor = &impl->base; + return C_SUCCESS; +} + + diff --git a/Foundation/c_TcpPollReactor.h b/Foundation/c_TcpPollReactor.h new file mode 100644 index 0000000..61e79e2 --- /dev/null +++ b/Foundation/c_TcpPollReactor.h @@ -0,0 +1,14 @@ +#ifndef INCLUDED_C_TCPPOLLREACTOR_H +#define INCLUDED_C_TCPPOLLREACTOR_H + +#ifndef INCLUDED_C_TCPSERVER_H +#include +#endif /*INCLUDED_C_TCPSERVER_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_TcpPollReactor_Create(c_TcpReactor_t** out_reactor, c_Allocator_t* allocator); + +#endif /*INCLUDED_C_TCPPOLLREACTOR_H*/ diff --git a/Foundation/c_TcpPollReactor.t.c b/Foundation/c_TcpPollReactor.t.c new file mode 100644 index 0000000..39504ac --- /dev/null +++ b/Foundation/c_TcpPollReactor.t.c @@ -0,0 +1,101 @@ +#include "c_TcpPollReactor.h" +#include "c_Test.h" +#include "c_TcpServer.h" + + +static int g_tracking_counter = 0; + +static bool OnConnectHandler(c_socket_t s, const c_SockAddr_t* addr, void* args) { + (void)addr; (void)args; + g_tracking_counter = 10; + + char ip_buffer[NI_MAXHOST]; + char port_buffer[NI_MAXSERV]; + + // 3. 将二进制地址转换为明文的 IP 和 端口字符串 + // NI_NUMERICHOST: 以数字形式返回 IP + // NI_NUMERICSERV: 以数字形式返回端口号(而不是服务名如 http/ftp) + c_err_t err = c_SocketUtil_GetNameInfoForSocket(s, ip_buffer, NI_MAXHOST, port_buffer, NI_MAXSERV); + + if (err == 0) { + printf("Connect from IP:PORT %s:%s\n", ip_buffer, port_buffer); + } + + return true; +} + +static bool OnDataHandler(c_socket_t s, void* args) { + (void)s; (void)args; + + char ip_buffer[NI_MAXHOST]; + char port_buffer[NI_MAXSERV]; + + // 3. 将二进制地址转换为明文的 IP 和 端口字符串 + // NI_NUMERICHOST: 以数字形式返回 IP + // NI_NUMERICSERV: 以数字形式返回端口号(而不是服务名如 http/ftp) + c_err_t err = c_SocketUtil_GetNameInfoForSocket(s, ip_buffer, NI_MAXHOST, port_buffer, NI_MAXSERV); + + if (err == 0) { + printf("Receive data from IP:PORT %s:%s\n", ip_buffer, port_buffer); + } + + return false; /* Instantly signal termination to verify disconnect loops */ +} + +/* Disconnect handler callback matching your exact specification naming format */ +static void OnDisconnectHandler(c_socket_t s, void* args) { + (void)s; (void)args; + g_tracking_counter = 0; /* Clear metrics register */ + + char ip_buffer[NI_MAXHOST]; + char port_buffer[NI_MAXSERV]; + + // 3. 将二进制地址转换为明文的 IP 和 端口字符串 + // NI_NUMERICHOST: 以数字形式返回 IP + // NI_NUMERICSERV: 以数字形式返回端口号(而不是服务名如 http/ftp) + c_err_t err = c_SocketUtil_GetNameInfoForSocket(s, ip_buffer, NI_MAXHOST, port_buffer, NI_MAXSERV); + + if (err == 0) { + printf("Client Closed IP:PORT %s:%s\n", ip_buffer, port_buffer); + } else { + fprintf(stderr, "getnameinfo 失败: %s\n", gai_strerror(c_Socket_GetErrno())); + } +} + +TEST_CASE(test_multiplex_reactor_server_polling) { + c_Socket_Init(); + + c_TcpServer_t server; + c_err_t err = c_TcpServer_Init(&server, AF_INET, 8888, 512, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* Hook callbacks using the realigned layout names */ + c_TcpServer_SetCallbacks(&server, OnConnectHandler, OnDataHandler, OnDisconnectHandler); + + + /* 1. Instantiate the poll driver subclass instance */ + c_TcpReactor_t* poll_backend = NULL; + err = c_TcpPollReactor_Create(&poll_backend, &server.allocator); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 2. Bind the engine onto the polymorphic execution loop layer */ + err = c_TcpServer_SetReactor(&server, poll_backend); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 3. Execute a non-blocking test loop pass with a quick 50ms timeout threshold */ + while (server.is_running) { + err = c_TcpServer_Run(&server, 60000, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + } + + c_TcpServer_Destroy(&server); + c_Socket_Destroy(); +} + +int main(void) { + TEST_START(TcpServer_MultiplexReactor_Suite); + RUN_TEST(test_multiplex_reactor_server_polling); + TEST_REPORT(); + RETURN_TEST_STATUS; +} + diff --git a/Foundation/c_TcpPollReactor_TimeServer.t.c b/Foundation/c_TcpPollReactor_TimeServer.t.c new file mode 100644 index 0000000..90571d7 --- /dev/null +++ b/Foundation/c_TcpPollReactor_TimeServer.t.c @@ -0,0 +1,127 @@ +#include +#include +#include +#include "c_TcpServer.h" +#include "c_SocketStream.h" +#include "c_SockAddr.h" +#include "c_TcpPollReactor.h" + +#if defined(_WIN32) || defined(_WIN64) + #pragma comment(lib, "ws2_32.lib") +#endif + +/** + * @brief Reactor connection event callback. + * Fired automatically by c_TcpPollReactor whenever an inbound client connects. + */ +static bool OnPollTimeServerConnect(c_TcpServer_t* server, c_socket_t client_sock, const c_SockAddr_t* peer_addr, void* args) { + (void)args; + char ip_str[INET6_ADDRSTRLEN] = {0}; + uint16_t port = c_SockAddr_GetPort(peer_addr); + + if (c_SockAddr_ToString(peer_addr, ip_str, sizeof(ip_str)) == C_ERR_OK) { + printf("[POLL INFO] Accepted client connection from: %s:%d\n", ip_str, (int)port); + } + + + + return true; +} + +static bool OnDataRequest(c_TcpServer_t* server, c_socket_t s , void* args) { + (void)args; + char host[32]={0}; + char port[9]={0}; + + c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port)); + printf("Request from %s:%s\n", host, port); + + char buffer[1024] = {0}; + int err = recv(s, buffer, sizeof(buffer), 0); + + printf("body: %.*s\n", err, buffer); + /* 1. Wrap raw client socket inside polymorphic Output Stream pipeline */ + time_t raw_time = time(NULL); + struct tm* time_info = localtime(&raw_time); + + char time_buffer[64] = {0}; + strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S\n", time_info); + + /* 3. Flush plaintext string down into the polymorphic socket driver channel */ + c_size_t bytes_written = 0; + c_SocketUtil_Send(s, time_buffer, strlen(time_buffer), &bytes_written); + + if (strncmp(buffer, "STOP", 4)==0) { + server->is_running = false; + return false; + } + + return true; + +} + +static void OnClose(c_TcpServer_t* server, c_socket_t s, void* args) { + (void)args; + char host[32]={0}; + char port[9]={0}; + + c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port)); + printf("Client Closed from %s:%s\n", host, port); +} + +static void OnError(c_TcpServer_t* server, c_socket_t s, int err, void* args) { + (void)args; + char host[32]={0}; + char port[9]={0}; + + c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port)); + printf("[ERROR] Server %s:%s ON %s\n", host, port, c_TcpServer_GetErrorString(err)); +} + +int main(void) { + c_Socket_Init(); + uint16_t server_port = 1313; + c_TcpServer_t server; + + printf("[SERVER] Initializing Poll-driven Time Server on port %d...\n", server_port); + + /* 1. Spin up master server listener instance with your explicit flags signature layout */ + c_err_t err = c_TcpServer_Init(&server, AF_INET, server_port, 512, NULL); + if (err != C_SUCCESS) { + fprintf(stderr, "[ERROR] Failed to open master server listening socket\n"); + return -1; + } + + c_TcpServer_SetCallbacks(&server, OnPollTimeServerConnect, OnDataRequest, OnClose, OnError); + + /* 2. Instantiate the Polymorphic POSIX poll Reactor multiplexer backend */ + c_TcpReactor_t* poll_reactor = NULL; + err = c_TcpPollReactor_Create(&poll_reactor, &server.allocator); + if (err != C_SUCCESS) { + fprintf(stderr, "[ERROR] Failed to instantiate c_TcpPollReactor driver context\n"); + c_TcpServer_Destroy(&server); + return -1; + } + + /* 3. Bind poll multiplexer backend to server execution dispatcher context */ + err = c_TcpServer_SetReactor(&server, poll_reactor); + if (err != C_SUCCESS) { + fprintf(stderr, "[ERROR] Failed to wire reactor driver onto dispatcher context\n"); + c_TcpServer_Destroy(&server); + return -1; + } + + printf("[SERVER] Multiplexed Time Server is fully running via c_TcpPollReactor...\n"); + + /* 4. Non-recursive event loop dispatcher loop (Polled with 1000ms heartbeat intervals) */ + while (server.is_running) { + c_TcpServer_Run(&server, 1000, NULL); + } + + /* 5. Complete cleanup resource teardowns */ + c_TcpServer_Destroy(&server); + printf("[SERVER] Time Server cleanly terminated.\n"); + + c_Socket_Destroy(); + return 0; +} diff --git a/Foundation/c_TcpSelectReactor.c b/Foundation/c_TcpSelectReactor.c new file mode 100644 index 0000000..2f9cbc4 --- /dev/null +++ b/Foundation/c_TcpSelectReactor.c @@ -0,0 +1,225 @@ +#include +#include + +/* ================================================================================================================== */ +/* Concrete Implementation: Refactored Cross-Platform select() Driver Subclass Context */ + +typedef struct { + c_TcpReactor_t base; + c_socket_t* sockets; /* Array tracking all registered socket handles (Listener + Clients) */ + c_size_t count; /* Current count of monitored sockets */ + c_size_t capacity; /* Upper bounds allocation capacity limit */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +} c_SelectReactorImpl_t; + +static c_err_t Select_Add(c_TcpReactor_t* self, c_socket_t sock, uint32_t events) { + c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self; + (void)events; + + /* Enforce hard ceiling compliance checks against maximum threshold allowances for select */ + if (impl->count >= FD_SETSIZE) { + return C_ERR_OUTOFBOUND; + } + + if (impl->count >= impl->capacity) { + c_size_t old_cap = impl->capacity; + c_size_t new_cap = old_cap == 0 ? 16 : C_MIN(old_cap * 2, FD_SETSIZE); + + c_socket_t* new_sockets = (c_socket_t*)c_Allocator_Realloc( + &impl->allocator, impl->sockets, old_cap * sizeof(c_socket_t), new_cap * sizeof(c_socket_t) + ); + if (!new_sockets) return C_ERR_NOMEM; + + impl->sockets = new_sockets; + impl->capacity = new_cap; + } + + /* Prevent duplicate entries inside the tracking index */ + for (c_size_t i = 0; i < impl->count; ++i) { + if (impl->sockets[i] == sock) return C_SUCCESS; + } + + impl->sockets[impl->count++] = sock; + return C_SUCCESS; +} + +static c_err_t Select_Remove(c_TcpReactor_t* self, c_socket_t sock) { + c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self; + for (c_size_t i = 0; i < impl->count; ++i) { + if (impl->sockets[i] == sock) { + impl->sockets[i] = impl->sockets[impl->count - 1]; /* O(1) tail-swap optimization */ + impl->count--; + return C_SUCCESS; + } + } + return C_ERR_NOTFOUND; +} + +static c_err_t Select_Poll(c_TcpReactor_t* self, long timeout_ms, c_TcpServer_t* server, void* args) { + c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self; + if (impl->count == 0 || !server->is_running) return C_SUCCESS; + + fd_set read_set; + fd_set err_set; + FD_ZERO(&read_set); + FD_ZERO(&err_set); + + c_socket_t max_fd = 0; + for (c_size_t i = 0; i < impl->count; ++i) { + FD_SET(impl->sockets[i], &read_set); + FD_SET(impl->sockets[i], &err_set); +#if !defined(_WIN32) && !defined(_WIN64) + if (impl->sockets[i] > max_fd) { + max_fd = impl->sockets[i]; + } +#endif + } + + struct timeval tv; + struct timeval* tv_ptr = NULL; + if (timeout_ms >= 0) { + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + tv_ptr = &tv; + } + +#if defined(_WIN32) || defined(_WIN64) + int ret = select(0, &read_set, NULL, &err_set, tv_ptr); +#else + int ret = select((int)(max_fd + 1), &read_set, NULL, &err_set, tv_ptr); +#endif + + if (ret < 0) { + if (server->fnOnError) { + server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_POLL, args); + } + return C_SUCCESS; + } + if (ret == 0) return C_SUCCESS; /* Timeout pass, exit early */ + + /* + * Safe Backward Traversal Pass: + * Scanning the tracked sockets pool backward ensures that deleting an element via + * tail-swapping doesn't corrupt unvisited index boundaries during the current pass. + */ + for (c_size_t i = impl->count; i > 0; --i) { + c_size_t idx = i - 1; + c_socket_t active_sock = impl->sockets[idx]; + + c_bool_t has_read = FD_ISSET(active_sock, &read_set); + c_bool_t has_error = FD_ISSET(active_sock, &err_set); + + if (!has_read && !has_error) continue; + + /* Channel A: Master server listener handles events */ + if (active_sock == server->listen_sock) { + if (has_read) { + c_SockAddr_t peer_addr; + c_socket_t client = c_SocketUtil_Accept(server->listen_sock, &peer_addr); + + if (c_Socket_IsValid(client)) { + bool keep = true; + if (server->fnOnConnect) { + keep = server->fnOnConnect(server, client, &peer_addr, args); + } + + if (keep) { + Select_Add(self, client, 1); /* Add client onto select watchlist */ + } else { + c_Socket_Close(client); + } + } else if (server->fnOnError) { + server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_ACCEPT, args); + } + } + if (has_error) { + if (server->fnOnError) { + server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_POLLERR, args); + } + } + ret--; + } + /* Channel B: Connected active client descriptor processing */ + else { + c_bool_t keep_alive = C_TRUE; + + /* Intercept operational error signals first */ + if (has_error) { + if (server->fnOnError) { + server->fnOnError(server, active_sock, C_TCPSERVER_ERR_ON_POLLERR, args); + } + keep_alive = C_FALSE; + } + + if (keep_alive && has_read) { + /* Intercept silent socket termination conditions using zero-byte peeks */ + char peek_buf; +#if defined(_WIN32) || defined(_WIN64) + int peek_res = recv(active_sock, &peek_buf, 1, MSG_PEEK); +#else + ssize_t peek_res = recv(active_sock, &peek_buf, 1, MSG_PEEK); +#endif + + if (peek_res == 0 || peek_res == C_SOCKET_ERROR) { + keep_alive = C_FALSE; /* Connection broken or closed by remote peer node */ + } else if (server->fnOnRequest) { + /* Fire your exact matching signature callback layout */ + keep_alive = server->fnOnRequest(server, active_sock, args); + } + } + + /* Clean up active client descriptor arrays if data pipeline drops or errors out */ + if (!keep_alive) { + Select_Remove(self, active_sock); + + /* Fire your formal on_disconnect registration interface hook */ + if (server->fnOnDisconnect) { + server->fnOnDisconnect(server, active_sock, args); + } + c_Socket_Close(active_sock); + } + ret--; + } + + if (ret == 0) break; /* Event counters satisfied, return early */ + } + + return C_SUCCESS; +} + +static void Select_Destroy(c_TcpReactor_t* self) { + c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self; + c_Allocator_t alloc = impl->allocator; + + /* Safely dismantle any hanging active client sockets remaining in the array pool */ + for (c_size_t i = 0; i < impl->count; ++i) { + c_socket_t sock = impl->sockets[i]; + c_Socket_Close(sock); + } + + if (impl->sockets) c_Allocator_Free(&alloc, impl->sockets); + c_Allocator_Free(&alloc, impl); +} + +static const c_TcpReactorVtbl_t g_SelectReactorVtbl = { Select_Add, Select_Remove, Select_Poll, Select_Destroy }; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_TcpSelectReactor_Create(c_TcpReactor_t** out_reactor, c_Allocator_t* allocator) { + if (!out_reactor) return C_ERR_PARAM; + c_Allocator_t alloc = allocator ? *allocator : c_DefaultAllocator; + + c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)c_Allocator_Calloc(&alloc, 1, sizeof(c_SelectReactorImpl_t)); + if (!impl) return C_ERR_NOMEM; + + impl->base.vtbl = &g_SelectReactorVtbl; + impl->sockets = NULL; + impl->count = 0; + impl->capacity = 0; + impl->allocator = alloc; + + *out_reactor = &impl->base; + return C_SUCCESS; +} + diff --git a/Foundation/c_TcpSelectReactor.h b/Foundation/c_TcpSelectReactor.h new file mode 100644 index 0000000..3e5b81f --- /dev/null +++ b/Foundation/c_TcpSelectReactor.h @@ -0,0 +1,16 @@ +#ifndef INCLUDED_C_TCPSELECTREACTOR_H +#define INCLUDED_C_TCPSELECTREACTOR_H + + +#ifndef INCLUDED_C_TCPSERVER_H +#include +#endif /*INCLUDED_C_TCPSERVER_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_TcpSelectReactor_Create(c_TcpReactor_t** out_reactor, c_Allocator_t* allocator); + + +#endif /*INCLUDED_C_TCPSELECTREACTOR_H*/ diff --git a/Foundation/c_TcpSelectReactor.t.c b/Foundation/c_TcpSelectReactor.t.c new file mode 100644 index 0000000..13a8416 --- /dev/null +++ b/Foundation/c_TcpSelectReactor.t.c @@ -0,0 +1,47 @@ +#include "c_TcpSelectReactor.h" +#include "c_Test.h" +#include "c_TcpServer.h" + +static void OnSelectClientConnected(c_socket_t client_sock, const c_SockAddr_t* peer_addr, void* args) { + (void)args; + char ip_str[64]; + if (c_SockAddr_ToString(peer_addr, ip_str, sizeof(ip_str)) == C_SUCCESS) { + printf(" " COLOR_GREEN "[SELECT EVENT] Connection Intercepted: %s:%d" COLOR_RESET "\n", + ip_str, (int)c_SockAddr_GetPort(peer_addr)); + } + c_Socket_Close(client_sock); +} + +TEST_CASE(test_select_reactor_server_lifecycle) { + c_Socket_Init(); + + c_TcpServer_t server; + /* Initialize an unweighted IPv4 TCP Stream Server on Port 8585 */ + c_err_t err = c_TcpServer_Init(&server, AF_INET, 8585, 512, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 1. Instantiate the select driver backend */ + c_TcpReactor_t* select_backend = NULL; + err = c_TcpSelectReactor_Create(&select_backend, 0); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 2. Bind the driver onto the polymorphic loop interface */ + err = c_TcpServer_SetReactor(&server, select_backend); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 3. Execute a fast non-blocking single-pass polling check (10ms timeout) */ + err = c_TcpServer_Run(&server, 60000, OnSelectClientConnected, NULL); + ASSERT_INT_EQ(C_SUCCESS, err); + + /* 4. Complete teardown */ + c_TcpServer_Destroy(&server); + + c_Socket_Destroy(); +} + +int main(void) { + TEST_START(TcpServer_SelectReactor_Suite); + RUN_TEST(test_select_reactor_server_lifecycle); + TEST_REPORT(); + RETURN_TEST_STATUS; +} diff --git a/Foundation/c_TcpSelectReactor_TimeServer.t.c b/Foundation/c_TcpSelectReactor_TimeServer.t.c new file mode 100644 index 0000000..8132e30 --- /dev/null +++ b/Foundation/c_TcpSelectReactor_TimeServer.t.c @@ -0,0 +1,127 @@ +#include +#include +#include +#include "c_TcpServer.h" +#include "c_SocketStream.h" +#include "c_SockAddr.h" +#include "c_TcpSelectReactor.h" + +#if defined(_WIN32) || defined(_WIN64) + #pragma comment(lib, "ws2_32.lib") +#endif + +/** + * @brief Reactor connection event callback. + * Fired automatically by c_TcpPollReactor whenever an inbound client connects. + */ +static bool OnConnect(c_TcpServer_t* server, c_socket_t client_sock, const c_SockAddr_t* peer_addr, void* args) { + (void)args; + char ip_str[INET6_ADDRSTRLEN] = {0}; + uint16_t port = c_SockAddr_GetPort(peer_addr); + + if (c_SockAddr_ToString(peer_addr, ip_str, sizeof(ip_str)) == C_ERR_OK) { + printf("[POLL INFO] Accepted client connection from: %s:%d\n", ip_str, (int)port); + } + + + + return true; +} + +static bool OnDataRequest(c_TcpServer_t* server, c_socket_t s , void* args) { + (void)args; + char host[32]={0}; + char port[9]={0}; + + c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port)); + printf("Request from %s:%s\n", host, port); + + char buffer[1024] = {0}; + int err = recv(s, buffer, sizeof(buffer), 0); + + printf("body: %.*s\n", err, buffer); + /* 1. Wrap raw client socket inside polymorphic Output Stream pipeline */ + time_t raw_time = time(NULL); + struct tm* time_info = localtime(&raw_time); + + char time_buffer[64] = {0}; + strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S\n", time_info); + + /* 3. Flush plaintext string down into the polymorphic socket driver channel */ + c_size_t bytes_written = 0; + c_SocketUtil_Send(s, time_buffer, strlen(time_buffer), &bytes_written); + + if (strncmp(buffer, "STOP", 4)==0) { + server->is_running = false; + return false; + } + + return true; + +} + +static void OnClose(c_TcpServer_t* server, c_socket_t s, void* args) { + (void)args; + char host[32]={0}; + char port[9]={0}; + + c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port)); + printf("Client Closed from %s:%s\n", host, port); +} + +static void OnError(c_TcpServer_t* server, c_socket_t s, int err, void* args) { + (void)args; + char host[32]={0}; + char port[9]={0}; + + c_SocketUtil_GetNameInfoForSocket(s, host, sizeof(host), port, sizeof(port)); + printf("[ERROR] Server %s:%s ON %s\n", host, port, c_TcpServer_GetErrorString(err)); +} + +int main(void) { + c_Socket_Init(); + uint16_t server_port = 1313; + c_TcpServer_t server; + + printf("[SERVER] Initializing Poll-driven Time Server on port %d...\n", server_port); + + /* 1. Spin up master server listener instance with your explicit flags signature layout */ + c_err_t err = c_TcpServer_Init(&server, AF_INET, server_port, 512, NULL); + if (err != C_SUCCESS) { + fprintf(stderr, "[ERROR] Failed to open master server listening socket\n"); + return -1; + } + + c_TcpServer_SetCallbacks(&server, OnConnect, OnDataRequest, OnClose, OnError); + + /* 2. Instantiate the Polymorphic POSIX poll Reactor multiplexer backend */ + c_TcpReactor_t* reactor = NULL; + err = c_TcpSelectReactor_Create(&reactor, &server.allocator); + if (err != C_SUCCESS) { + fprintf(stderr, "[ERROR] Failed to instantiate c_TcpSelectReactor driver context\n"); + c_TcpServer_Destroy(&server); + return -1; + } + + /* 3. Bind poll multiplexer backend to server execution dispatcher context */ + err = c_TcpServer_SetReactor(&server, reactor); + if (err != C_SUCCESS) { + fprintf(stderr, "[ERROR] Failed to wire reactor driver onto dispatcher context\n"); + c_TcpServer_Destroy(&server); + return -1; + } + + printf("[SERVER] Multiplexed Time Server is fully running via c_TcpPollReactor...\n"); + + /* 4. Non-recursive event loop dispatcher loop (Polled with 1000ms heartbeat intervals) */ + while (server.is_running) { + c_TcpServer_Run(&server, 1000, NULL); + } + + /* 5. Complete cleanup resource teardowns */ + c_TcpServer_Destroy(&server); + printf("[SERVER] Time Server cleanly terminated.\n"); + + c_Socket_Destroy(); + return 0; +} diff --git a/Foundation/c_TcpServer.c b/Foundation/c_TcpServer.c new file mode 100644 index 0000000..0cc825b --- /dev/null +++ b/Foundation/c_TcpServer.c @@ -0,0 +1,93 @@ +#include + +#include "c_SocketStream.h" + +c_err_t c_TcpServer_Init(c_TcpServer_t* self, int family, uint16_t port, int backlog, c_Allocator_t* allocator) { + if (!self) return C_ERR_PARAM; + + self->allocator = allocator ? *allocator : c_DefaultAllocator; + self->listen_sock = C_INVALID_SOCKET; + self->reactor = NULL; + self->is_running = C_FALSE; + + /* 2. Instantiate the listening socket descriptor using the explicit parameter map */ + self->listen_sock = socket(family, SOCK_STREAM, IPPROTO_IP); + if (self->listen_sock == C_INVALID_SOCKET) return C_ERR_FAIL; + + /* 3. Enable SO_REUSEADDR to avoid socket bind crashes on rapid restarts */ + c_Socket_SetReuseAddr(self->listen_sock, true); + + /* 4. Configure dual-stack sockaddr union variants based on the address family */ + c_SockAddr_t bind_addr; + if (c_SockAddr_Init(&bind_addr, family, 0, port)!=C_ERR_OK) { + c_TcpServer_Destroy(self); + return C_ERR_FAIL; + } + + if (family == AF_INET6) { + /* Optional optimization: allow dual-stack IPv4 mapping on the IPv6 socket if supported */ + c_Socket_SetIPV6Only(self->listen_sock, false); + } + + /* 6. Bind the structural endpoint socket down to the network stack */ + if (c_SocketUtil_BindWithSockAddr(self->listen_sock, &bind_addr) != C_ERR_OK) { + c_TcpServer_Destroy(self); + return C_ERR_FAIL; + } + + /* 7. Switch the socket handle to listener mode with a generous backlog size */ + if (listen(self->listen_sock, backlog) == C_SOCKET_ERROR) { + c_TcpServer_Destroy(self); + return C_ERR_FAIL; + } + + self->is_running = C_TRUE; + return C_SUCCESS; +} + +c_err_t c_TcpServer_SetReactor(c_TcpServer_t* self, c_TcpReactor_t* reactor) { + if (!self || !reactor) return C_ERR_PARAM; + self->reactor = reactor; + /* Automatically add our master listener socket to watch for read/inbound events */ + return c_TcpReactor_Add(self->reactor, self->listen_sock, 1); +} + +c_err_t c_TcpServer_Run(c_TcpServer_t* self, long timeout_ms, void* args) { + if (!self || !self->reactor || !self->is_running) return C_ERR_PARAM; + return c_TcpReactor_Poll(self->reactor, timeout_ms, self, args); +} + +void c_TcpServer_Destroy(c_TcpServer_t* self) { + if (!self) return; + self->is_running = C_FALSE; + if (self->reactor) { + c_TcpReactor_Destroy(self->reactor); + self->reactor = NULL; + } + if (self->listen_sock != C_INVALID_SOCKET) { + c_Socket_Close(self->listen_sock); + } +} + +const char* c_TcpServer_GetErrorString(int code) { + switch (code) { + case C_ERR_OK: { + return "OK"; + } + case C_TCPSERVER_ERR_ON_ACCEPT: { + return "ACCEPT"; + } + case C_TCPSERVER_ERR_ON_POLL: { + return "POLL"; + } + case C_TCPSERVER_ERR_ON_POLLERR: { + return "POLLERR"; + } + case C_TCPSERVER_ERR_ON_RECV: { + return "RECV"; + } + default: { + return "UNKNOWN"; + } + } +} \ No newline at end of file diff --git a/Foundation/c_TcpServer.h b/Foundation/c_TcpServer.h new file mode 100644 index 0000000..7691835 --- /dev/null +++ b/Foundation/c_TcpServer.h @@ -0,0 +1,184 @@ +#ifndef INCLUDED_C_TCPSERVER_H +#define INCLUDED_C_TCPSERVER_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + +#ifndef INCLUDED_C_INSTREAM_H +#include +#endif /*INCLUDED_C_INSTREAM_H*/ + +#ifndef INCLUDED_C_OUTSTREAM_H +#include +#endif /*INCLUDED_C_OUTSTREAM_H*/ + +#ifndef INCLUDED_C_SOCKET_H +#include +#endif /*INCLUDED_C_SOCKET_H*/ + +#ifndef INCLUDED_C_SOCKETUTIL_H +#include +#endif /*INCLUDED_C_SOCKETUTIL_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#define C_TCPSERVER_ERR_ON_POLL 1001 +#define C_TCPSERVER_ERR_ON_ACCEPT 1002 +#define C_TCPSERVER_ERR_ON_POLLERR 1003 +#define C_TCPSERVER_ERR_ON_RECV 1004 + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct c_TcpServer_t c_TcpServer_t; +typedef struct c_TcpReactor_t c_TcpReactor_t; + +/** + * @brief Unified event-driven user callback signature. + * @param client_sock The accepted socket descriptor for the client. + * @param peer_addr Unified dual-stack address structure containing client peer details. + */ +typedef bool (*c_TcpServer_OnConnect_t)(c_TcpServer_t* server, c_socket_t client_sock, const c_SockAddr_t* peer_addr, void* args); + +/** + * @brief Event-driven user data processing callback matched exactly to your layout signature. + * @param s The raw socket descriptor exhibiting unread data availability. + * @return C_TRUE to keep monitoring the client; C_FALSE to break and close the connection. + */ +typedef bool (*c_TcpServer_OnRequest_t)(c_TcpServer_t* server, c_socket_t s, void* args); + +/** + * @brief Event-driven user callback fired whenever an active client disconnects or encounters an error. + * @param s The raw socket descriptor of the client being terminated. + * @param args Custom user context parameters pointer. + */ +typedef void (*c_TcpServer_OnDisconnect_t)(c_TcpServer_t* server, c_socket_t s, void* args); + +/** + * @brief Event-driven user callback fired whenever an operational network error is caught. + * @param s The socket descriptor associated with the failure (Can be server->listen_sock or a client handle). + * @param error_code Internal unified framework error mapping code. + * @param args Custom user context parameters pointer. + */ +typedef void (*c_TcpServer_OnError_t)(c_TcpServer_t* server, c_socket_t s, c_err_t error_code, void* args); + + + +/* Virtual Interface Table for I/O Multiplexing Engines */ +typedef struct { + c_err_t (*add)(c_TcpReactor_t* self, c_socket_t sock, uint32_t events); + c_err_t (*remove)(c_TcpReactor_t* self, c_socket_t sock); + c_err_t (*poll)(c_TcpReactor_t* self, long timeout_ms, c_TcpServer_t* server, void* args); + void (*destroy)(c_TcpReactor_t* self); +} c_TcpReactorVtbl_t; + +struct c_TcpReactor_t { + const c_TcpReactorVtbl_t* vtbl; +}; + +struct c_TcpServer_t { + c_socket_t listen_sock; /* Master server listening socket descriptor */ + c_TcpReactor_t* reactor; /* Polymorphic multiplexing backend driver link */ + c_TcpServer_OnConnect_t fnOnConnect; /* Connection accept callback hook */ + c_TcpServer_OnRequest_t fnOnRequest; /* Data incoming available callback hook */ + c_TcpServer_OnDisconnect_t fnOnDisconnect; /* Disconnect termination event hook */ + c_TcpServer_OnError_t fnOnError; /* Network operational error event hook */ + c_bool_t is_running; /* Server master thread loop state flag */ + c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ +}; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +c_err_t c_TcpReactor_Add(c_TcpReactor_t* self, c_socket_t sock, uint32_t events) { + if (!self || !self->vtbl || !self->vtbl->add || !c_Socket_IsValid(sock)) return C_ERR_PARAM; + return self->vtbl->add(self, sock, events); +} + +C_STATIC_FORCE_INLINE +c_err_t c_TcpReactor_Remove(c_TcpReactor_t* self, c_socket_t sock) { + if (!self || !self->vtbl || !self->vtbl->remove || !c_Socket_IsValid(sock)) return C_ERR_PARAM; + return self->vtbl->remove(self, sock); +} + +C_STATIC_FORCE_INLINE +c_err_t c_TcpReactor_Poll(c_TcpReactor_t* self, long timeout_ms, c_TcpServer_t* server, void* args) { + if (!self || !self->vtbl || !self->vtbl->poll || !server) return C_ERR_PARAM; + return self->vtbl->poll(self, timeout_ms, server, args); +} + +C_STATIC_FORCE_INLINE +void c_TcpReactor_Destroy(c_TcpReactor_t* self) { + if (!self || !self->vtbl || !self->vtbl->destroy) return; + self->vtbl->destroy(self); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/* Core Lifecycle Primitives */ +c_err_t c_TcpServer_Init(c_TcpServer_t* self, int family, uint16_t port, int backlog, c_Allocator_t* allocator) ; +void c_TcpServer_Destroy(c_TcpServer_t* self); + +/** + * @brief Explicitly binds a specific I/O multiplexing reactor back-end driver. + */ +c_err_t c_TcpServer_SetReactor(c_TcpServer_t* self, c_TcpReactor_t* reactor); + + +C_STATIC_FORCE_INLINE +c_err_t c_TcpServer_SetCallbacks(c_TcpServer_t* self + , c_TcpServer_OnConnect_t on_connect + , c_TcpServer_OnRequest_t on_data + , c_TcpServer_OnDisconnect_t on_disconnect + , c_TcpServer_OnError_t on_error) +{ + if (!self) return C_ERR_PARAM; + self->fnOnConnect = on_connect; + self->fnOnRequest = on_data; + self->fnOnDisconnect = on_disconnect; + self->fnOnError = on_error; + return C_SUCCESS; +} + +C_STATIC_FORCE_INLINE +void c_TcpServer_SetOnConnect(c_TcpServer_t* self, c_TcpServer_OnConnect_t on_connect) { + if (!self) return; + self->fnOnConnect = on_connect; +} + +C_STATIC_FORCE_INLINE +void c_TcpServer_SetOnData(c_TcpServer_t* self, c_TcpServer_OnRequest_t on_data) { + if (!self) return; + self->fnOnRequest = on_data; +} + +C_STATIC_FORCE_INLINE +void c_TcpServer_SetOnDisconnect(c_TcpServer_t* self, c_TcpServer_OnDisconnect_t on_disconnect) { + if (!self) return; + self->fnOnDisconnect = on_disconnect; +} + +C_STATIC_FORCE_INLINE +void c_TcpServer_SetOnError(c_TcpServer_t* self, c_TcpServer_OnError_t on_error) { + if (!self) return; + self->fnOnError = on_error; +} + +/** + * @brief Non-recursively runs the multiplexed event polling dispatcher loop layer. + */ +c_err_t c_TcpServer_Run(c_TcpServer_t* self, long timeout_ms, void* args); + + +const char* c_TcpServer_GetErrorString(int code); + +#endif /*INCLUDED_C_TCPSERVER_H*/