/* Warning! This file is generated by the tinycsocket project. */ #ifndef TINYCSOCKET_HEADER_H_ #define TINYCSOCKET_HEADER_H_ /**********************************/ /********* tinycsocket.h **********/ /**********************************/ /* * Copyright 2018 Markus Lindelöw * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files(the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and / or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef TINYCSOCKET_INTERNAL_H_ #define TINYCSOCKET_INTERNAL_H_ static const char* const TCS_VERSION_TXT = "v0.3.71"; static const char* const TCS_LICENSE_TXT = "Copyright 2018 Markus Lindelöw\n" "\n" "Permission is hereby granted, free of charge, to any person obtaining a copy " "of this software and associated documentation files(the \"Software\"), to deal " "in the Software without restriction, including without limitation the rights " "to use, copy, modify, merge, publish, distribute, sublicense, and / or sell " "copies of the Software, and to permit persons to whom the Software is " "furnished to do so, subject to the following conditions:\n" "\n" "The above copyright notice and this permission notice shall be included in all " "copies or substantial portions of the Software.\n" "\n" "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR " "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, " "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE " "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER " "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, " "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE " "SOFTWARE."; /* * List of all functions in the library: * * The documentation for each function is located at the declaration in this document. * Note: Most IDE:s and editors can jump to decalarations directly from here (ctrl+click on symbol or right click -> "Go to Declaration") * * Library Management: * - TcsResult tcs_lib_init(void); * - TcsResult tcs_lib_free(void); * * Socket Creation: * - TcsResult tcs_socket(TcsSocket* socket_ctx, TcsAddressFamily family, int type, int protocol); * - TcsResult tcs_socket_tcp(TcsSocket* socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* remote_address, int timeout_ms); * - TcsResult tcs_socket_tcp_str(TcsSocket* socket_ctx, const char* local_address, const char* remote_address, int timeout_ms); * - TcsResult tcs_socket_udp(TcsSocket* socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* remote_address); * - TcsResult tcs_socket_udp_str(TcsSocket* socket_ctx, const char* local_address, const char* remote_address); * - TcsResult tcs_socket_packet(TcsSocket* socket_ctx, const struct TcsAddress* bind_address, int type); * - TcsResult tcs_socket_packet_str(TcsSocket* socket_ctx, const char* interface_name, uint16_t protocol, int type); * - TcsResult tcs_close(TcsSocket* socket_ctx); * * Socket Operations: * - TcsResult tcs_bind(TcsSocket socket_ctx, const struct TcsAddress* local_address); * - TcsResult tcs_connect(TcsSocket socket_ctx, const struct TcsAddress* address); * - TcsResult tcs_connect_str(TcsSocket socket_ctx, const char* remote_address, uint16_t port); * - TcsResult tcs_listen(TcsSocket socket_ctx, int backlog); * - TcsResult tcs_accept(TcsSocket socket_ctx, TcsSocket* child_socket_ctx, struct TcsAddress* address); * - TcsResult tcs_shutdown(TcsSocket socket_ctx, TcsSocketDirection direction); * * Data Transfer: * - TcsResult tcs_send(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size, uint32_t flags, size_t* bytes_sent); * - TcsResult tcs_send_to(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size, uint32_t flags, const struct TcsAddress* destination_address, size_t* bytes_sent); * - TcsResult tcs_sendv(TcsSocket socket_ctx, const struct TcsBuffer* buffers, size_t buffer_count, uint32_t flags, size_t* bytes_sent); * - TcsResult tcs_send_netstring(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size); * - TcsResult tcs_receive(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, uint32_t flags, size_t* bytes_received); * - TcsResult tcs_receive_from(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, uint32_t flags, struct TcsAddress* source_address, size_t* bytes_received); * - TcsResult tcs_receive_line(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, size_t* bytes_received, uint8_t delimiter); * - TcsResult tcs_receive_netstring(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, size_t* bytes_received); * * Socket Pooling: * - TcsResult tcs_pool_create(struct TcsPool** pool); * - TcsResult tcs_pool_destroy(struct TcsPool** pool); * - TcsResult tcs_pool_add(struct TcsPool* pool, TcsSocket socket_ctx, void* user_data, bool poll_can_read, bool poll_can_write, bool poll_error); * - TcsResult tcs_pool_remove(struct TcsPool* pool, TcsSocket socket_ctx); * - TcsResult tcs_pool_poll(struct TcsPool* pool, struct TcsPollEvent* events, size_t events_count, size_t* events_populated, int timeout_ms); * * Socket Options: * - TcsResult tcs_opt_set(TcsSocket socket_ctx, int32_t level, int32_t option_name, const void* option_value, size_t option_size); * - TcsResult tcs_opt_get(TcsSocket socket_ctx, int32_t level, int32_t option_name, void* option_value, size_t* option_size); * - TcsResult tcs_opt_type_get(TcsSocket socket_ctx, int* type); * - TcsResult tcs_opt_broadcast_set(TcsSocket socket_ctx, bool do_allow_broadcast); * - TcsResult tcs_opt_broadcast_get(TcsSocket socket_ctx, bool* is_broadcast_allowed); * - TcsResult tcs_opt_keep_alive_set(TcsSocket socket_ctx, bool do_keep_alive); * - TcsResult tcs_opt_keep_alive_get(TcsSocket socket_ctx, bool* is_keep_alive_enabled); * - TcsResult tcs_opt_reuse_address_set(TcsSocket socket_ctx, bool do_allow_reuse_address); * - TcsResult tcs_opt_reuse_address_get(TcsSocket socket_ctx, bool* is_reuse_address_allowed); * - TcsResult tcs_opt_reuse_port_set(TcsSocket socket_ctx, bool do_allow_reuse_port); * - TcsResult tcs_opt_reuse_port_get(TcsSocket socket_ctx, bool* is_reuse_port_allowed); * - TcsResult tcs_opt_send_buffer_size_set(TcsSocket socket_ctx, size_t send_buffer_size); * - TcsResult tcs_opt_send_buffer_size_get(TcsSocket socket_ctx, size_t* send_buffer_size); * - TcsResult tcs_opt_receive_buffer_size_set(TcsSocket socket_ctx, size_t receive_buffer_size); * - TcsResult tcs_opt_receive_buffer_size_get(TcsSocket socket_ctx, size_t* receive_buffer_size); * - TcsResult tcs_opt_receive_timeout_set(TcsSocket socket_ctx, int timeout_ms); * - TcsResult tcs_opt_receive_timeout_get(TcsSocket socket_ctx, int* timeout_ms); * - TcsResult tcs_opt_linger_set(TcsSocket socket_ctx, bool do_linger, int timeout_seconds); * - TcsResult tcs_opt_linger_get(TcsSocket socket_ctx, bool* do_linger, int* timeout_seconds); * - TcsResult tcs_opt_ip_no_delay_set(TcsSocket socket_ctx, bool use_no_delay); * - TcsResult tcs_opt_ip_no_delay_get(TcsSocket socket_ctx, bool* is_no_delay_used); * - TcsResult tcs_opt_out_of_band_inline_set(TcsSocket socket_ctx, bool enable_oob); * - TcsResult tcs_opt_out_of_band_inline_get(TcsSocket socket_ctx, bool* is_oob_enabled); * - TcsResult tcs_opt_priority_set(TcsSocket socket_ctx, int priority); * - TcsResult tcs_opt_priority_get(TcsSocket socket_ctx, int* priority); * - TcsResult tcs_opt_nonblocking_set(TcsSocket socket_ctx, bool do_nonblocking); * - TcsResult tcs_opt_nonblocking_get(TcsSocket socket_ctx, bool* is_nonblocking); * - TcsResult tcs_opt_membership_add(TcsSocket socket_ctx, const struct TcsAddress* multicast_address); * - TcsResult tcs_opt_membership_add_str(TcsSocket socket_ctx, const char* multicast_address); * - TcsResult tcs_opt_membership_add_to(TcsSocket socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* multicast_address); * - TcsResult tcs_opt_membership_drop(TcsSocket socket_ctx, const struct TcsAddress* multicast_address); * - TcsResult tcs_opt_membership_drop_str(TcsSocket socket_ctx, const char* multicast_address); * - TcsResult tcs_opt_membership_drop_from(TcsSocket socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* multicast_address); * - TcsResult tcs_opt_multicast_interface_set(TcsSocket socket_ctx, const struct TcsAddress* local_address); * - TcsResult tcs_opt_multicast_loop_set(TcsSocket socket_ctx, bool do_loopback); * - TcsResult tcs_opt_multicast_loop_get(TcsSocket socket_ctx, bool* is_loopback); * * Address and Interface Utilities: * - TcsResult tcs_interface_list(struct TcsInterface interfaces[], size_t capacity, size_t* out_count); * - TcsResult tcs_address_resolve(const char* hostname, TcsAddressFamily address_family, struct TcsAddress addresses[], size_t capacity, size_t* out_count); * - TcsResult tcs_address_list(unsigned int interface_id_filter, TcsAddressFamily address_family_filter, struct TcsInterfaceAddress interface_addresses[], size_t capacity, size_t* out_count); * - TcsResult tcs_address_socket_local(TcsSocket socket_ctx, struct TcsAddress* local_address); * - TcsResult tcs_address_socket_remote(TcsSocket socket_ctx, struct TcsAddress* remote_address); * - TcsResult tcs_address_socket_family(TcsSocket socket_ctx, TcsAddressFamily* out_family); * - TcsResult tcs_address_parse(const char str[], struct TcsAddress* out_address); * - TcsResult tcs_address_to_str(const struct TcsAddress* address, char out_str[70]); * - bool tcs_address_is_equal(const struct TcsAddress* l, const struct TcsAddress* r); * - bool tcs_address_is_any(const struct TcsAddress* addr); * - bool tcs_address_is_link_local(const struct TcsAddress* addr); * - bool tcs_address_is_loopback(const struct TcsAddress* addr); * - bool tcs_address_is_multicast(const struct TcsAddress* addr); * - bool tcs_address_is_broadcast(const struct TcsAddress* addr); */ // Recognize which system we are compiling against #if defined(WIN32) || defined(__MINGW32__) #define TINYCSOCKET_USE_WIN32_IMPL #elif defined(__linux__) || defined(__sun) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ (defined(__APPLE__) && defined(__MACH__)) || defined(__MSYS__) || defined(__unix__) #define TINYCSOCKET_USE_POSIX_IMPL #else #pragma message("Warning: Unknown OS, trying POSIX") #define TINYCSOCKET_USE_POSIX_IMPL #endif #ifdef __cplusplus extern "C" { #endif // Then we have some platforms specific definitions #if defined(TINYCSOCKET_USE_WIN32_IMPL) #ifdef _WINSOCKAPI_ #error winsock.h included instead of winsock2.h. Define "_WINSOCKAPI_" or include this header file before windows.h to fix the problem #endif #ifndef __MINGW32__ // MinGW will generate a warning by it self. #define _WINSOCKAPI_ // Prevent inclusion of winsock.h in windows.h, use winsock2.h #endif #include typedef UINT_PTR TcsSocket; typedef unsigned int TcsInterfaceId; // TODO: GUID is used for in vista at newer. Change this type. #elif defined(TINYCSOCKET_USE_POSIX_IMPL) #if defined(TINYCSOCKET_IMPLEMENTATION) #if (defined(__linux__) || defined(__CYGWIN__)) && defined(__STRICT_ANSI__) #pragma message( \ "tinycsocket: Strict ANSI C mode detected on glibc/Cygwin. " \ "POSIX symbols may be hidden. Use -std=gnu99 instead of -std=c99, " \ "or define _POSIX_C_SOURCE=200112L and _DEFAULT_SOURCE before including this header.") #endif #if defined(__sun) && !defined(__EXTENSIONS__) #pragma message( \ "tinycsocket: illumos/Solaris detected without __EXTENSIONS__. " \ "Define __EXTENSIONS__ and _XOPEN_SOURCE=500 before including this header.") #endif #endif typedef int TcsSocket; typedef unsigned int TcsInterfaceId; #endif #ifndef TCS_SENDV_STACK_MAX #define TCS_SENDV_STACK_MAX 112 #endif // Declarations #include #include #include /** @internal */ #define tcs_static_assert(name, expr) typedef char tcs_sa_##name[(expr) ? 1 : -1] /** * @brief Address Family */ typedef enum { TCS_AF_ANY, /**< Layer 4 agnostic */ TCS_AF_IP4, /**< INET IPv4 interface */ TCS_AF_IP6, /**< INET IPv6 interface */ TCS_AF_PACKET, /**< Layer 2 interface */ } TcsAddressFamily; /** * @brief IPv6 address (16 bytes), analogous to POSIX struct in6_addr. */ struct TcsIp6Address { uint8_t bytes[16]; }; /** * @brief Network Address * * Always host-byte-order. You will never need to use htons etc. */ struct TcsAddress { TcsAddressFamily family; union { unsigned char _storage[24]; /**< Ensures full zero-initialization when copied from TCS_ADDRESS_NONE */ struct { uint32_t address; /**< Same byte order as the host */ uint16_t port; /**< Same byte order as the host */ } ip4; struct { struct TcsIp6Address address; /**< Network byte order */ TcsInterfaceId scope_id; /**< Native type. Only valid for local link addresses. See ::tcs_interface_list(). */ uint16_t port; /**< Same byte order as the host */ } ip6; struct { TcsInterfaceId interface_id; /**< Local interface index, use tcs_interface_list() to find valid interfaces. Native type. */ uint16_t protocol; /**< Host byte order. E.i. TCS_ETH_P_ALL, 0 (block all until bind), ETH_P_TSN etc. */ uint8_t mac[6]; /**< Typical destination mac address or local mac address when joining groups */ } packet; } data; }; #ifndef TCS_INTERFACE_NAME_SIZE #define TCS_INTERFACE_NAME_SIZE 64 #endif /** * @brief Network Interface Information */ struct TcsInterface { TcsInterfaceId id; char name[TCS_INTERFACE_NAME_SIZE]; }; struct TcsInterfaceAddress { struct TcsInterface iface; struct TcsAddress address; }; tcs_static_assert(address_storage_size, sizeof(((struct TcsAddress*)0)->data) <= 24); // gcc may trigger bug #53119 #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-braces" #endif static const struct TcsAddress TCS_ADDRESS_NONE = {(TcsAddressFamily)0, {{0}}}; #ifdef __GNUC__ #pragma GCC diagnostic pop #endif extern const uint32_t TCS_ADDRESS_ANY_IP4; extern const uint32_t TCS_ADDRESS_LOOPBACK_IP4; extern const uint32_t TCS_ADDRESS_BROADCAST_IP4; extern const uint32_t TCS_ADDRESS_NONE_IP4; extern const struct TcsIp6Address TCS_ADDRESS_ANY_IP6; extern const struct TcsIp6Address TCS_ADDRESS_LOOPBACK_IP6; /** * @brief Used when sending/receiving an array of buffers. * * Useful if you want to send two or more data arrays at once, for example a header and a body. * Make an array of TcsBuffer and use tcs_sendv() to send them all at once. */ struct TcsBuffer { const uint8_t* data; size_t size; }; extern const TcsSocket TCS_SOCKET_INVALID; /**< Define new sockets to this value, always. */ static const uint32_t TCS_FLAG_NONE = 0; // Type extern const int TCS_SOCK_STREAM; /**< Use for streaming types like TCP */ extern const int TCS_SOCK_DGRAM; /**< Use for datagrams types like UDP */ extern const int TCS_SOCK_RAW; /**< Use for raw sockets, eg. layer 2 packet sockets */ // Protocol extern const uint16_t TCS_PROTOCOL_IP_TCP; /**< Use TCP protocol (use with TCS_SOCK_STREAM for normal cases) */ extern const uint16_t TCS_PROTOCOL_IP_UDP; /**< Use UDP protocol (use with TCS_SOCK_DGRAM for normal cases) */ // Ethernet protocols (host byte order) static const uint16_t TCS_ETH_P_ALL = 0x0003; /**< Receive all protocols. Use with TCS_AF_PACKET for capture. */ // Flags extern const uint32_t TCS_AI_PASSIVE; /**< Use this flag for pure listening sockets */ // Recv flags extern const uint32_t TCS_MSG_PEEK; extern const uint32_t TCS_MSG_OOB; extern const uint32_t TCS_MSG_WAITALL; // Send flags extern const uint32_t TCS_MSG_SENDALL; // Backlog extern const int TCS_BACKLOG_MAX; /**< Max number of queued sockets when listening */ // Socket Direction typedef enum { TCS_SD_RECEIVE, /**< To shutdown incoming packets for socket */ TCS_SD_SEND, /**< To shutdown outgoing packets for socket */ TCS_SD_BOTH, /**< To shutdown both incoming and outgoing packets for socket */ } TcsSocketDirection; // Option levels extern const int TCS_SOL_SOCKET; /**< Socket option level for socket options */ extern const int TCS_SOL_IP; /**< IP option level for socket options */ // Socket options extern const int TCS_SO_TYPE; extern const int TCS_SO_BROADCAST; extern const int TCS_SO_KEEPALIVE; extern const int TCS_SO_LINGER; extern const int TCS_SO_REUSEADDR; extern const int TCS_SO_REUSEPORT; extern const int TCS_SO_RCVBUF; /**< Byte size of receiving buffer */ extern const int TCS_SO_RCVTIMEO; extern const int TCS_SO_SNDBUF; /**< Byte size of receiving buffer */ extern const int TCS_SO_OOBINLINE; extern const int TCS_SO_PRIORITY; // IP options extern const int TCS_SO_IP_NODELAY; extern const int TCS_SO_IP_MEMBERSHIP_ADD; extern const int TCS_SO_IP_MEMBERSHIP_DROP; extern const int TCS_SO_IP_MULTICAST_LOOP; // Packet options extern const int TCS_SO_PACKET_MEMBERSHIP_ADD; extern const int TCS_SO_PACKET_MEMBERSHIP_DROP; // Use for timeout to wait until infinity happens extern const int TCS_WAIT_INF; // Return codes typedef enum { TCS_SUCCESS = 0, /* 1–15: Non-fatal return codes */ TCS_AGAIN = 1, TCS_IN_PROGRESS = 2, TCS_SHUTDOWN = 3, /* -1...-31: General errors */ TCS_ERROR_UNKNOWN = -1, TCS_ERROR_MEMORY = -2, TCS_ERROR_INVALID_ARGUMENT = -3, TCS_ERROR_SYSTEM = -4, /* OS error not mapped below */ TCS_ERROR_PERMISSION_DENIED = -5, TCS_ERROR_NOT_IMPLEMENTED = -6, TCS_ERROR_NOT_SUPPORTED = -7, /* OS does not support this functionality */ /* -32...-63: Network and socket errors */ TCS_ERROR_ADDRESS_LOOKUP_FAILED = -32, TCS_ERROR_CONNECTION_REFUSED = -33, TCS_ERROR_NOT_CONNECTED = -34, TCS_ERROR_SOCKET_CLOSED = -35, TCS_ERROR_WOULD_BLOCK = -36, TCS_ERROR_TIMED_OUT = -37, TCS_ERROR_TEMPORARY_FAILURE = -38, TCS_ERROR_NETWORK_UNREACHABLE = -39, TCS_ERROR_CONNECTION_RESET = -40, TCS_ERROR_ADDRESS_IN_USE = -41, /* -64...-95: Configuration errors */ TCS_ERROR_LIBRARY_NOT_INITIALIZED = -64, /* -96...-128: Protocol errors */ TCS_ERROR_ILL_FORMED_MESSAGE = -96, } TcsResult; struct TcsPool; struct TcsPollEvent { TcsSocket socket; void* user_data; bool can_read; bool can_write; TcsResult error; }; static const struct TcsPollEvent TCS_POOL_EVENT_EMPTY = {0, 0, false, false, TCS_SUCCESS}; // ######## Library Management ######## /** * @brief Initialize tinycsocket library. * * This function needs to be called before using any other function in the library. * * On Windows, it will initialize Winsock, otherwise it does nothing and will always return #TCS_SUCCESS. * * You can call this multiple times, it will keep a counter of how many times you have called it (RAII friendly). * You should call tcs_lib_free() after you are done with the library (before program exit), atleast the number of times you have called tcs_lib_init(). * * @code * #include "tinycsocket.h" * * int main() * { * TcsResult tcs_init_res = tcs_lib_init(); * if (tcs_init_res != TCS_SUCCESS) * return -1; // Failed to initialize tinycsocket * // Do stuff with the library here * tcs_lib_free(); * } * @endcode * * @see tcs_lib_free() * * @return #TCS_SUCCESS if successful, otherwise the error code. * * @retval #TCS_ERROR_SYSTEM if a system error occurred. Maybe the Windows version is not supported, or broken. * @retval #TCS_ERROR_MEMORY if the library failed to allocate memory. */ TcsResult tcs_lib_init(void); /** * @brief De-initialize tinycsocket library. * * You need to call this the same amount of times as you have called ::tcs_lib_init(). * * @return #TCS_SUCCESS if successful, otherwise the error code. * * @retval #TCS_ERROR_LIBRARY_NOT_INITIALIZED if you have not called tcs_lib_init() before calling this function. */ TcsResult tcs_lib_free(void); // ######## Socket Creation ######## /** * @brief Create a new socket (BSD-style) * * This is a thin wrapper around the native socket function. * You may want to use one of the helper functions to create and setup a socket directly: * - ::tcs_socket_tcp_str() * - ::tcs_socket_udp_str() * - ::tcs_socket_packet_str() * * Call ::tcs_close() to stop communication and free all resources for the socket. * * @code * #include "tinycsocket.h" * * int main() * { * TcsResult tcs_init_res = tcs_lib_init(); * if (tcs_init_res != TCS_SUCCESS) * return -1; // Failed to initialize tinycsocket * * TcsSocket my_socket = TCS_SOCKET_INVALID; // Always initialize TcsSocket to TCS_SOCKET_INVALID. * TcsResult tcs_socket_res = tcs_socket(&my_socket, TCS_AF_IP4, TCS_SOCK_STREAM, TCS_PROTOCOL_IP_TCP); * if (tcs_socket_res != TCS_SUCCESS) * { * tcs_lib_free(); * return -2; // Failed to create socket * } * * // Do stuff with my_socket here. See examples in the documentation. * * tcs_close(&my_socket); // Safe to call even if my_socket is TCS_SOCKET_INVALID * tcs_lib_free(); * } * @endcode * * @param[out] socket_ctx pointer to socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use. * @param[in] family See ::TcsAddressFamily enum for supported values. * @param[in] type specifies the type of the socket, supported values are: ::TCS_SOCK_STREAM, ::TCS_SOCK_DGRAM and ::TCS_SOCK_RAW. * @param[in] protocol specifies the protocol, for example #TCS_PROTOCOL_IP_TCP or #TCS_PROTOCOL_IP_UDP. * * @return #TCS_SUCCESS if successful, otherwise the error code. * * @retval #TCS_SUCCESS if successful. * @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument. Such as a socket that is not #TCS_SOCKET_INVALID. * @retval #TCS_ERROR_NOT_IMPLEMENTED if you have provided an address family that is not supported on this platform. * @retval #TCS_ERROR_PERMISSION_DENIED if you do not have permission to create the socket. E.g. raw sockets often require elevated permissions. * * @see tcs_socket_tcp_str() * @see tcs_socket_udp_str() * @see tcs_socket_packet_str() * @see tcs_close() * @see tcs_lib_init() * @see tcs_lib_free() */ TcsResult tcs_socket(TcsSocket* socket_ctx, TcsAddressFamily family, int type, int protocol); /** * @brief Create a TCP socket, optionally bind to a local address and/or connect to a remote address. * * Creates an IPv4 or IPv6 TCP socket based on the address family of the provided address(es). * If @p local_address is not NULL, SO_REUSEADDR is set and the socket is bound to it. * If @p remote_address is not NULL, the socket connects to it. * At least one of @p local_address or @p remote_address must be non-NULL. * If both are provided, they must have the same address family. * On failure, *socket_ctx is always set back to #TCS_SOCKET_INVALID. * * @code * #include "tinycsocket.h" * int main() * { * tcs_lib_init(); * * struct TcsAddress local = TCS_ADDRESS_NONE; * local.family = TCS_AF_IP4; * local.data.ip4.address = TCS_ADDRESS_ANY_IP4; * local.data.ip4.port = 8080; * * TcsSocket server = TCS_SOCKET_INVALID; * TcsResult res = tcs_socket_tcp(&server, &local, NULL, 0); * if (res != TCS_SUCCESS) * { * tcs_lib_free(); * return -1; * } * * // Socket is now bound, call tcs_listen() and tcs_accept() to accept connections * * tcs_close(&server); * tcs_lib_free(); * } * @endcode * * @param[out] socket_ctx pointer to socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use. * @param[in] local_address address to bind to, or NULL to skip binding. * @param[in] remote_address address to connect to, or NULL to skip connecting. * @param[in] timeout_ms maximum time in milliseconds to wait for connection, or #TCS_WAIT_INF for OS default timeout. Ignored if @p remote_address is NULL. * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if @p socket_ctx is NULL, if *socket_ctx is not #TCS_SOCKET_INVALID, if both addresses are NULL, or if both are provided with different address families. * @retval #TCS_ERROR_CONNECTION_REFUSED if the remote server refused the connection. * @retval #TCS_ERROR_TIMED_OUT if the connection attempt timed out. * * @see tcs_connect() * @see tcs_listen() * @see tcs_close() */ TcsResult tcs_socket_tcp(TcsSocket* socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* remote_address, int timeout_ms); /** * @brief Create a TCP socket from string addresses, optionally bind and/or connect. * * Parses the address strings with ::tcs_address_parse() and delegates to ::tcs_socket_tcp(). * Addresses must include a port, e.g. "127.0.0.1:8080" or "[::1]:8080". * At least one of @p local_address or @p remote_address must be non-NULL. * On failure, *socket_ctx is always set back to #TCS_SOCKET_INVALID. * * @code * #include "tinycsocket.h" * int main() * { * tcs_lib_init(); * * TcsSocket socket = TCS_SOCKET_INVALID; * TcsResult res = tcs_socket_tcp_str(&socket, NULL, "127.0.0.1:8080", 5000); * if (res != TCS_SUCCESS) * { * tcs_lib_free(); * return -1; * } * * // Socket is now connected and ready for communication * * tcs_close(&socket); * tcs_lib_free(); * } * @endcode * * @param[out] socket_ctx pointer to socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use. * @param[in] local_address address string to bind to, or NULL to skip binding. * @param[in] remote_address address string to connect to, or NULL to skip connecting. * @param[in] timeout_ms maximum time in milliseconds to wait for connection, or #TCS_WAIT_INF for OS default timeout. Ignored if @p remote_address is NULL. * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if @p socket_ctx is NULL, if *socket_ctx is not #TCS_SOCKET_INVALID, or if both addresses are NULL. * @retval #TCS_ERROR_CONNECTION_REFUSED if the remote server refused the connection. * @retval #TCS_ERROR_TIMED_OUT if the connection attempt timed out. * * @see tcs_socket_tcp() * @see tcs_close() */ TcsResult tcs_socket_tcp_str(TcsSocket* socket_ctx, const char* local_address, const char* remote_address, int timeout_ms); /** * @brief Create a UDP socket, optionally bind to a local address and/or connect to a remote address. * * Creates an IPv4 or IPv6 UDP socket based on the address family of the provided address(es). * If @p local_address is not NULL, SO_REUSEADDR is set and the socket is bound to it. * If @p remote_address is not NULL and is a unicast address, the socket is connected to it, * allowing use of ::tcs_send() instead of ::tcs_send_to(). * If @p remote_address is a multicast address, the socket joins the multicast group. * When only @p remote_address is given (no @p local_address), the socket is also connected, * allowing use of ::tcs_send(). When both are given, the socket is not connected to avoid * filtering out multicast traffic — use ::tcs_send_to() instead. * At least one of @p local_address or @p remote_address must be non-NULL. * If both are provided, they must have the same address family. * On failure, *socket_ctx is always set back to #TCS_SOCKET_INVALID. * * @code * #include "tinycsocket.h" * int main() * { * tcs_lib_init(); * * struct TcsAddress local = TCS_ADDRESS_NONE; * local.family = TCS_AF_IP4; * local.data.ip4.address = TCS_ADDRESS_ANY_IP4; * local.data.ip4.port = 8080; * * TcsSocket socket = TCS_SOCKET_INVALID; * TcsResult res = tcs_socket_udp(&socket, &local, NULL); * if (res != TCS_SUCCESS) * { * tcs_lib_free(); * return -1; * } * * // Socket is now bound and ready to receive with tcs_receive_from() * * tcs_close(&socket); * tcs_lib_free(); * } * @endcode * * @param[out] socket_ctx pointer to socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use. * @param[in] local_address address to bind to, or NULL to skip binding. * @param[in] remote_address address to connect to, or NULL to skip connecting. * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if @p socket_ctx is NULL, if *socket_ctx is not #TCS_SOCKET_INVALID, if both addresses are NULL, or if both are provided with different address families. * * @see tcs_socket_udp_str() * @see tcs_close() */ TcsResult tcs_socket_udp(TcsSocket* socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* remote_address); /** * @brief Create a UDP socket from string addresses, optionally bind and/or connect. * * Parses the address strings with ::tcs_address_resolve() and delegates to ::tcs_socket_udp(). * Addresses must include a port, e.g. "127.0.0.1:8080" or "[::1]:8080". * At least one of @p local_address or @p remote_address must be non-NULL. * On failure, *socket_ctx is always set back to #TCS_SOCKET_INVALID. * * @code * #include "tinycsocket.h" * int main() * { * tcs_lib_init(); * * TcsSocket socket = TCS_SOCKET_INVALID; * TcsResult res = tcs_socket_udp_str(&socket, "0.0.0.0:8080", NULL); * if (res != TCS_SUCCESS) * { * tcs_lib_free(); * return -1; * } * * // Socket is now bound and ready to receive with tcs_receive_from() * * tcs_close(&socket); * tcs_lib_free(); * } * @endcode * * @param[out] socket_ctx pointer to socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use. * @param[in] local_address address string to bind to, or NULL to skip binding. * @param[in] remote_address address string to connect to, or NULL to skip connecting. * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if @p socket_ctx is NULL, if *socket_ctx is not #TCS_SOCKET_INVALID, or if both addresses are NULL. * * @see tcs_socket_udp() * @see tcs_close() */ TcsResult tcs_socket_udp_str(TcsSocket* socket_ctx, const char* local_address, const char* remote_address); /** * @brief Create a packet socket bound to a network interface. * * Creates an AF_PACKET socket for sending and receiving raw L2 frames. * The socket is bound to the interface and protocol specified in @p bind_address. * On failure, *socket_ctx is always set back to #TCS_SOCKET_INVALID. * * @code * #include "tinycsocket.h" * int main() * { * tcs_lib_init(); * * struct TcsAddress bind = TCS_ADDRESS_NONE; * bind.family = TCS_AF_PACKET; * bind.data.packet.interface_id = 1; // e.g. lo * bind.data.packet.protocol = 0x22F0; // e.g. AVTP * * TcsSocket socket = TCS_SOCKET_INVALID; * TcsResult res = tcs_socket_packet(&socket, &bind, TCS_SOCK_DGRAM); * if (res != TCS_SUCCESS) * { * tcs_lib_free(); * return -1; * } * * // Socket is now bound and ready for tcs_send_to() / tcs_receive_from() * * tcs_close(&socket); * tcs_lib_free(); * } * @endcode * * @param[out] socket_ctx pointer to socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use. * @param[in] bind_address address with family TCS_AF_PACKET specifying interface_id and protocol. * @param[in] type socket type, either #TCS_SOCK_RAW for full L2 frames or #TCS_SOCK_DGRAM for frames without the L2 header. * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if @p socket_ctx is NULL, if *socket_ctx is not #TCS_SOCKET_INVALID, or if @p bind_address is NULL or not TCS_AF_PACKET. * * @see tcs_socket_packet_str() * @see tcs_close() */ TcsResult tcs_socket_packet(TcsSocket* socket_ctx, const struct TcsAddress* bind_address, int type); /** * @brief Create a packet socket bound to a named network interface. * * Looks up the interface by name using ::tcs_interface_list() and delegates to ::tcs_socket_packet(). * On failure, *socket_ctx is always set back to #TCS_SOCKET_INVALID. * * @code * #include "tinycsocket.h" * int main() * { * tcs_lib_init(); * * TcsSocket socket = TCS_SOCKET_INVALID; * TcsResult res = tcs_socket_packet_str(&socket, "eth0", 0x22F0, TCS_SOCK_DGRAM); * if (res != TCS_SUCCESS) * { * tcs_lib_free(); * return -1; * } * * // Socket is now bound and ready for tcs_send_to() / tcs_receive_from() * * tcs_close(&socket); * tcs_lib_free(); * } * @endcode * * @param[out] socket_ctx pointer to socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use. * @param[in] interface_name name of the network interface to bind to. * @param[in] protocol EtherType protocol in host byte order, e.g. 0x22F0 for AVTP. * @param[in] type socket type, either #TCS_SOCK_RAW for full L2 frames or #TCS_SOCK_DGRAM for frames without the L2 header. * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if @p socket_ctx is NULL, if *socket_ctx is not #TCS_SOCKET_INVALID, or if @p interface_name is NULL or not found. * * @see tcs_socket_packet() * @see tcs_close() */ TcsResult tcs_socket_packet_str(TcsSocket* socket_ctx, const char* interface_name, uint16_t protocol, int type); /** * @brief Closes the socket, stop communication and free all resources for the socket. * * This will free all resources associated with the socket and set the socket value to #TCS_SOCKET_INVALID. * * @param[in,out] socket_ctx is a pointer to your socket context you have previously created with ::tcs_socket() or one of the helper functions. Will be set to #TCS_SOCKET_INVALID on success. * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument. Such as a NULL pointer or a socket that is already #TCS_SOCKET_INVALID. * @retval #TCS_ERROR_SOCKET_CLOSED if the socket is already closed. * * @see tcs_socket() * @see tcs_socket_tcp() * @see tcs_socket_udp() * @see tcs_socket_packet() * @see tcs_lib_init() * @see tcs_lib_free() */ TcsResult tcs_close(TcsSocket* socket_ctx); /** * @brief Binds a socket to a local address. * * This function associates a socket with a specific local address and port, allowing it to * receive incoming connections or datagrams on that address. For TCP server sockets, you * must call this before tcs_listen(). For UDP sockets, binding determines which address * and port the socket will receive datagrams on. * * @code * #include "tinycsocket.h" * int main() * { * TcsResult tcs_init_res = tcs_lib_init(); * if (tcs_init_res != TCS_SUCCESS) * return -1; * * TcsSocket server_socket = TCS_SOCKET_INVALID; * TcsResult socket_res = tcs_socket(&server_socket, TCS_AF_IP4, TCS_SOCK_STREAM, TCS_PROTOCOL_IP_TCP); * if (socket_res != TCS_SUCCESS) * { * tcs_lib_free(); * return -2; * } * * struct TcsAddress local_address = TCS_ADDRESS_NONE; * local_address.family = TCS_AF_IP4; * local_address.data.ip4.address = TCS_ADDRESS_ANY_IP4; // Bind to all interfaces * local_address.data.ip4.port = 8080; * * TcsResult bind_res = tcs_bind(server_socket, &local_address); * if (bind_res != TCS_SUCCESS) * { * tcs_close(&server_socket); * tcs_lib_free(); * return -3; // Failed to bind to address * } * * // For TCP: now call tcs_listen() * // For UDP: socket is ready to receive datagrams * * tcs_close(&server_socket); * tcs_lib_free(); * return 0; * } * @endcode * * @param socket_ctx The socket to bind. Must be a valid socket created with tcs_socket(). * @param local_address The local address structure to bind to. Use TCS_ADDRESS_ANY_IP4 for the address field to bind to all interfaces. * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if socket_ctx is invalid or local_address is NULL. * @retval #TCS_ERROR_PERMISSION_DENIED if binding to the specified address/port requires elevated privileges. * @retval #TCS_ERROR_SYSTEM if the address is already in use or another system error occurred. * * @see tcs_listen() * @see tcs_socket_tcp() * @see tcs_socket_udp() * @see tcs_address_socket_local() */ TcsResult tcs_bind(TcsSocket socket_ctx, const struct TcsAddress* local_address); /** * @brief Connect a socket to a remote address structure. * * This function establishes a connection to the specified remote address structure. * For TCP sockets, this initiates a three-way handshake. For UDP sockets, this * associates the socket with the remote address for subsequent send operations. * The function blocks indefinitely until the connection is established or fails. * Timeout for this function is set by OS defaults. Use TcsPool or * ::tcs_opt_nonblocking_set() for non-blocking behavior. * * @code * #include "tinycsocket.h" * int main() * { * TcsResult tcs_init_res = tcs_lib_init(); * if (tcs_init_res != TCS_SUCCESS) * return -1; * * TcsSocket client_socket = TCS_SOCKET_INVALID; * TcsResult socket_res = tcs_socket(&client_socket, TCS_AF_IP4, TCS_SOCK_STREAM, TCS_PROTOCOL_IP_TCP); * if (socket_res != TCS_SUCCESS) * { * tcs_lib_free(); * return -2; * } * * struct TcsAddress remote_address = TCS_ADDRESS_NONE; * remote_address.family = TCS_AF_IP4; * remote_address.data.ip4.address = 0x7F000001; // 127.0.0.1 loopback * remote_address.data.ip4.port = 8080; * * TcsResult connect_res = tcs_connect(client_socket, &remote_address); * if (connect_res != TCS_SUCCESS) * { * tcs_close(&client_socket); * tcs_lib_free(); * return -3; // Failed to connect * } * * // Socket is now connected and ready for communication * uint8_t buffer[] = "Hello, server!"; * size_t bytes_sent = 0; * tcs_send(client_socket, buffer, sizeof(buffer)-1, TCS_MSG_SENDALL, &bytes_sent); * * tcs_close(&client_socket); * tcs_lib_free(); * return 0; * } * @endcode * * @param socket_ctx The socket to connect. Must be a valid socket created with tcs_socket(). * @param address The remote address structure to connect to. * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if socket_ctx is invalid or address is NULL. * @retval #TCS_ERROR_CONNECTION_REFUSED if the remote server refused the connection. * @retval #TCS_ERROR_TIMED_OUT if the connection attempt timed out (can take 3+ minutes for unreachable hosts). * @retval #TCS_ERROR_SYSTEM if another system error occurred. * * @see tcs_connect_str() * @see tcs_socket_tcp() * @see tcs_bind() * @see tcs_listen() */ TcsResult tcs_connect(TcsSocket socket_ctx, const struct TcsAddress* address); /** * @brief Connect a socket to a remote hostname and port. * * This function establishes a connection to the specified remote address and port. * For TCP sockets, this initiates a three-way handshake. For UDP sockets, this * associates the socket with the remote address for subsequent send operations. * Timeout for this function is set by OS defaults. Use TcsPool or * ::tcs_opt_nonblocking_set() for non-blocking behavior. * * @code * #include "tinycsocket.h" * int main() * { * TcsResult tcs_init_res = tcs_lib_init(); * if (tcs_init_res != TCS_SUCCESS) * return -1; * * TcsSocket client_socket = TCS_SOCKET_INVALID; * TcsResult socket_res = tcs_socket(&client_socket, TCS_AF_IP4, TCS_SOCK_STREAM, TCS_PROTOCOL_IP_TCP); * if (socket_res != TCS_SUCCESS) * { * tcs_lib_free(); * return -2; * } * * TcsResult connect_res = tcs_connect_str(client_socket, "192.168.1.100", 8080); * if (connect_res != TCS_SUCCESS) * { * tcs_close(&client_socket); * tcs_lib_free(); * return -3; // Failed to connect * } * * // Socket is now connected and ready for communication * uint8_t buffer[] = "Hello, server!"; * size_t bytes_sent = 0; * tcs_send(client_socket, buffer, sizeof(buffer)-1, TCS_MSG_SENDALL, &bytes_sent); * * tcs_close(&client_socket); * tcs_lib_free(); * return 0; * } * @endcode * * @param socket_ctx The socket to connect. Must be a valid socket created with tcs_socket(). * @param remote_address The remote hostname or IP address to connect to. * @param port The remote port number to connect to. * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if socket_ctx is invalid or remote_address is NULL. * @retval #TCS_ERROR_CONNECTION_REFUSED if the remote server refused the connection. * @retval #TCS_ERROR_ADDRESS_LOOKUP_FAILED if the hostname could not be resolved. * @retval #TCS_ERROR_TIMED_OUT if the connection attempt timed out. * @retval #TCS_ERROR_SYSTEM if another system error occurred. * * @see tcs_connect() * @see tcs_socket_tcp_str() * @see tcs_bind() * @see tcs_listen() */ TcsResult tcs_connect_str(TcsSocket socket_ctx, const char* remote_address, uint16_t port); /** * @brief Let a socket start listening for incoming connections. * * Call #tcs_bind() first to bind to a local address to listening at. * * @param socket_ctx is your in-out socket context. * @param backlog is the maximum number of queued incoming sockets. Use #TCS_BACKLOG_MAX to set it to max. * @return #TCS_SUCCESS if successful, otherwise the error code. * @see tcs_accept() */ TcsResult tcs_listen(TcsSocket socket_ctx, int backlog); /** * @brief Accept a socket from a listening socket. * * The accepted socket will get assigned a random local free port. * The listening socket will not be affected by this call. * * Example usage: * @code * TcsSocket listen_socket = TCS_SOCKET_INVALID; * tcs_socket(&listen_socket, TCS_AF_IP4, TCS_SOCK_STREAM, TCS_PROTOCOL_IP_TCP); * struct TcsAddress local_address = TCS_ADDRESS_NONE; * local_address.family = TCS_AF_IP4; * local_address.data.ip4.port = 1212; * tcs_bind(listen_socket, &local_address); * tcs_listen(listen_socket, TCS_BACKLOG_MAX); * while (true) * { * TcsSocket client_socket = TCS_SOCKET_INVALID; * tcs_accept(listen_socket, &client_socket, NULL); * // Do stuff with client_socket here * tcs_close(&client_socket); * } * @endcode * * @param socket_ctx is your listening socket you used when you called ::tcs_listen(). * @param child_socket_ctx is your accepted socket. Must have the in value of #TCS_SOCKET_INVALID. * @param address is an optional pointer to a buffer where the remote address of the accepted socket can be stored. * * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_accept(TcsSocket socket_ctx, TcsSocket* child_socket_ctx, struct TcsAddress* address); /** * @brief Turn off communication with a 3-way handshaking for the socket. * * Use this function to cancel blocking calls (recv, accept etc) from another thread, or use sigaction. * The socket will finish all queued sends first. * * @param socket_ctx is your in-out socket context. * @param direction defines in which direction you want to turn off the communication. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_shutdown(TcsSocket socket_ctx, TcsSocketDirection direction); /** * @brief Sends data on a socket, blocking * * @param socket_ctx is your in-out socket context. * @param buffer is a pointer to your data you want to send. * @param buffer_size is number of bytes of the data you want to send. * @param flags is currently not in use. * @param bytes_sent is how many bytes that was successfully sent. * @return #TCS_SUCCESS if successful, otherwise the error code. * @see tcs_receive() */ TcsResult tcs_send(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size, uint32_t flags, size_t* bytes_sent); /** * @brief Sends data to an address, useful with UDP sockets. * * @param socket_ctx is your in-out socket context. * @param buffer is a pointer to your data you want to send. * @param buffer_size is number of bytes of the data you want to send. * @param flags is currently not in use. * @param destination_address is the address to send to. * @param bytes_sent is how many bytes that was successfully sent. * @return #TCS_SUCCESS if successful, otherwise the error code. * @see tcs_receive_from() */ TcsResult tcs_send_to(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size, uint32_t flags, const struct TcsAddress* destination_address, size_t* bytes_sent); /** * @brief Sends several data buffers on a socket as one message. * * @param socket_ctx is your in-out socket context. * @param buffers is a pointer to your array of buffers you want to send. * @param buffer_count is the number of buffers in your array. * @param flags is currently not in use. * @param bytes_sent is how many bytes in total that was successfully sent. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_sendv(TcsSocket socket_ctx, const struct TcsBuffer* buffers, size_t buffer_count, uint32_t flags, size_t* bytes_sent); /** * @brief Send data encoded as a netstring. * * Netstrings provide a simple framing format for sending discrete messages over a * stream-oriented transport such as TCP (::TCS_SOCK_STREAM). The format is: * @code * :, * @endcode * For example, the string "hello" is encoded as @c 5:hello, * * This is useful when you need packet-like semantics over TCP, where message * boundaries are otherwise not preserved. * * @param socket_ctx socket to send on. * @param buffer data to send. * @param buffer_size number of bytes to send. * @return #TCS_SUCCESS if successful, otherwise the error code. * @see tcs_receive_netstring() */ TcsResult tcs_send_netstring(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size); /** * @brief Receive data from a socket to your buffer * * @param socket_ctx is your in-out socket context. * @param buffer is a pointer to your buffer where you want to store the incoming data to. * @param buffer_size is the byte size of your buffer, for preventing overflows. * @param flags is currently not in use. * @param bytes_received is how many bytes that was successfully written to your buffer. * @return #TCS_SUCCESS if successful, otherwise the error code. * @see tcs_send() */ TcsResult tcs_receive(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, uint32_t flags, size_t* bytes_received); /** * @brief Receive data from an address, useful with UDP sockets. * * @param socket_ctx is your in-out socket context. * @param buffer is a pointer to your buffer where you want to store the incoming data to. * @param buffer_size is the byte size of your buffer, for preventing overflows. * @param flags is currently not in use. * @param source_address is the address to receive from. * @param bytes_received is how many bytes that was successfully written to your buffer. * @return #TCS_SUCCESS if successful, otherwise the error code. * @see tcs_send_to() */ TcsResult tcs_receive_from(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, uint32_t flags, struct TcsAddress* source_address, size_t* bytes_received); /** * @brief Read up to and including a delimiter. * * This function ensures that the socket buffer will keep its data after the delimiter. * For performance it is recommended to read everything and split it yourself. * The call will block until the delimiter is received or the supplied buffer is filled. * The timeout time will not be per call but between each packet received. Longer call time than timeout is possible. * * @param socket_ctx is your in-out socket context. * @param buffer is a pointer to your buffer where you want to store the incoming data to. * @param buffer_size is the byte size of your buffer, for preventing overflows. * @param bytes_received is how many bytes that was successfully written to your buffer. * @param delimiter is your byte value where you want to stop reading. (including delimiter) * @return #TCS_AGAIN if no delimiter was found and the supplied buffer was filled. * @return #TCS_SUCCESS if the delimiter was found. Otherwise the error code. * @see tcs_receive_netstring() */ TcsResult tcs_receive_line(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, size_t* bytes_received, uint8_t delimiter); /** * @brief Receive a netstring-encoded message. * * Reads and decodes a netstring (see tcs_send_netstring() for format details) from * a stream socket. This allows receiving discrete messages over TCP where message * boundaries are otherwise not preserved. * * @param socket_ctx socket to receive from. * @param buffer buffer to store the decoded data (without the netstring framing). * @param buffer_size size of the buffer in bytes. * @param bytes_received optional pointer to receive the number of payload bytes received. * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_ILL_FORMED_MESSAGE if the netstring is malformed or the length overflows. * @retval #TCS_ERROR_MEMORY if the buffer is too small for the payload. * @see tcs_send_netstring() */ TcsResult tcs_receive_netstring(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, size_t* bytes_received); /** * @brief Create a context used for waiting on several sockets. * * TcsPool can be used to monitor several sockets for events (reading, writing or error). * Use tcs_pool_poll() to get a list of sockets ready to interact with. * * @code * tcs_lib_init(); * TcsSocket socket1 = TCS_SOCKET_INVALID; * TcsSocket socket2 = TCS_SOCKET_INVALID; * tcs_socket(&socket1, TCS_AF_IP4, TCS_SOCK_DGRAM, TCS_PROTOCOL_IP_UDP); * tcs_socket(&socket2, TCS_AF_IP4, TCS_SOCK_DGRAM, TCS_PROTOCOL_IP_UDP); * * struct TcsAddress addr1 = TCS_ADDRESS_NONE; * addr1.family = TCS_AF_IP4; * addr1.data.ip4.port = 1000; * tcs_bind(socket1, &addr1); * * struct TcsAddress addr2 = TCS_ADDRESS_NONE; * addr2.family = TCS_AF_IP4; * addr2.data.ip4.port = 1001; * tcs_bind(socket2, &addr2); * * struct TcsPool* pool = NULL; * tcs_pool_create(&pool); * tcs_pool_add(pool, socket1, NULL, true, false, false); // Only wait for incoming data * tcs_pool_add(pool, socket2, NULL, true, false, false); * * size_t populated = 0; * struct TcsPollEvent ev[2] = {TCS_POOL_EVENT_EMPTY, TCS_POOL_EVENT_EMPTY}; * tcs_pool_poll(pool, ev, 2, &populated, 1000); // Will wait 1000 ms for data on port 1000 or 1001 * for (size_t i = 0; i < populated; ++i) * { * if (ev[i].can_read) * { * uint8_t recv_buffer[8192] = {0}; * size_t bytes_received = 0; * tcs_receive(ev[i].socket, recv_buffer, 8191, TCS_FLAG_NONE, &bytes_received); * } * } * tcs_pool_destroy(&pool); * tcs_close(&socket1); * tcs_close(&socket2); * tcs_lib_free(); * @endcode * * @param[out] pool is your out pool context pointer. Initiate a TcsPool pointer to NULL and use the address of this pointer. * @return #TCS_SUCCESS if successful, otherwise the error code. * @see tcs_pool_destroy() */ TcsResult tcs_pool_create(struct TcsPool** pool); /** * @brief Frees all resources bound to the pool. * * Will set @p pool to NULL when successfully. * * @param[in,out] pool is your pool context pointer created with tcs_pool_create(). Will be set to NULL. * @return #TCS_SUCCESS if successful, otherwise the error code. * @see tcs_pool_create() */ TcsResult tcs_pool_destroy(struct TcsPool** pool); /** * @brief Add a socket to the pool. * * @param[in] pool is your pool context pointer created with tcs_pool_create(). * @param socket_ctx will be added to the pool. Note that you can still use it outside of the pool. * @param[in] user_data is a pointer of your choice that is associated with the socket. Use NULL if not used. * @param poll_can_read true if you want to poll @p socket_ctx for read events. * @param poll_can_write true if you want to poll @p socket_ctx for write events. * @param poll_error true if you want to poll if any error has happened to @p socket_ctx. * @return #TCS_SUCCESS if successful, otherwise the error code. * @see tcs_pool_remove() */ TcsResult tcs_pool_add(struct TcsPool* pool, TcsSocket socket_ctx, void* user_data, bool poll_can_read, bool poll_can_write, bool poll_error); /** * @brief Remove a socket from the pool. * * @param[in] pool is a context pointer created with tcs_pool_create() * @param socket_ctx will be removed from the pool. * @return #TCS_SUCCESS if successful, otherwise the error code. * @see tcs_pool_add() */ TcsResult tcs_pool_remove(struct TcsPool* pool, TcsSocket socket_ctx); /** * @brief Wait for events on sockets in the pool. * * @param[in] pool is your pool context pointer created with @p tcs_pool_create(). * @param[in,out] events is an array with in-out events. Assign each element to #TCS_POOL_EVENT_EMPTY. * @param events_count number of in elements in your events array. Does not make sense to have more events than number of sockets in the pool. If too short, all events may not be returned. * @param[out] events_populated will contain the number of events the parameter events has been populated with by the call. * @param timeout_ms is the maximum wait time for any event. If any event happens before this time, the call will return immediately. * @return #TCS_SUCCESS if successful, otherwise the error code. * @see tcs_pool_remove() */ TcsResult tcs_pool_poll(struct TcsPool* pool, struct TcsPollEvent* events, size_t events_count, size_t* events_populated, int timeout_ms); /** * @brief Set parameters on a socket. It is recommended to use tcs_opt_*_set() instead. * * @param socket_ctx is your in-out socket context. * @param level is the definition level. * @param option_name is the option name. * @param option_value is a pointer to the option value. * @param option_size is the byte size of the data pointed by @p option_value. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_set(TcsSocket socket_ctx, int32_t level, int32_t option_name, const void* option_value, size_t option_size); /** * @brief Get parameters on a socket. It is recommended to use tcs_opt_*_get() instead. * * @code * uint8_t c; * size_t a = sizeof(c); * tcs_opt_get(socket, TCS_SOL_IP, TCS_SO_IP_MULTICAST_LOOP, &c, &a); * @endcode * * @param socket_ctx is your in-out socket context. * @param level is the definition level. * @param option_name is the option name. * @param option_value is a pointer to the option value. * @param option_size is a pointer the byte size of the data pointed by @p option_value. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_get(TcsSocket socket_ctx, int32_t level, int32_t option_name, void* option_value, size_t* option_size); /** * @brief Query the socket type (e.g. ::TCS_SOCK_STREAM or ::TCS_SOCK_DGRAM). * * @param socket_ctx socket to query. * @param type pointer to receive the socket type. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_type_get(TcsSocket socket_ctx, int* type); /** * @brief Enable the socket to be allowed to send to broadcast addresses. * * By default, sockets are not permitted to send to broadcast addresses (e.g. * 255.255.255.255 or subnet broadcast addresses) as a safety measure to prevent * accidental broadcast storms. Enable this option on UDP sockets that need to * send broadcast traffic. * * Only valid for protocols that support broadcast, for example UDP. Default is false. * * @param socket_ctx socket to enable/disable permission to send broadcast on. * @param do_allow_broadcast set to true to allow, false to forbid. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_broadcast_set(TcsSocket socket_ctx, bool do_allow_broadcast); /** * @brief Query whether broadcast is enabled on a socket. * * See tcs_opt_broadcast_set() for details on the broadcast option. * * @param socket_ctx socket to query. * @param is_broadcast_allowed pointer to receive the current broadcast setting. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_broadcast_get(TcsSocket socket_ctx, bool* is_broadcast_allowed); /** * @brief Enable or disable TCP keep-alive on a socket. * * Keep-alive operates at the transport layer (TCP). When enabled, the OS periodically * sends probe packets on an idle connection to detect if the remote peer is still * reachable. Without keep-alive, a connection where neither side sends data can remain * open indefinitely even if the remote host has crashed or the network path is broken. * * Keep-alive probes also prevent NAT routers and stateful firewalls from dropping * idle connection mappings due to inactivity timeouts. * * This is particularly useful for long-lived connections that may be idle for extended * periods, such as database connections or control channels. * * @param socket_ctx socket to configure. * @param do_keep_alive set to true to enable, false to disable. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_keep_alive_set(TcsSocket socket_ctx, bool do_keep_alive); /** * @brief Query whether keep-alive is enabled on a socket. * * See tcs_opt_keep_alive_set() for details on what keep-alive does. * * @param socket_ctx socket to query. * @param is_keep_alive_enabled pointer to receive the current keep-alive setting. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_keep_alive_get(TcsSocket socket_ctx, bool* is_keep_alive_enabled); /** * @brief Allow or disallow address reuse on a socket. * * **TCP:** * Allows binding to a port that is in the TIME_WAIT state. When a TCP connection * is closed, the port remains reserved for up to 2 minutes (2x Maximum Segment * Lifetime) to ensure delayed packets from the old connection are not misinterpreted * by a new one. This option bypasses that restriction, which is useful for server * sockets that need to restart quickly. * On POSIX, it also allows overlapping wildcard and specific address binds on the same * port (e.g. both 0.0.0.0:8080 and 192.168.1.1:8080). The most specific address wins. * * **UDP:** * Allows multiple sockets to bind to the same address and port. The behavior for * unicast datagrams is OS-specific, but on most implementations only the most * recently bound socket receives them. * This is primarily useful for multicast, where several sockets need to receive the * same group traffic. * * For load-balancing multiple sockets on the same port, see ::tcs_opt_reuse_port_set(). * * @note On Windows, this library sets SO_EXCLUSIVEADDRUSE alongside SO_REUSEADDR * for TCP sockets to prevent port hijacking, matching the security guarantees of * POSIX. For UDP sockets, only SO_REUSEADDR is set, preserving multicast and * same-port binding behavior. I.e. it mimics POSIX behaviour. * * @param socket_ctx socket to configure. * @param do_allow_reuse_address set to true to allow, false to disallow. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_reuse_address_set(TcsSocket socket_ctx, bool do_allow_reuse_address); /** * @brief Query whether address reuse is enabled on a socket. * * See tcs_opt_reuse_address_set() for details on the address reuse option. * * @param socket_ctx socket to query. * @param is_reuse_address_allowed pointer to receive the current setting. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_reuse_address_get(TcsSocket socket_ctx, bool* is_reuse_address_allowed); /** * @brief Allow or disallow multiple sockets to bind to the same address and port. * * When enabled, the kernel distributes incoming traffic across all sockets bound to * the same address and port. All participating sockets must set this option, and on * POSIX systems they must belong to the same effective UID. * * This is useful for multi-threaded or multi-process servers that want to load-balance * incoming connections or datagrams across workers without application-level dispatching. * * @note Only supported on POSIX (Linux 3.9+). Returns #TCS_ERROR_NOT_SUPPORTED on Windows, * which has no equivalent with the same semantics. * * @param socket_ctx socket to configure. * @param do_allow_reuse_port set to true to allow, false to disallow. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_reuse_port_set(TcsSocket socket_ctx, bool do_allow_reuse_port); /** * @brief Query whether port reuse is enabled on a socket. * * See tcs_opt_reuse_port_set() for details. * * @note Returns #TCS_ERROR_NOT_SUPPORTED on Windows. * * @param socket_ctx socket to query. * @param is_reuse_port_allowed pointer to receive the current setting. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_reuse_port_get(TcsSocket socket_ctx, bool* is_reuse_port_allowed); /** * @brief Set the send buffer size of a socket. * * @param socket_ctx socket to configure. * @param send_buffer_size desired send buffer size in bytes. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_send_buffer_size_set(TcsSocket socket_ctx, size_t send_buffer_size); /** * @brief Query the send buffer size of a socket. * * @param socket_ctx socket to query. * @param send_buffer_size pointer to receive the send buffer size in bytes. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_send_buffer_size_get(TcsSocket socket_ctx, size_t* send_buffer_size); /** * @brief Set the receive buffer size of a socket. * * @param socket_ctx socket to configure. * @param receive_buffer_size desired receive buffer size in bytes. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_receive_buffer_size_set(TcsSocket socket_ctx, size_t receive_buffer_size); /** * @brief Query the receive buffer size of a socket. * * @param socket_ctx socket to query. * @param receive_buffer_size pointer to receive the receive buffer size in bytes. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_receive_buffer_size_get(TcsSocket socket_ctx, size_t* receive_buffer_size); /** * @brief Set the receive timeout of a socket. * * @param socket_ctx socket to configure. * @param timeout_ms timeout in milliseconds. Must be non-negative. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_receive_timeout_set(TcsSocket socket_ctx, int timeout_ms); /** * @brief Query the receive timeout of a socket. * * @param socket_ctx socket to query. * @param timeout_ms pointer to receive the timeout in milliseconds. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_receive_timeout_get(TcsSocket socket_ctx, int* timeout_ms); /** * @brief Configure the linger behavior of a socket on close. * * @param socket_ctx socket to configure. * @param do_linger set to true to enable lingering, false to disable. * @param timeout_seconds linger timeout in seconds (only used when do_linger is true). * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_linger_set(TcsSocket socket_ctx, bool do_linger, int timeout_seconds); /** * @brief Query the linger behavior of a socket. * * @param socket_ctx socket to query. * @param do_linger pointer to receive whether lingering is enabled. * @param timeout_seconds pointer to receive the linger timeout in seconds. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_linger_get(TcsSocket socket_ctx, bool* do_linger, int* timeout_seconds); /** * @brief Enable or disable Nagle's algorithm (TCP_NODELAY). * * @param socket_ctx socket to configure. * @param use_no_delay set to true to disable Nagle's algorithm (lower latency), false to enable it. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_ip_no_delay_set(TcsSocket socket_ctx, bool use_no_delay); /** * @brief Query whether Nagle's algorithm is disabled on a socket. * * @param socket_ctx socket to query. * @param is_no_delay_used pointer to receive the current setting. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_ip_no_delay_get(TcsSocket socket_ctx, bool* is_no_delay_used); /** * @brief Enable or disable inline reception of out-of-band data. * * @param socket_ctx socket to configure. * @param enable_oob set to true to receive OOB data inline, false to disable. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_out_of_band_inline_set(TcsSocket socket_ctx, bool enable_oob); /** * @brief Query whether out-of-band data is received inline. * * @param socket_ctx socket to query. * @param is_oob_enabled pointer to receive the current setting. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_out_of_band_inline_get(TcsSocket socket_ctx, bool* is_oob_enabled); /** * @brief Set the socket priority. * * @note Not supported on Windows. Will return #TCS_ERROR_NOT_IMPLEMENTED on that platform. * * @param socket_ctx socket to configure. * @param priority priority value. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_priority_set(TcsSocket socket_ctx, int priority); /** * @brief Query the socket priority. * * @note Not supported on Windows. Will return #TCS_ERROR_NOT_IMPLEMENTED on that platform. * * @param socket_ctx socket to query. * @param priority pointer to receive the priority value. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_priority_get(TcsSocket socket_ctx, int* priority); /** * @brief Join a multicast group on a specific local interface. * * @param socket_ctx socket to configure. * @param local_address local interface address to use. * @param multicast_address multicast group address to join. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_membership_add_to(TcsSocket socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* multicast_address); /** * @brief Leave a multicast group on a specific local interface. * * @param socket_ctx socket to configure. * @param local_address local interface address used when joining. * @param multicast_address multicast group address to leave. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_membership_drop_from(TcsSocket socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* multicast_address); /** * @brief Join a multicast group using the default local interface. * * @param socket_ctx socket to configure. * @param multicast_address multicast group address to join. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_membership_add(TcsSocket socket_ctx, const struct TcsAddress* multicast_address); /** * @brief Join a multicast group by address string. * * Resolves the multicast address string and joins the group using the default local interface. * * @param socket_ctx socket to configure. * @param multicast_address multicast group address string (e.g. "239.1.2.3" or "ff02::1"). * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if multicast_address is NULL. * @retval #TCS_ERROR_ADDRESS_LOOKUP_FAILED if the address string could not be resolved. * @see tcs_opt_membership_add() * @see tcs_opt_membership_drop_str() */ TcsResult tcs_opt_membership_add_str(TcsSocket socket_ctx, const char* multicast_address); /** * @brief Leave a multicast group using the default local interface. * * @param socket_ctx socket to configure. * @param multicast_address multicast group address to leave. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_membership_drop(TcsSocket socket_ctx, const struct TcsAddress* multicast_address); /** * @brief Leave a multicast group by address string. * * Parses the multicast address string and leaves the group using the default local interface. * * @param socket_ctx socket to configure. * @param multicast_address multicast group address string (e.g. "239.1.2.3" or "ff02::1"). * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if multicast_address is NULL. * @retval #TCS_ERROR_ADDRESS_LOOKUP_FAILED if the address string could not be resolved. * @see tcs_opt_membership_drop() * @see tcs_opt_membership_add_str() */ TcsResult tcs_opt_membership_drop_str(TcsSocket socket_ctx, const char* multicast_address); /** * @brief Set the outgoing interface for multicast packets. * * @param socket_ctx socket to configure. * @param local_address local interface address to use for outgoing multicast. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_multicast_interface_set(TcsSocket socket_ctx, const struct TcsAddress* local_address); /** * @brief Enable or disable multicast loopback. * * When enabled, multicast packets sent on this socket are looped back and * delivered to local receivers on the same host. * * @param socket_ctx socket to configure. * @param do_loopback set to true to enable loopback, false to disable. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_multicast_loop_set(TcsSocket socket_ctx, bool do_loopback); /** * @brief Get the current multicast loopback setting. * * @param socket_ctx socket to query. * @param is_loopback pointer to receive the current setting. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_multicast_loop_get(TcsSocket socket_ctx, bool* is_loopback); /** * @brief Set a socket to non-blocking or blocking mode. * * @param socket_ctx socket to configure. * @param do_nonblocking set to true for non-blocking, false for blocking. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_opt_nonblocking_set(TcsSocket socket_ctx, bool do_nonblocking); /** * @brief Query the non-blocking state of a socket. * * @note Not supported on Windows. Will return #TCS_ERROR_NOT_SUPPORTED on that platform. */ TcsResult tcs_opt_nonblocking_get(TcsSocket socket_ctx, bool* is_nonblocking); /** * @brief List available network interfaces. * * @param interfaces array to receive interface information, or NULL to only count. * @param capacity number of elements in the interfaces array. * @param out_count pointer to receive the total number of interfaces available, which may exceed capacity. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_interface_list(struct TcsInterface interfaces[], size_t capacity, size_t* out_count); /** * @brief Resolve a hostname to one or more addresses. * * @param hostname hostname or IP string to resolve. * @param address_family address family filter, or ::TCS_AF_ANY for all. * @param addresses array to receive resolved addresses, or NULL to only count. * @param capacity number of elements in the addresses array. * @param out_count pointer to receive the number of addresses found. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_address_resolve(const char* hostname, TcsAddressFamily address_family, struct TcsAddress addresses[], size_t capacity, size_t* out_count); /** * @brief List addresses associated with network interfaces. * * @param interface_id_filter interface ID to filter by, or 0 for all interfaces. * @param address_family_filter address family filter, or ::TCS_AF_ANY for all. * @param interface_addresses array to receive results, or NULL to only count. * @param capacity number of elements in the array. * @param out_count pointer to receive the total number of results available, which may exceed capacity. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_address_list(unsigned int interface_id_filter, TcsAddressFamily address_family_filter, struct TcsInterfaceAddress interface_addresses[], size_t capacity, size_t* out_count); /** * @brief Get the local address of a bound or connected socket. * * @param socket_ctx socket to query. * @param local_address pointer to receive the local address. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_address_socket_local(TcsSocket socket_ctx, struct TcsAddress* local_address); /** * @brief Get the remote address of a connected socket. * * @param socket_ctx socket to query. * @param remote_address pointer to receive the remote address. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_address_socket_remote(TcsSocket socket_ctx, struct TcsAddress* remote_address); /** * @brief Get the address family of a socket. * * @param socket_ctx socket to query. * @param out_family pointer to receive the address family. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_address_socket_family(TcsSocket socket_ctx, TcsAddressFamily* out_family); /** * @brief Parse a network address from a string. * * Supports IPv4, IPv6 (RFC 4291 all three forms), MAC, and bracket/port notation (RFC 3986). * IPv6 zone IDs are limited to numeric values per the minimum requirement of RFC 4007. * String-based zone IDs (e.g. "%%eth0") are not supported. * * Examples: * - "192.168.0.1:1212" * - "::1" * - "[::1]:443" * - "fe80::1%%3" * - "::ffff:192.168.1.1" * - "91:E0:F0:00:FE:00" * * Note that this function will not perform DNS resolution. Use ::tcs_address_resolve() for that. * * @param str The string to parse. * @param out_address The parsed address. * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_address_parse(const char str[], struct TcsAddress* out_address); /** * @brief Convert an address to a string. * * This will make a verbose string representation of the address. * * @param address * @param out_str * @return #TCS_SUCCESS if successful, otherwise the error code. */ TcsResult tcs_address_to_str(const struct TcsAddress* address, char out_str[70]); /** @brief Check if two addresses are equal. */ bool tcs_address_is_equal(const struct TcsAddress* l, const struct TcsAddress* r); /** @brief Check if the address is a wildcard (any) address. */ bool tcs_address_is_any(const struct TcsAddress* addr); /** @brief Check if the address is a link-local address. */ bool tcs_address_is_link_local(const struct TcsAddress* addr); /** @brief Check if the address is a loopback address. */ bool tcs_address_is_loopback(const struct TcsAddress* addr); /** @brief Check if the address is a multicast address. */ bool tcs_address_is_multicast(const struct TcsAddress* addr); /** @brief Check if the address is a broadcast address. */ bool tcs_address_is_broadcast(const struct TcsAddress* addr); #ifdef __cplusplus } #endif #endif #ifdef TINYCSOCKET_IMPLEMENTATION /**********************************/ /****** tinydatastructures.h ******/ /**********************************/ #ifndef TINYDATASTRUCTURES_H_ #define TINYDATASTRUCTURES_H_ #include #include #ifdef _MSC_VER #define TDS_UNUSED #else #define TDS_UNUSED __attribute__((unused)) #endif static inline int tds_ulist_create(void** data, size_t* count, size_t* capacity, size_t element_size); static inline int tds_ulist_destroy(void** data, size_t* count, size_t* capacity); static inline int tds_ulist_reserve(void** data, size_t* capacity, size_t element_size, size_t requested_capacity); static inline int tds_ulist_add(void** data, size_t* count, size_t* capacity, size_t element_size, void* add_data, size_t add_count); static inline int tds_ulist_remove(void** data, size_t* count, size_t* capacity, size_t element_size, size_t remove_from, size_t remove_count); static inline int tds_ulist_create(void** data, size_t* count, size_t* capacity, size_t element_size) { *data = NULL; *count = 0; *capacity = 0; if (element_size == 0) return -1; return tds_ulist_reserve(data, capacity, element_size, 1); } static inline int tds_ulist_destroy(void** data, size_t* count, size_t* capacity) { if (*data != NULL) { free(*data); *data = NULL; } *count = 0; *capacity = 0; return 0; } // TODO: move to reserve static inline size_t tds_ulist_best_capacity_fit(size_t old_capacity, size_t new_capacity) { const size_t MINIMUM_CAPACITY = 8; size_t c = MINIMUM_CAPACITY; while (c < new_capacity) c *= 2; // Hysteresis if (c * 2 == old_capacity) return old_capacity; return c; } static inline int tds_ulist_reserve(void** data, size_t* capacity, size_t element_size, size_t requested_capacity) { size_t new_capacity = tds_ulist_best_capacity_fit(*capacity, requested_capacity); if (new_capacity == *capacity) return 0; // UB protection for C23 and implemention defined protection before C23 (Should never happen) #ifndef NDEBUG if (new_capacity == 0) return -1; #endif void* new_data = realloc(*data, new_capacity * element_size); if (new_data == NULL) return -1; if (new_capacity > *capacity) { memset((char*)new_data + *capacity * element_size, 0, (new_capacity - *capacity) * element_size); } *data = new_data; *capacity = new_capacity; return 0; } static inline int tds_ulist_add(void** data, size_t* count, size_t* capacity, size_t element_size, void* add_data, size_t add_count) { if (*count + add_count > *capacity) { int reserve_sts = tds_ulist_reserve(data, capacity, element_size, *count + add_count); if (reserve_sts != 0) return reserve_sts; } memcpy((char*)*data + (*count * element_size), add_data, add_count * element_size); *count += add_count; return 0; } static inline int tds_ulist_remove(void** data, size_t* count, size_t* capacity, size_t element_size, size_t remove_from, size_t remove_count) { if (remove_from >= *count || remove_count == 0 || remove_from + remove_count > *count) return -1; void* dst = (char*)(*data) + (remove_from * element_size); void* src = (char*)(*data) + (*count - remove_count) * element_size; memmove(dst, src, element_size * remove_count); *count -= remove_count; if (*count < *capacity / 2) { int reserve_sts = tds_ulist_reserve(data, capacity, element_size, *count); if (reserve_sts != 0) return reserve_sts; } return 0; } #define TDS_ULIST_IMPL(TYPE, NAME) \ struct TdsUList_##NAME \ { \ TYPE* data; \ size_t count; \ size_t capacity; \ }; \ \ TDS_UNUSED static inline int tds_ulist_##NAME##_create(struct TdsUList_##NAME* ulist) \ { \ return tds_ulist_create((void**)&ulist->data, &ulist->count, &ulist->capacity, sizeof(TYPE)); \ } \ TDS_UNUSED static inline int tds_ulist_##NAME##_destroy(struct TdsUList_##NAME* ulist) \ { \ int sts = tds_ulist_destroy((void**)&ulist->data, &ulist->count, &ulist->capacity); \ memset(ulist, 0, sizeof(*ulist)); \ return sts; \ } \ TDS_UNUSED static inline int tds_ulist_##NAME##_add(struct TdsUList_##NAME* ulist, TYPE* data, size_t count) \ { \ return tds_ulist_add((void**)&ulist->data, &ulist->count, &ulist->capacity, sizeof(TYPE), (void*)data, count); \ } \ TDS_UNUSED static inline int tds_ulist_##NAME##_remove( \ struct TdsUList_##NAME* ulist, size_t remove_from, size_t remove_count) \ { \ return tds_ulist_remove( \ (void**)&ulist->data, &ulist->count, &ulist->capacity, sizeof(TYPE), remove_from, remove_count); \ } \ TDS_UNUSED static inline int tds_ulist_##NAME##_reserve(struct TdsUList_##NAME* ulist, size_t new_capacity) \ { \ return tds_ulist_reserve((void**)&ulist->data, &ulist->capacity, sizeof(TYPE), new_capacity); \ } // Tiny Data Structures Map Implementation static inline int tds_map_create(void** keys, void** values, size_t* count, size_t* capacity, size_t key_element_size, size_t value_element_size) { size_t key_capacity = 0; size_t key_count = 0; size_t value_capacity = 0; size_t value_count = 0; int key_sts = tds_ulist_create(keys, &key_count, &key_capacity, key_element_size); int value_sts = tds_ulist_create(values, &value_count, &value_capacity, value_element_size); if (key_sts != 0 || value_sts != 0) { tds_ulist_destroy(keys, &key_count, &key_capacity); tds_ulist_destroy(values, &value_count, &value_capacity); if (key_sts != 0) return key_sts; if (value_sts != 0) return value_sts; } if (key_capacity != value_capacity) { tds_ulist_destroy(keys, &key_count, &key_capacity); tds_ulist_destroy(values, &value_count, &value_capacity); return -1; } *capacity = key_capacity; *count = key_count; return 0; } static inline int tds_map_destroy(void** keys, void** values, size_t* count, size_t* capacity) { size_t key_capacity = 0; size_t key_count = 0; size_t value_capacity = 0; size_t value_count = 0; int key_sts = tds_ulist_destroy(keys, &key_count, &key_capacity); int value_sts = tds_ulist_destroy(values, &value_count, &value_capacity); *count = 0; *capacity = 0; if (key_sts != 0) return key_sts; if (value_sts != 0) return value_sts; return 0; } static inline int tds_map_add(void** keys, void** values, size_t* count, size_t* capacity, size_t key_element_size, size_t value_element_size, void* key_add, void* value_add) { size_t value_count = *count; size_t key_count = *count; size_t key_capacity = *capacity; size_t value_capacity = *capacity; int key_sts = tds_ulist_add(keys, &key_count, &key_capacity, key_element_size, key_add, 1); int value_sts = tds_ulist_add(values, &value_count, &value_capacity, value_element_size, value_add, 1); if (key_sts != 0 || value_sts != 0) { // TODO: fix invariant memory state. Restore memory capacity should work most of the time. // This is invariant is still non-fatal though, we may use more memory than needed. return -1; } if (key_capacity != value_capacity) return -1; *count += 1; *capacity = key_capacity; return 0; } static inline int tds_map_remove(void** keys, void** values, size_t* count, size_t* capacity, size_t key_element_size, size_t value_element_size, size_t index) { size_t value_count = *count; size_t key_count = *count; size_t key_capacity = *capacity; size_t value_capacity = *capacity; if (index >= *count) return -1; int key_sts = tds_ulist_remove(keys, &key_count, &key_capacity, key_element_size, index, 1); int value_sts = tds_ulist_remove(values, &value_count, &value_capacity, value_element_size, index, 1); if (key_sts != 0 || value_sts != 0) { // -2 indicates we are in a very bad situation and the data structure may be corrupted. // This should not be able to happen, with current implementation, but if it does, we need to be able to recover from it. return -2; } if (key_count != value_count) return -2; *count = key_count; *capacity = key_capacity; return 0; } #define TDS_MAP_IMPL(KEY_TYPE, VALUE_TYPE, NAME) \ \ struct TdsMap_##NAME \ { \ KEY_TYPE* keys; \ VALUE_TYPE* values; \ size_t count; \ size_t capacity; \ }; \ \ TDS_UNUSED static inline int tds_map_##NAME##_create(struct TdsMap_##NAME* map) \ { \ memset(map, 0, sizeof(struct TdsMap_##NAME)); \ return tds_map_create((void**)&map->keys, \ (void**)&map->values, \ &map->count, \ &map->capacity, \ sizeof(KEY_TYPE), \ sizeof(VALUE_TYPE)); \ } \ TDS_UNUSED static inline int tds_map_##NAME##_destroy(struct TdsMap_##NAME* map) \ { \ int sts = tds_map_destroy((void**)&map->keys, (void**)&map->values, &map->count, &map->capacity); \ if (sts != 0) \ return sts; \ memset(map, 0, sizeof(struct TdsMap_##NAME)); \ return 0; \ } \ TDS_UNUSED static inline int tds_map_##NAME##_add(struct TdsMap_##NAME* map, KEY_TYPE key, VALUE_TYPE value) \ { \ return tds_map_add((void**)&map->keys, \ (void**)&map->values, \ &map->count, \ &map->capacity, \ sizeof(KEY_TYPE), \ sizeof(VALUE_TYPE), \ &key, \ &value); \ } \ TDS_UNUSED static inline int tds_map_##NAME##_addp(struct TdsMap_##NAME* map, KEY_TYPE* key, VALUE_TYPE* value) \ { \ return tds_map_add((void**)&map->keys, \ (void**)&map->values, \ &map->count, \ &map->capacity, \ sizeof(KEY_TYPE), \ sizeof(VALUE_TYPE), \ key, \ value); \ } \ TDS_UNUSED static inline int tds_map_##NAME##_remove(struct TdsMap_##NAME* map, size_t index) \ { \ return tds_map_remove((void**)&map->keys, \ (void**)&map->values, \ &map->count, \ &map->capacity, \ sizeof(KEY_TYPE), \ sizeof(VALUE_TYPE), \ index); \ } #endif /**********************************/ /****** tinycsocket_posix.h *******/ /**********************************/ /* * Copyright 2018 Markus Lindelöw * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files(the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and / or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // Header only should not need other files #ifndef TINYCSOCKET_INTERNAL_H_ #include "tinycsocket_internal.h" #endif #ifdef TINYCSOCKET_USE_POSIX_IMPL #ifndef TINYDATASTRUCTURES_H_ #include "tinydatastructures.h" #endif #ifdef DO_WRAP #include "dbg_wrap.h" #endif #ifndef TCS_HAS_AF_PACKET #if defined(__linux__) #define TCS_HAS_AF_PACKET 1 #else #define TCS_HAS_AF_PACKET 0 #endif #endif #ifndef TCS_HAS_GETIFADDRS #if defined(__ANDROID__) #if __ANDROID_API__ >= 24 #define TCS_HAS_GETIFADDRS 1 #else #define TCS_HAS_GETIFADDRS 0 #endif #elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ defined(__NetBSD__) || defined(__CYGWIN__) #define TCS_HAS_GETIFADDRS 1 #else #define TCS_HAS_GETIFADDRS 0 #endif #endif #include #include // fcntl() for non-blocking #include #include // Flags for ifaddrs (?) #include // Protocols and custom return codes #include // IPPROTO_XXP #include // TCP_NODELAY #include // poll() #include // malloc()/free() #include // strcpy, memset #include // Flags for ifaddrs #ifdef __sun #include // SIOCGIFCONF on Solaris/illumos #endif #include // pretty much everything #include // POSIX.1 compatibility #include // struct iovec #include // close() #if TCS_HAS_GETIFADDRS #include // getifaddr() #endif #if TCS_HAS_AF_PACKET #include // sll_hatype (ethernet and not can or firewire etc.) #include // struct sockaddr_ll #endif #ifndef TDS_MAP_pollfd_pvoid #define TDS_MAP_pollfd_pvoid TDS_MAP_IMPL(struct pollfd, void*, poll) #endif struct TcsPool { union __backend { struct __poll { struct TdsMap_poll map; } poll; } backend; }; const TcsSocket TCS_SOCKET_INVALID = -1; const int TCS_WAIT_INF = -1; static long tcs_iov_max = 1024; // Default, updated by tcs_lib_init() via sysconf(_SC_IOV_MAX) // Addresses const uint32_t TCS_ADDRESS_ANY_IP4 = INADDR_ANY; const uint32_t TCS_ADDRESS_LOOPBACK_IP4 = INADDR_LOOPBACK; const uint32_t TCS_ADDRESS_BROADCAST_IP4 = INADDR_BROADCAST; const uint32_t TCS_ADDRESS_NONE_IP4 = INADDR_NONE; const struct TcsIp6Address TCS_ADDRESS_ANY_IP6 = {{0}}; const struct TcsIp6Address TCS_ADDRESS_LOOPBACK_IP6 = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}; // Type const int TCS_SOCK_STREAM = SOCK_STREAM; const int TCS_SOCK_DGRAM = SOCK_DGRAM; const int TCS_SOCK_RAW = SOCK_RAW; // Protocol const uint16_t TCS_PROTOCOL_IP_TCP = IPPROTO_TCP; const uint16_t TCS_PROTOCOL_IP_UDP = IPPROTO_UDP; // Flags const uint32_t TCS_AI_PASSIVE = AI_PASSIVE; // Recv flags const uint32_t TCS_MSG_PEEK = MSG_PEEK; const uint32_t TCS_MSG_OOB = MSG_OOB; const uint32_t TCS_MSG_WAITALL = MSG_WAITALL; // Send flags const uint32_t TCS_MSG_SENDALL = 0x80000000; // Backlog const int TCS_BACKLOG_MAX = SOMAXCONN; // Option levels const int TCS_SOL_SOCKET = SOL_SOCKET; const int TCS_SOL_IP = IPPROTO_IP; // Same as SOL_IP but crossplatform (BSD) // Socket options const int TCS_SO_TYPE = SO_TYPE; const int TCS_SO_BROADCAST = SO_BROADCAST; const int TCS_SO_KEEPALIVE = SO_KEEPALIVE; const int TCS_SO_LINGER = SO_LINGER; const int TCS_SO_REUSEADDR = SO_REUSEADDR; #ifdef SO_REUSEPORT const int TCS_SO_REUSEPORT = SO_REUSEPORT; #else const int TCS_SO_REUSEPORT = -1; #endif const int TCS_SO_RCVBUF = SO_RCVBUF; const int TCS_SO_RCVTIMEO = SO_RCVTIMEO; const int TCS_SO_SNDBUF = SO_SNDBUF; const int TCS_SO_OOBINLINE = SO_OOBINLINE; #ifdef SO_PRIORITY const int TCS_SO_PRIORITY = SO_PRIORITY; #else const int TCS_SO_PRIORITY = -1; #endif // IP options const int TCS_SO_IP_NODELAY = TCP_NODELAY; const int TCS_SO_IP_MEMBERSHIP_ADD = IP_ADD_MEMBERSHIP; const int TCS_SO_IP_MEMBERSHIP_DROP = IP_DROP_MEMBERSHIP; const int TCS_SO_IP_MULTICAST_LOOP = IP_MULTICAST_LOOP; #if TCS_HAS_AF_PACKET const int TCS_SO_PACKET_MEMBERSHIP_ADD = PACKET_ADD_MEMBERSHIP; const int TCS_SO_PACKET_MEMBERSHIP_DROP = PACKET_DROP_MEMBERSHIP; #else const int TCS_SO_PACKET_MEMBERSHIP_ADD = -1; const int TCS_SO_PACKET_MEMBERSHIP_DROP = -1; #endif // Default flags const int TCS_DEFAULT_SEND_FLAGS = MSG_NOSIGNAL; const int TCS_DEFAULT_RECV_FLAGS = 0; // ######## Internal Helpers ######## static TcsResult errno2retcode(int error_code) { switch (error_code) { case EAGAIN: #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN) case EWOULDBLOCK: #endif return TCS_ERROR_WOULD_BLOCK; case EINPROGRESS: return TCS_IN_PROGRESS; case EPERM: case EACCES: return TCS_ERROR_PERMISSION_DENIED; case ECONNREFUSED: return TCS_ERROR_CONNECTION_REFUSED; case ECONNRESET: return TCS_ERROR_CONNECTION_RESET; case ENOTCONN: return TCS_ERROR_NOT_CONNECTED; case ETIMEDOUT: return TCS_ERROR_TIMED_OUT; case ENETUNREACH: case EHOSTUNREACH: case ENETDOWN: return TCS_ERROR_NETWORK_UNREACHABLE; case EINVAL: return TCS_ERROR_INVALID_ARGUMENT; case EADDRINUSE: return TCS_ERROR_ADDRESS_IN_USE; case ENOPROTOOPT: return TCS_ERROR_NOT_SUPPORTED; case ENODEV: return TCS_ERROR_INVALID_ARGUMENT; case ENOMEM: return TCS_ERROR_MEMORY; case EAI_SOCKTYPE: return TCS_ERROR_NOT_IMPLEMENTED; default: return TCS_ERROR_UNKNOWN; } } static TcsResult family2native(const TcsAddressFamily family, sa_family_t* native_family) { if (native_family == NULL) return TCS_ERROR_INVALID_ARGUMENT; switch (family) { case TCS_AF_ANY: *native_family = AF_UNSPEC; return TCS_SUCCESS; case TCS_AF_IP4: *native_family = AF_INET; return TCS_SUCCESS; case TCS_AF_IP6: *native_family = AF_INET6; return TCS_SUCCESS; case TCS_AF_PACKET: #if TCS_HAS_AF_PACKET *native_family = AF_PACKET; return TCS_SUCCESS; #else return TCS_ERROR_NOT_IMPLEMENTED; #endif default: return TCS_ERROR_INVALID_ARGUMENT; } } static TcsResult sockaddr2native(const struct TcsAddress* tcs_address, struct sockaddr_storage* out_address, socklen_t* out_address_size) { if (tcs_address == NULL || out_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; memset(out_address, 0, sizeof(struct sockaddr_storage)); if (out_address_size != NULL) *out_address_size = 0; if (tcs_address->family == TCS_AF_ANY) { return TCS_ERROR_NOT_IMPLEMENTED; } else if (tcs_address->family == TCS_AF_IP4) { struct sockaddr_in* addr = (struct sockaddr_in*)out_address; addr->sin_family = (sa_family_t)AF_INET; addr->sin_port = (in_port_t)htons(tcs_address->data.ip4.port); addr->sin_addr.s_addr = (in_addr_t)htonl(tcs_address->data.ip4.address); if (out_address_size != NULL) *out_address_size = sizeof(struct sockaddr_in); return TCS_SUCCESS; } else if (tcs_address->family == TCS_AF_IP6) { struct sockaddr_in6* addr = (struct sockaddr_in6*)out_address; addr->sin6_family = (sa_family_t)AF_INET6; addr->sin6_port = (in_port_t)htons(tcs_address->data.ip6.port); memcpy(&addr->sin6_addr, tcs_address->data.ip6.address.bytes, 16); addr->sin6_scope_id = (uint32_t)tcs_address->data.ip6.scope_id; if (out_address_size != NULL) *out_address_size = sizeof(struct sockaddr_in6); return TCS_SUCCESS; } else if (tcs_address->family == TCS_AF_PACKET) { #if TCS_HAS_AF_PACKET struct sockaddr_ll* addr = (struct sockaddr_ll*)out_address; addr->sll_family = (sa_family_t)AF_PACKET; addr->sll_ifindex = (int)tcs_address->data.packet.interface_id; addr->sll_protocol = (uint16_t)htons(tcs_address->data.packet.protocol); addr->sll_halen = 6; // MAC address length memcpy(addr->sll_addr, tcs_address->data.packet.mac, 6); if (out_address_size != NULL) *out_address_size = sizeof(struct sockaddr_ll); return TCS_SUCCESS; #else return TCS_ERROR_NOT_IMPLEMENTED; #endif } return TCS_ERROR_NOT_IMPLEMENTED; } static TcsResult native2family(const sa_family_t native_family, TcsAddressFamily* family) { if (family == NULL) return TCS_ERROR_INVALID_ARGUMENT; switch (native_family) { case AF_UNSPEC: *family = TCS_AF_ANY; return TCS_SUCCESS; case AF_INET: *family = TCS_AF_IP4; return TCS_SUCCESS; case AF_INET6: *family = TCS_AF_IP6; return TCS_SUCCESS; #if TCS_HAS_AF_PACKET case AF_PACKET: *family = TCS_AF_PACKET; return TCS_SUCCESS; #endif default: return TCS_ERROR_NOT_IMPLEMENTED; } } static TcsResult native2sockaddr(const struct sockaddr* in_addr, struct TcsAddress* out_addr) { if (in_addr == NULL || out_addr == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (in_addr->sa_family == AF_INET) { // (const void*) Supresses false positive alignment warning, the creator of the sockaddr is responsible for the alignment. struct sockaddr_in const* addr = (struct sockaddr_in const*)(const void*)in_addr; out_addr->family = TCS_AF_IP4; out_addr->data.ip4.port = ntohs((uint16_t)addr->sin_port); out_addr->data.ip4.address = ntohl((uint32_t)addr->sin_addr.s_addr); } else if (in_addr->sa_family == AF_INET6) { struct sockaddr_in6 const* addr = (struct sockaddr_in6 const*)(const void*)in_addr; out_addr->family = TCS_AF_IP6; out_addr->data.ip6.port = ntohs((uint16_t)addr->sin6_port); memcpy(out_addr->data.ip6.address.bytes, &addr->sin6_addr, 16); out_addr->data.ip6.scope_id = (TcsInterfaceId)addr->sin6_scope_id; } #if TCS_HAS_AF_PACKET else if (in_addr->sa_family == AF_PACKET) { struct sockaddr_ll const* addr = (struct sockaddr_ll const*)(const void*)in_addr; if (addr->sll_family != AF_PACKET) return TCS_ERROR_NOT_IMPLEMENTED; if (addr->sll_hatype != ARPHRD_ETHER && addr->sll_hatype != ARPHRD_LOOPBACK) return TCS_ERROR_NOT_IMPLEMENTED; // Not ethernet or loopback if (addr->sll_halen > 6) return TCS_ERROR_INVALID_ARGUMENT; if (addr->sll_ifindex < 0) return TCS_ERROR_INVALID_ARGUMENT; out_addr->family = TCS_AF_PACKET; out_addr->data.packet.interface_id = (unsigned int)addr->sll_ifindex; memcpy(out_addr->data.packet.mac, addr->sll_addr, 6); out_addr->data.packet.protocol = ntohs((uint16_t)addr->sll_protocol); } #endif else if (in_addr->sa_family == AF_UNSPEC) { return TCS_ERROR_INVALID_ARGUMENT; } else { return TCS_ERROR_NOT_IMPLEMENTED; } return TCS_SUCCESS; } // ######## Library Management ######## TcsResult tcs_lib_init(void) { long iov_max = sysconf(_SC_IOV_MAX); if (iov_max > 0) tcs_iov_max = iov_max; return TCS_SUCCESS; } TcsResult tcs_lib_free(void) { // Not needed for posix return TCS_SUCCESS; } // ######## Socket Creation ######## TcsResult tcs_socket(TcsSocket* socket_ctx, TcsAddressFamily family, int type, int protocol) { if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; sa_family_t native_family; TcsResult sts = family2native(family, &native_family); if (sts != TCS_SUCCESS) return sts; #if TCS_HAS_AF_PACKET int native_protocol = (native_family == AF_PACKET) ? (int)htons((uint16_t)protocol) : protocol; #else int native_protocol = protocol; #endif *socket_ctx = socket(native_family, type, native_protocol); if (*socket_ctx != -1) // Same as TCS_NULLSOCKET return TCS_SUCCESS; else return errno2retcode(errno); } // tcs_socket_tcp() is defined in tinycsocket_common.c // tcs_socket_tcp_str() is defined in tinycsocket_common.c // tcs_socket_udp() is defined in tinycsocket_common.c // tcs_socket_udp_str() is defined in tinycsocket_common.c // tcs_socket_packet() is defined in tinycsocket_common.c // tcs_socket_packet_str() is defined in tinycsocket_common.c TcsResult tcs_close(TcsSocket* socket_ctx) { if (socket_ctx == NULL || *socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (close(*socket_ctx) == 0) { *socket_ctx = TCS_SOCKET_INVALID; return TCS_SUCCESS; } else { return errno2retcode(errno); } } // ######## Socket Operations ######## TcsResult tcs_bind(TcsSocket socket_ctx, const struct TcsAddress* address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (address == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct sockaddr_storage native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); socklen_t sockaddr_size = 0; TcsResult convert_address_status = sockaddr2native(address, &native_sockaddr, &sockaddr_size); if (convert_address_status != TCS_SUCCESS) return convert_address_status; if (bind(socket_ctx, (struct sockaddr*)&native_sockaddr, sockaddr_size) != -1) return TCS_SUCCESS; else return errno2retcode(errno); } TcsResult tcs_connect(TcsSocket socket_ctx, const struct TcsAddress* address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (address == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct sockaddr_storage native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); socklen_t sockaddr_size = 0; TcsResult convert_address_status = sockaddr2native(address, &native_sockaddr, &sockaddr_size); if (convert_address_status != TCS_SUCCESS) return convert_address_status; if (connect(socket_ctx, (const struct sockaddr*)&native_sockaddr, sockaddr_size) == 0) return TCS_SUCCESS; else return errno2retcode(errno); } // tcs_connect_str() is defined in tinycsocket_common.c TcsResult tcs_listen(TcsSocket socket_ctx, int backlog) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (backlog < 1 || backlog > TCS_BACKLOG_MAX) backlog = TCS_BACKLOG_MAX; if (listen(socket_ctx, backlog) == 0) return TCS_SUCCESS; else return errno2retcode(errno); } TcsResult tcs_accept(TcsSocket socket_ctx, TcsSocket* child_socket_ctx, struct TcsAddress* address) { if (socket_ctx == TCS_SOCKET_INVALID || child_socket_ctx == NULL || *child_socket_ctx != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (address != NULL) *address = TCS_ADDRESS_NONE; struct sockaddr_storage native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); socklen_t sockaddr_size = sizeof native_sockaddr; *child_socket_ctx = accept(socket_ctx, (struct sockaddr*)&native_sockaddr, &sockaddr_size); if (*child_socket_ctx != -1) { if (address != NULL) { TcsResult convert_address_status = native2sockaddr((struct sockaddr*)&native_sockaddr, address); if (convert_address_status != TCS_SUCCESS) return convert_address_status; } return TCS_SUCCESS; } else { *child_socket_ctx = TCS_SOCKET_INVALID; return errno2retcode(errno); } } TcsResult tcs_shutdown(TcsSocket socket_ctx, TcsSocketDirection direction) { const int LUT[] = {SHUT_RD, SHUT_WR, SHUT_RDWR}; if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (direction < 0 || direction > 2) return TCS_ERROR_INVALID_ARGUMENT; const int how = LUT[direction]; if (shutdown(socket_ctx, how) == 0) return TCS_SUCCESS; else return errno2retcode(errno); } // ######## Data Transfer ######## TcsResult tcs_send(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size, uint32_t flags, size_t* bytes_sent) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (bytes_sent != NULL) *bytes_sent = 0; if (buffer == NULL || buffer_size == 0) return TCS_ERROR_INVALID_ARGUMENT; // Send all if (flags & TCS_MSG_SENDALL) { uint32_t new_flags = flags & ~TCS_MSG_SENDALL; // For recursive call size_t left = buffer_size; const uint8_t* iterator = buffer; while (left > 0) { size_t sent = 0; TcsResult sts = tcs_send(socket_ctx, iterator, left, new_flags, &sent); if (bytes_sent != NULL) *bytes_sent += sent; if (sts != TCS_SUCCESS) return sts; left -= sent; iterator += sent; } return TCS_SUCCESS; } else // Send { ssize_t send_status = send(socket_ctx, (const char*)buffer, buffer_size, TCS_DEFAULT_SEND_FLAGS | (int)flags); if (send_status >= 0) { if (bytes_sent != NULL) *bytes_sent = (size_t)send_status; return TCS_SUCCESS; } else { if (bytes_sent != NULL) *bytes_sent = 0; return errno2retcode(errno); } } } TcsResult tcs_send_to(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size, uint32_t flags, const struct TcsAddress* destination_address, size_t* bytes_sent) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (buffer == NULL && buffer_size > 0) return TCS_ERROR_INVALID_ARGUMENT; if (destination_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (flags & TCS_MSG_SENDALL) return TCS_ERROR_NOT_IMPLEMENTED; struct sockaddr_storage native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); socklen_t sockaddr_size = 0; TcsResult convert_addr_status = sockaddr2native(destination_address, &native_sockaddr, &sockaddr_size); if (convert_addr_status != TCS_SUCCESS) return convert_addr_status; ssize_t sendto_status = sendto(socket_ctx, (const char*)buffer, buffer_size, TCS_DEFAULT_SEND_FLAGS | (int)flags, (const struct sockaddr*)&native_sockaddr, sockaddr_size); if (sendto_status >= 0) { if (bytes_sent != NULL) *bytes_sent = (size_t)sendto_status; return TCS_SUCCESS; } else { if (bytes_sent != NULL) *bytes_sent = 0; return errno2retcode(errno); } } TcsResult tcs_sendv(TcsSocket socket_ctx, const struct TcsBuffer* buffers, size_t buffer_count, uint32_t flags, size_t* bytes_sent) { if (socket_ctx == TCS_SOCKET_INVALID || buffers == NULL || buffer_count == 0) return TCS_ERROR_INVALID_ARGUMENT; if (flags & TCS_MSG_SENDALL) return TCS_ERROR_NOT_IMPLEMENTED; if (buffer_count > (size_t)tcs_iov_max) return TCS_ERROR_INVALID_ARGUMENT; struct iovec stack_iovec[TCS_SENDV_STACK_MAX]; struct iovec* my_iovec = stack_iovec; struct iovec* heap_iovec = NULL; if (buffer_count > TCS_SENDV_STACK_MAX) { heap_iovec = (struct iovec*)malloc(sizeof(struct iovec) * buffer_count); if (heap_iovec == NULL) return TCS_ERROR_MEMORY; my_iovec = heap_iovec; } for (size_t i = 0; i < buffer_count; i++) { if (buffers[i].data == NULL && buffers[i].size > 0) { free(heap_iovec); return TCS_ERROR_INVALID_ARGUMENT; } // We know that sendmsg() does not modify the data, so we can safely cast away the const here. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" my_iovec[i].iov_base = (void*)buffers[i].data; #pragma GCC diagnostic pop my_iovec[i].iov_len = buffers[i].size; } struct msghdr msg; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = my_iovec; // msg_iovlen type varies across platforms (int on POSIX, size_t on glibc). // buffer_count is already validated against tcs_iov_max above. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" msg.msg_iovlen = buffer_count; #pragma GCC diagnostic pop msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; ssize_t ret = 0; ret = sendmsg(socket_ctx, &msg, TCS_DEFAULT_SEND_FLAGS | (int)flags); free(heap_iovec); if (ret >= 0) { if (bytes_sent != NULL) *bytes_sent = (size_t)ret; return TCS_SUCCESS; } else { if (bytes_sent != NULL) *bytes_sent = 0; return errno2retcode(errno); } } // tcs_send_netstring() is defined in tinycsocket_common.c TcsResult tcs_receive(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, uint32_t flags, size_t* bytes_received) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (buffer == NULL && buffer_size > 0) return TCS_ERROR_INVALID_ARGUMENT; ssize_t recv_status = recv(socket_ctx, (char*)buffer, buffer_size, TCS_DEFAULT_RECV_FLAGS | (int)flags); if (recv_status > 0) { if (bytes_received != NULL) *bytes_received = (size_t)recv_status; return TCS_SUCCESS; } else if (recv_status == 0) { if (bytes_received != NULL) *bytes_received = 0; int sock_type = 0; if (tcs_opt_type_get(socket_ctx, &sock_type) == TCS_SUCCESS && sock_type == TCS_SOCK_STREAM) return TCS_SHUTDOWN; return TCS_SUCCESS; } else { if (bytes_received != NULL) *bytes_received = 0; #if (EAGAIN == EWOULDBLOCK) if (errno == EAGAIN) { bool is_nonblocking = false; int fcntl_flags = fcntl(socket_ctx, F_GETFL, 0); if (fcntl_flags == -1) return errno2retcode(errno); if (fcntl_flags & O_NONBLOCK) is_nonblocking = true; if (is_nonblocking) return TCS_ERROR_WOULD_BLOCK; else return TCS_ERROR_TIMED_OUT; } #endif return errno2retcode(errno); } } TcsResult tcs_receive_from(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, uint32_t flags, struct TcsAddress* source_address, size_t* bytes_received) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (buffer == NULL && buffer_size > 0) return TCS_ERROR_INVALID_ARGUMENT; struct sockaddr_storage native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); socklen_t addrlen = sizeof native_sockaddr; ssize_t recvfrom_status = recvfrom(socket_ctx, (char*)buffer, buffer_size, TCS_DEFAULT_RECV_FLAGS | (int)flags, (struct sockaddr*)&native_sockaddr, &addrlen); if (recvfrom_status > 0) { if (bytes_received != NULL) *bytes_received = (size_t)recvfrom_status; if (source_address != NULL) return native2sockaddr((struct sockaddr*)&native_sockaddr, source_address); return TCS_SUCCESS; } else if (recvfrom_status == 0) { if (bytes_received != NULL) *bytes_received = 0; int sock_type = 0; if (tcs_opt_type_get(socket_ctx, &sock_type) == TCS_SUCCESS && sock_type == TCS_SOCK_STREAM) return TCS_SHUTDOWN; return TCS_SUCCESS; } else { if (bytes_received != NULL) *bytes_received = 0; return errno2retcode(errno); } } // tcs_receive_line() is defined in tinycsocket_common.c // tcs_receive_netstring() is defined in tinycsocket_common.c // ######## Socket Pooling ######## TcsResult tcs_pool_create(struct TcsPool** pool) { if (pool == NULL || *pool != NULL) return TCS_ERROR_INVALID_ARGUMENT; *pool = (struct TcsPool*)malloc(sizeof(struct TcsPool)); if (*pool == NULL) return TCS_ERROR_MEMORY; memset(*pool, 0, sizeof(struct TcsPool)); if (tds_map_poll_create(&(*pool)->backend.poll.map) != 0) { free(*pool); *pool = NULL; return TCS_ERROR_MEMORY; } return TCS_SUCCESS; } TcsResult tcs_pool_destroy(struct TcsPool** pool) { if (pool == NULL || *pool == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (tds_map_poll_destroy(&(*pool)->backend.poll.map) != 0) { // Should not happen, but if it does, we may leak memory. // We can not do anything about it. return TCS_ERROR_MEMORY; } free(*pool); *pool = NULL; return TCS_SUCCESS; } TcsResult tcs_pool_add(struct TcsPool* pool, TcsSocket socket_ctx, void* user_data, bool poll_can_read, bool poll_can_write, bool poll_error) { if (pool == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; // todo(markusl): Add more events that is input and output events short ev = 0; if (poll_can_read) ev |= POLLIN; if (poll_can_write) ev |= POLLOUT; if (poll_error) ev |= POLLERR; struct pollfd pfd; pfd.fd = socket_ctx; pfd.events = ev; pfd.revents = 0; if (tds_map_poll_addp(&pool->backend.poll.map, &pfd, &user_data) != 0) return TCS_ERROR_MEMORY; return TCS_SUCCESS; } TcsResult tcs_pool_remove(struct TcsPool* pool, TcsSocket socket_ctx) { if (pool == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; struct TdsMap_poll const* map = &pool->backend.poll.map; bool found = false; for (size_t i = 0; i < map->count; ++i) { if (socket_ctx == map->keys[i].fd) { if (tds_map_poll_remove(&pool->backend.poll.map, i) != 0) return TCS_ERROR_MEMORY; found = true; break; } } if (!found) return TCS_ERROR_INVALID_ARGUMENT; return TCS_SUCCESS; } TcsResult tcs_pool_poll(struct TcsPool* pool, struct TcsPollEvent* events, size_t events_count, size_t* events_populated, int timeout_ms) { if (pool == NULL || events == NULL || events_populated == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (timeout_ms < 0 && timeout_ms != TCS_WAIT_INF) return TCS_ERROR_INVALID_ARGUMENT; struct TdsMap_poll* map = &pool->backend.poll.map; int poll_ret = poll(map->keys, map->count, timeout_ms); *events_populated = 0; if (poll_ret < 0) { return errno2retcode(errno); } if ((size_t)poll_ret > map->count) { return TCS_ERROR_UNKNOWN; // Corruption } int fill_max = poll_ret > (int)events_count ? (int)events_count : poll_ret; // min(ret, events_count) int filled = 0; for (size_t i = 0; filled < fill_max; ++i) { if (i >= map->count) return TCS_ERROR_UNKNOWN; if (map->keys[i].revents != 0) { events[filled].socket = map->keys[i].fd; events[filled].user_data = map->values[i]; events[filled].can_read = map->keys[i].revents & POLLIN; events[filled].can_write = map->keys[i].revents & POLLOUT; if (map->keys[i].revents & (POLLERR | POLLHUP)) { int so_error = 0; socklen_t so_error_size = sizeof(so_error); TcsResult fallback = (map->keys[i].revents & POLLERR) ? TCS_ERROR_UNKNOWN : TCS_ERROR_SOCKET_CLOSED; if (getsockopt(map->keys[i].fd, SOL_SOCKET, SO_ERROR, &so_error, &so_error_size) != 0) events[filled].error = errno2retcode(errno); else events[filled].error = so_error != 0 ? errno2retcode(so_error) : fallback; } else { events[filled].error = TCS_SUCCESS; } map->keys[i].revents = 0; // Reset revents ++filled; } } *events_populated = (size_t)filled; if (poll_ret == 0) return TCS_ERROR_TIMED_OUT; return TCS_SUCCESS; } // ######## Socket Options ######## TcsResult tcs_opt_set(TcsSocket socket_ctx, int32_t level, int32_t option_name, const void* option_value, size_t option_size) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (setsockopt(socket_ctx, (int)level, (int)option_name, (const char*)option_value, (socklen_t)option_size) == 0) return TCS_SUCCESS; else return errno2retcode(errno); } TcsResult tcs_opt_get(TcsSocket socket_ctx, int32_t level, int32_t option_name, void* option_value, size_t* option_size) { if (socket_ctx == TCS_SOCKET_INVALID || option_value == NULL || option_size == NULL) return TCS_ERROR_INVALID_ARGUMENT; socklen_t optlen = (socklen_t)*option_size; if (getsockopt(socket_ctx, (int)level, (int)option_name, (void*)option_value, &optlen) == 0) { *option_size = (size_t)optlen; // Linux sets the buffer size to the doubled because of internal use and returns the full doubled size including internal part #ifdef __linux__ if (option_name == TCS_SO_RCVBUF || option_name == TCS_SO_SNDBUF) { *(unsigned int*)option_value /= 2; } #endif return TCS_SUCCESS; } else { return errno2retcode(errno); } } // tcs_opt_broadcast_set() is defined in tinycsocket_common.c // tcs_opt_broadcast_get() is defined in tinycsocket_common.c // tcs_opt_keep_alive_set() is defined in tinycsocket_common.c // tcs_opt_keep_alive_get() is defined in tinycsocket_common.c // tcs_opt_reuse_address_set() is defined in tinycsocket_common.c TcsResult tcs_opt_reuse_address_set(TcsSocket socket_ctx, bool do_allow_reuse_address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; int b = do_allow_reuse_address ? 1 : 0; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_REUSEADDR, &b, sizeof(b)); } TcsResult tcs_opt_reuse_address_get(TcsSocket socket_ctx, bool* is_reuse_address_allowed) { if (socket_ctx == TCS_SOCKET_INVALID || is_reuse_address_allowed == NULL) return TCS_ERROR_INVALID_ARGUMENT; int b = 0; size_t s = sizeof(b); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_REUSEADDR, &b, &s); *is_reuse_address_allowed = b; return sts; } TcsResult tcs_opt_reuse_port_set(TcsSocket socket_ctx, bool do_allow_reuse_port) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; int b = do_allow_reuse_port ? 1 : 0; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_REUSEPORT, &b, sizeof(b)); } TcsResult tcs_opt_reuse_port_get(TcsSocket socket_ctx, bool* is_reuse_port_allowed) { if (socket_ctx == TCS_SOCKET_INVALID || is_reuse_port_allowed == NULL) return TCS_ERROR_INVALID_ARGUMENT; int b = 0; size_t s = sizeof(b); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_REUSEPORT, &b, &s); *is_reuse_port_allowed = b; return sts; } // tcs_opt_send_buffer_size_set() is defined in tinycsocket_common.c // tcs_opt_send_buffer_size_get() is defined in tinycsocket_common.c // tcs_opt_receive_buffer_size_set() is defined in tinycsocket_common.c // tcs_opt_receive_buffer_size_get() is defined in tinycsocket_common.c TcsResult tcs_opt_receive_timeout_set(TcsSocket socket_ctx, int timeout_ms) { if (socket_ctx == TCS_SOCKET_INVALID || timeout_ms < 0) return TCS_ERROR_INVALID_ARGUMENT; struct timeval tv; tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_RCVTIMEO, &tv, sizeof(tv)); } TcsResult tcs_opt_receive_timeout_get(TcsSocket socket_ctx, int* timeout_ms) { if (socket_ctx == TCS_SOCKET_INVALID || timeout_ms == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct timeval tv = {0, 0}; size_t tv_size = sizeof(tv); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_RCVTIMEO, &tv, &tv_size); if (sts == TCS_SUCCESS) { int c = 0; c += (int)tv.tv_sec * 1000; c += (int)tv.tv_usec / 1000; *timeout_ms = c; } return sts; } TcsResult tcs_opt_linger_set(TcsSocket socket_ctx, bool do_linger, int timeout_seconds) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; struct linger l = {(u_short)do_linger, (u_short)timeout_seconds}; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_LINGER, &l, sizeof(l)); } TcsResult tcs_opt_linger_get(TcsSocket socket_ctx, bool* do_linger, int* timeout_seconds) { if (socket_ctx == TCS_SOCKET_INVALID || (do_linger == NULL && timeout_seconds == NULL)) return TCS_ERROR_INVALID_ARGUMENT; struct linger l = {0, 0}; size_t l_size = sizeof(l); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_LINGER, &l, &l_size); if (sts == TCS_SUCCESS) { if (do_linger) *do_linger = l.l_onoff; if (timeout_seconds) *timeout_seconds = l.l_linger; } return sts; } // tcs_opt_ip_no_delay_set() is defined in tinycsocket_common.c // tcs_opt_ip_no_delay_get() is defined in tinycsocket_common.c // tcs_opt_out_of_band_inline_set() is defined in tinycsocket_common.c // tcs_opt_out_of_band_inline_get() is defined in tinycsocket_common.c // tcs_opt_priority_set() is defined in tinycsocket_common.c // tcs_opt_priority_get() is defined in tinycsocket_common.c TcsResult tcs_opt_nonblocking_set(TcsSocket socket_ctx, bool do_non_blocking) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; int flags = fcntl(socket_ctx, F_GETFL, 0); if (flags == -1) return errno2retcode(errno); if (do_non_blocking) flags |= O_NONBLOCK; /* sätt biten */ else flags &= ~O_NONBLOCK; /* rensa biten */ if (fcntl(socket_ctx, F_SETFL, flags) == -1) return errno2retcode(errno); return TCS_SUCCESS; } TcsResult tcs_opt_nonblocking_get(TcsSocket socket_ctx, bool* is_non_blocking) { if (socket_ctx == TCS_SOCKET_INVALID || is_non_blocking == NULL) return TCS_ERROR_INVALID_ARGUMENT; int flags = fcntl(socket_ctx, F_GETFL, 0); if (flags == -1) return errno2retcode(errno); *is_non_blocking = (flags & O_NONBLOCK) != 0; return TCS_SUCCESS; } TcsResult tcs_opt_membership_add(TcsSocket socket_ctx, const struct TcsAddress* multicast_address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct TcsAddress local_address = TCS_ADDRESS_NONE; TcsResult sts = tcs_address_socket_local(socket_ctx, &local_address); if (sts != TCS_SUCCESS) return sts; if (local_address.family != multicast_address->family) return TCS_ERROR_INVALID_ARGUMENT; return tcs_opt_membership_add_to(socket_ctx, &local_address, multicast_address); } // tcs_opt_membership_add_str() is defined in tinycsocket_common.c TcsResult tcs_opt_membership_add_to(TcsSocket socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* multicast_address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (local_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (local_address->family != multicast_address->family) return TCS_ERROR_INVALID_ARGUMENT; struct sockaddr_storage local_address_native; memset(&local_address_native, 0, sizeof(struct sockaddr_storage)); socklen_t local_address_native_size = 0; TcsResult sts_la2n = sockaddr2native(local_address, &local_address_native, &local_address_native_size); if (sts_la2n != TCS_SUCCESS) return sts_la2n; struct sockaddr_storage multicast_address_native; memset(&multicast_address_native, 0, sizeof(struct sockaddr_storage)); socklen_t multicast_address_native_size = 0; TcsResult sts_ma2n = sockaddr2native(multicast_address, &multicast_address_native, &multicast_address_native_size); if (sts_ma2n != TCS_SUCCESS) return sts_ma2n; if (multicast_address->family == TCS_AF_IP4) { const struct sockaddr_in* address_native_local_p = (struct sockaddr_in*)&local_address_native; const struct sockaddr_in* address_native_multicast_p = (struct sockaddr_in*)&multicast_address_native; struct ip_mreq mreq; memset(&mreq, 0, sizeof mreq); mreq.imr_interface.s_addr = address_native_local_p->sin_addr.s_addr; mreq.imr_multiaddr.s_addr = address_native_multicast_p->sin_addr.s_addr; TcsResult sts_opt = tcs_opt_set(socket_ctx, TCS_SOL_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); if (sts_opt != TCS_SUCCESS) return sts_opt; return TCS_SUCCESS; } else if (multicast_address->family == TCS_AF_IP6) { struct ipv6_mreq mreq6; memset(&mreq6, 0, sizeof mreq6); memcpy(&mreq6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); mreq6.ipv6mr_interface = (unsigned int)local_address->data.ip6.scope_id; TcsResult sts_opt = tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6)); if (sts_opt != TCS_SUCCESS) return sts_opt; return TCS_SUCCESS; } else if (multicast_address->family == TCS_AF_PACKET) { #if TCS_HAS_AF_PACKET const struct sockaddr_ll* address_native_local_p = (struct sockaddr_ll*)&local_address_native; const struct sockaddr_ll* address_native_multicast_p = (struct sockaddr_ll*)&multicast_address_native; struct packet_mreq mreq; memset(&mreq, 0, sizeof mreq); mreq.mr_ifindex = address_native_local_p->sll_ifindex; mreq.mr_type = PACKET_MR_MULTICAST; memcpy(mreq.mr_address, address_native_multicast_p->sll_addr, ETH_ALEN); mreq.mr_alen = ETH_ALEN; TcsResult sts_opt = tcs_opt_set(socket_ctx, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); if (sts_opt != TCS_SUCCESS) return sts_opt; return TCS_SUCCESS; #else return TCS_ERROR_NOT_IMPLEMENTED; #endif } return TCS_ERROR_NOT_IMPLEMENTED; } // tcs_opt_membership_drop_str() is defined in tinycsocket_common.c TcsResult tcs_opt_membership_drop(TcsSocket socket_ctx, const struct TcsAddress* multicast_address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct TcsAddress local_address = TCS_ADDRESS_NONE; TcsResult sts = tcs_address_socket_local(socket_ctx, &local_address); if (sts != TCS_SUCCESS) return sts; if (local_address.family != multicast_address->family) return TCS_ERROR_INVALID_ARGUMENT; return tcs_opt_membership_drop_from(socket_ctx, &local_address, multicast_address); } TcsResult tcs_opt_membership_drop_from(TcsSocket socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* multicast_address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (local_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (local_address->family != multicast_address->family) return TCS_ERROR_INVALID_ARGUMENT; struct sockaddr_storage local_address_native; memset(&local_address_native, 0, sizeof(struct sockaddr_storage)); socklen_t local_address_native_size = 0; TcsResult sts_la2n = sockaddr2native(local_address, &local_address_native, &local_address_native_size); if (sts_la2n != TCS_SUCCESS) return sts_la2n; struct sockaddr_storage multicast_address_native; memset(&multicast_address_native, 0, sizeof(struct sockaddr_storage)); socklen_t multicast_address_native_size = 0; TcsResult sts_ma2n = sockaddr2native(multicast_address, &multicast_address_native, &multicast_address_native_size); if (sts_ma2n != TCS_SUCCESS) return sts_ma2n; if (multicast_address->family == TCS_AF_IP4) { const struct sockaddr_in* address_native_local_p = (struct sockaddr_in*)&local_address_native; const struct sockaddr_in* address_native_multicast_p = (struct sockaddr_in*)&multicast_address_native; struct ip_mreq mreq; memset(&mreq, 0, sizeof mreq); mreq.imr_interface.s_addr = address_native_local_p->sin_addr.s_addr; mreq.imr_multiaddr.s_addr = address_native_multicast_p->sin_addr.s_addr; TcsResult sts_opt = tcs_opt_set(socket_ctx, TCS_SOL_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); if (sts_opt != TCS_SUCCESS) return sts_opt; return TCS_SUCCESS; } else if (multicast_address->family == TCS_AF_IP6) { struct ipv6_mreq mreq6; memset(&mreq6, 0, sizeof mreq6); memcpy(&mreq6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); mreq6.ipv6mr_interface = (unsigned int)local_address->data.ip6.scope_id; TcsResult sts_opt = tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq6, sizeof(mreq6)); if (sts_opt != TCS_SUCCESS) return sts_opt; return TCS_SUCCESS; } else if (multicast_address->family == TCS_AF_PACKET) { #if TCS_HAS_AF_PACKET const struct sockaddr_ll* address_native_local_p = (struct sockaddr_ll*)&local_address_native; const struct sockaddr_ll* address_native_multicast_p = (struct sockaddr_ll*)&multicast_address_native; struct packet_mreq mreq; memset(&mreq, 0, sizeof mreq); mreq.mr_ifindex = address_native_local_p->sll_ifindex; mreq.mr_type = PACKET_MR_MULTICAST; memcpy(mreq.mr_address, address_native_multicast_p->sll_addr, ETH_ALEN); mreq.mr_alen = ETH_ALEN; TcsResult sts_opt = tcs_opt_set(socket_ctx, SOL_PACKET, PACKET_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); if (sts_opt != TCS_SUCCESS) return sts_opt; return TCS_SUCCESS; #else return TCS_ERROR_NOT_IMPLEMENTED; #endif } return TCS_ERROR_NOT_IMPLEMENTED; } TcsResult tcs_opt_multicast_interface_set(TcsSocket socket_ctx, const struct TcsAddress* local_address) { if (socket_ctx == TCS_SOCKET_INVALID || local_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (local_address->family == TCS_AF_IP4) { struct in_addr iface; iface.s_addr = htonl(local_address->data.ip4.address); return tcs_opt_set(socket_ctx, TCS_SOL_IP, IP_MULTICAST_IF, &iface, sizeof(iface)); } else if (local_address->family == TCS_AF_IP6) { unsigned int idx = (unsigned int)local_address->data.ip6.scope_id; return tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_MULTICAST_IF, &idx, sizeof(idx)); } return TCS_ERROR_INVALID_ARGUMENT; } TcsResult tcs_opt_multicast_loop_set(TcsSocket socket_ctx, bool do_loopback) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; unsigned char val = do_loopback ? 1 : 0; return tcs_opt_set(socket_ctx, TCS_SOL_IP, IP_MULTICAST_LOOP, &val, sizeof(val)); } TcsResult tcs_opt_multicast_loop_get(TcsSocket socket_ctx, bool* is_loopback) { if (socket_ctx == TCS_SOCKET_INVALID || is_loopback == NULL) return TCS_ERROR_INVALID_ARGUMENT; unsigned char val = 0; size_t s = sizeof(val); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_IP, IP_MULTICAST_LOOP, &val, &s); *is_loopback = val; return sts; } // ######## Address and Interface Utilities ######## #if TCS_HAS_GETIFADDRS TcsResult tcs_interface_list(struct TcsInterface* found_interfaces, size_t interfaces_length, size_t* interfaces_populated) { if (found_interfaces == NULL && interfaces_populated == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (found_interfaces == NULL && interfaces_length != 0) return TCS_ERROR_INVALID_ARGUMENT; if (interfaces_populated != NULL) *interfaces_populated = 0; struct if_nameindex* interfaces = if_nameindex(); if (interfaces == NULL) return errno2retcode(errno); for (size_t i = 0; interfaces[i].if_index != 0; ++i) { if (found_interfaces != NULL && i < interfaces_length) { strncpy(found_interfaces[i].name, interfaces[i].if_name, TCS_INTERFACE_NAME_SIZE - 1); found_interfaces[i].name[TCS_INTERFACE_NAME_SIZE - 1] = '\0'; found_interfaces[i].id = interfaces[i].if_index; } if (interfaces_populated != NULL) *interfaces_populated += 1; } if_freenameindex(interfaces); return TCS_SUCCESS; } #else // Fallback using ioctl(SIOCGIFCONF) for systems without if_nameindex (e.g. Android < API 24) TcsResult tcs_interface_list(struct TcsInterface* found_interfaces, size_t interfaces_length, size_t* interfaces_populated) { if (found_interfaces == NULL && interfaces_populated == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (found_interfaces == NULL && interfaces_length != 0) return TCS_ERROR_INVALID_ARGUMENT; if (interfaces_populated != NULL) *interfaces_populated = 0; int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) return errno2retcode(errno); // Start with stack buffer, grow on heap if truncated char stack_buf[512]; char* buf = stack_buf; int buf_len = (int)sizeof(stack_buf); struct ifconf ifc; for (;;) { ifc.ifc_len = buf_len; ifc.ifc_buf = buf; if (ioctl(fd, SIOCGIFCONF, &ifc) != 0) { if (buf != stack_buf) free(buf); close(fd); return errno2retcode(errno); } if (ifc.ifc_len < buf_len) break; // If caller only wants N results (not counting), don't grow beyond what's needed if (found_interfaces != NULL && interfaces_populated == NULL && (size_t)ifc.ifc_len / sizeof(struct ifreq) >= interfaces_length) break; buf_len *= 2; if (buf != stack_buf) free(buf); buf = (char*)malloc((size_t)buf_len); if (buf == NULL) { close(fd); return TCS_ERROR_MEMORY; } } size_t count = 0; int offset = 0; while (offset < ifc.ifc_len) { struct ifreq* ifr = (struct ifreq*)(buf + offset); int entry_len = (int)sizeof(struct ifreq); if (found_interfaces != NULL && count < interfaces_length) { strncpy(found_interfaces[count].name, ifr->ifr_name, TCS_INTERFACE_NAME_SIZE - 1); found_interfaces[count].name[TCS_INTERFACE_NAME_SIZE - 1] = '\0'; found_interfaces[count].id = (unsigned int)(count + 1); } count++; offset += entry_len; } if (interfaces_populated != NULL) *interfaces_populated = count; if (buf != stack_buf) free(buf); close(fd); return TCS_SUCCESS; } #endif TcsResult tcs_address_resolve(const char* hostname, TcsAddressFamily address_family, struct TcsAddress found_addresses[], size_t found_addresses_length, size_t* no_of_found_addresses) { if (hostname == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (found_addresses == NULL && no_of_found_addresses == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (no_of_found_addresses != NULL) *no_of_found_addresses = 0; // Fast path: try numeric/MAC parse first to avoid DNS lookup struct TcsAddress parsed = TCS_ADDRESS_NONE; if (tcs_address_parse(hostname, &parsed) == TCS_SUCCESS && parsed.family != TCS_AF_ANY) { if (address_family == TCS_AF_ANY || parsed.family == address_family) { if (found_addresses != NULL && found_addresses_length > 0) found_addresses[0] = parsed; if (no_of_found_addresses != NULL) *no_of_found_addresses = 1; return TCS_SUCCESS; } } struct addrinfo native_hints; memset(&native_hints, 0, sizeof native_hints); sa_family_t native_family; TcsResult family_convert_status = family2native(address_family, &native_family); if (family_convert_status != TCS_SUCCESS) return family_convert_status; native_hints.ai_family = native_family; native_hints.ai_flags = AI_NUMERICSERV; native_hints.ai_socktype = SOCK_DGRAM; native_hints.ai_protocol = IPPROTO_UDP; struct addrinfo* native_addrinfo_list = NULL; int sts = getaddrinfo(hostname, NULL, &native_hints, &native_addrinfo_list); // TODO: Move error codes to errno2retcode() if (sts == EAI_SYSTEM) return errno2retcode(errno); else if (sts == EAI_AGAIN) return TCS_ERROR_TEMPORARY_FAILURE; else if (sts == EAI_BADFLAGS) return TCS_ERROR_INVALID_ARGUMENT; else if (sts == EAI_FAIL) return TCS_ERROR_ADDRESS_LOOKUP_FAILED; else if (sts == EAI_FAMILY) return TCS_ERROR_INVALID_ARGUMENT; else if (sts == EAI_MEMORY) return TCS_ERROR_MEMORY; else if (sts == EAI_NONAME) return TCS_ERROR_ADDRESS_LOOKUP_FAILED; else if (sts == EAI_SERVICE) return TCS_ERROR_INVALID_ARGUMENT; else if (native_addrinfo_list == NULL) return TCS_ERROR_UNKNOWN; else if (sts != 0) return errno2retcode(sts); size_t i = 0; if (found_addresses == NULL) { for (struct addrinfo* iter = native_addrinfo_list; iter != NULL; iter = iter->ai_next) i++; } else { for (struct addrinfo* iter = native_addrinfo_list; iter != NULL && i < found_addresses_length; iter = iter->ai_next) { if (iter->ai_addr == NULL) continue; TcsResult convert_address_status = native2sockaddr(iter->ai_addr, &found_addresses[i]); if (convert_address_status != TCS_SUCCESS) continue; i++; } } if (no_of_found_addresses != NULL) *no_of_found_addresses = i; freeaddrinfo(native_addrinfo_list); if (i == 0) return TCS_ERROR_ADDRESS_LOOKUP_FAILED; return TCS_SUCCESS; } #if TCS_HAS_GETIFADDRS TcsResult tcs_address_list(unsigned int interface_id_filter, TcsAddressFamily address_family_filter, struct TcsInterfaceAddress interface_addresses[], size_t capacity, size_t* out_count) { if (interface_addresses == NULL && out_count == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (interface_addresses == NULL && capacity != 0) return TCS_ERROR_INVALID_ARGUMENT; if (out_count != NULL) *out_count = 0; struct ifaddrs* ifap = NULL; if (getifaddrs(&ifap) == -1) { if (ifap != NULL) freeifaddrs(ifap); return errno2retcode(errno); } if (ifap == NULL) return TCS_ERROR_UNKNOWN; size_t populated = 0; for (struct ifaddrs* iter = ifap; iter != NULL; iter = iter->ifa_next) { if (iter->ifa_addr == NULL) continue; if (interface_id_filter != 0) { unsigned int id = if_nametoindex(iter->ifa_name); if (id == 0) { freeifaddrs(ifap); return errno2retcode(errno); } if (id != interface_id_filter) continue; } if (address_family_filter != TCS_AF_ANY) { sa_family_t native_family; TcsResult family_convert_status = family2native(address_family_filter, &native_family); if (family_convert_status == TCS_ERROR_NOT_IMPLEMENTED) continue; if (family_convert_status != TCS_SUCCESS) { freeifaddrs(ifap); return family_convert_status; } if (iter->ifa_addr->sa_family != native_family) continue; } struct TcsAddress address = TCS_ADDRESS_NONE; TcsResult convert_address_status = native2sockaddr(iter->ifa_addr, &address); if (convert_address_status == TCS_ERROR_NOT_IMPLEMENTED) continue; if (convert_address_status != TCS_SUCCESS) { freeifaddrs(ifap); return convert_address_status; } if (interface_addresses != NULL && populated < capacity) { unsigned int interface_id = if_nametoindex(iter->ifa_name); if (interface_id == 0) { freeifaddrs(ifap); return errno2retcode(errno); } strncpy(interface_addresses[populated].iface.name, iter->ifa_name, TCS_INTERFACE_NAME_SIZE - 1); interface_addresses[populated].iface.name[TCS_INTERFACE_NAME_SIZE - 1] = '\0'; interface_addresses[populated].iface.id = interface_id; interface_addresses[populated].address = address; populated++; } if (out_count != NULL) (*out_count)++; } freeifaddrs(ifap); return TCS_SUCCESS; } #else // Fallback using ioctl(SIOCGIFCONF) for systems without getifaddrs (e.g. Android < API 24, old SunOS) // Limitation: SIOCGIFCONF only returns IPv4 addresses. IPv6 enumeration would require // platform-specific mechanisms (Linux: /proc/net/if_inet6 or netlink, Solaris: SIOCGLIFCONF). TcsResult tcs_address_list(unsigned int interface_id_filter, TcsAddressFamily address_family_filter, struct TcsInterfaceAddress interface_addresses[], size_t capacity, size_t* out_count) { if (interface_addresses == NULL && out_count == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (interface_addresses == NULL && capacity != 0) return TCS_ERROR_INVALID_ARGUMENT; if (out_count != NULL) *out_count = 0; // Check if we support the requested address family in the ioctl fallback. // IPv4 is always available. AF_PACKET is available on Linux via SIOCGIFHWADDR. bool supported = (address_family_filter == TCS_AF_ANY || address_family_filter == TCS_AF_IP4); #if TCS_HAS_AF_PACKET supported = supported || (address_family_filter == TCS_AF_PACKET); #endif if (!supported) return TCS_SUCCESS; int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) return errno2retcode(errno); // Start with stack buffer, grow on heap if truncated char stack_buf[512]; char* buf = stack_buf; int buf_len = (int)sizeof(stack_buf); struct ifconf ifc; for (;;) { ifc.ifc_len = buf_len; ifc.ifc_buf = buf; if (ioctl(fd, SIOCGIFCONF, &ifc) != 0) { if (buf != stack_buf) free(buf); close(fd); return errno2retcode(errno); } if (ifc.ifc_len < buf_len) break; if (interface_addresses != NULL && out_count == NULL && (size_t)ifc.ifc_len / sizeof(struct ifreq) >= capacity) break; buf_len *= 2; if (buf != stack_buf) free(buf); buf = (char*)malloc((size_t)buf_len); if (buf == NULL) { close(fd); return TCS_ERROR_MEMORY; } } size_t populated = 0; int offset = 0; while (offset < ifc.ifc_len) { struct ifreq* ifr = (struct ifreq*)(buf + offset); int entry_len = (int)sizeof(struct ifreq); unsigned int iface_id = if_nametoindex(ifr->ifr_name); if (iface_id == 0) { offset += entry_len; continue; } if (interface_id_filter != 0 && iface_id != interface_id_filter) { offset += entry_len; continue; } // IPv4 addresses if (address_family_filter == TCS_AF_ANY || address_family_filter == TCS_AF_IP4) { struct TcsAddress address = TCS_ADDRESS_NONE; TcsResult convert_status = native2sockaddr((struct sockaddr*)&ifr->ifr_addr, &address); if (convert_status == TCS_SUCCESS) { if (interface_addresses != NULL && populated < capacity) { strncpy(interface_addresses[populated].iface.name, ifr->ifr_name, TCS_INTERFACE_NAME_SIZE - 1); interface_addresses[populated].iface.name[TCS_INTERFACE_NAME_SIZE - 1] = '\0'; interface_addresses[populated].iface.id = iface_id; interface_addresses[populated].address = address; populated++; } if (out_count != NULL) (*out_count)++; } } #if TCS_HAS_AF_PACKET // AF_PACKET addresses via SIOCGIFHWADDR (Ethernet only) if (address_family_filter == TCS_AF_ANY || address_family_filter == TCS_AF_PACKET) { struct ifreq hw_req; memset(&hw_req, 0, sizeof(hw_req)); strncpy(hw_req.ifr_name, ifr->ifr_name, IFNAMSIZ - 1); if (ioctl(fd, SIOCGIFHWADDR, &hw_req) == 0 && hw_req.ifr_hwaddr.sa_family == ARPHRD_ETHER) { if (interface_addresses != NULL && populated < capacity) { strncpy(interface_addresses[populated].iface.name, ifr->ifr_name, TCS_INTERFACE_NAME_SIZE - 1); interface_addresses[populated].iface.name[TCS_INTERFACE_NAME_SIZE - 1] = '\0'; interface_addresses[populated].iface.id = iface_id; interface_addresses[populated].address.family = TCS_AF_PACKET; interface_addresses[populated].address.data.packet.interface_id = iface_id; memcpy(interface_addresses[populated].address.data.packet.mac, hw_req.ifr_hwaddr.sa_data, 6); populated++; } if (out_count != NULL) (*out_count)++; } } #endif offset += entry_len; } if (buf != stack_buf) free(buf); close(fd); return TCS_SUCCESS; } #endif TcsResult tcs_address_socket_local(TcsSocket socket_ctx, struct TcsAddress* local_address) { if (socket_ctx == TCS_SOCKET_INVALID || local_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct sockaddr_storage native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); socklen_t addrlen = sizeof native_sockaddr; if (getsockname(socket_ctx, (struct sockaddr*)&native_sockaddr, &addrlen) != 0) return errno2retcode(errno); return native2sockaddr((struct sockaddr*)&native_sockaddr, local_address); } TcsResult tcs_address_socket_remote(TcsSocket socket_ctx, struct TcsAddress* remote_address) { if (socket_ctx == TCS_SOCKET_INVALID || remote_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct sockaddr_storage native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); socklen_t addrlen = sizeof native_sockaddr; if (getpeername(socket_ctx, (struct sockaddr*)&native_sockaddr, &addrlen) != 0) return errno2retcode(errno); return native2sockaddr((struct sockaddr*)&native_sockaddr, remote_address); } TcsResult tcs_address_socket_family(TcsSocket socket_ctx, TcsAddressFamily* out_family) { if (socket_ctx == TCS_SOCKET_INVALID || out_family == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct sockaddr_storage native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); socklen_t addrlen = sizeof native_sockaddr; if (getsockname(socket_ctx, (struct sockaddr*)&native_sockaddr, &addrlen) != 0) return errno2retcode(errno); TcsAddressFamily family = TCS_AF_ANY; TcsResult family_convert_status = native2family(native_sockaddr.ss_family, &family); if (family_convert_status != TCS_SUCCESS) return family_convert_status; *out_family = family; return TCS_SUCCESS; } // tcs_address_parse() is defined in tinycsocket_common.c // tcs_address_to_str() is defined in tinycsocket_common.c // tcs_address_is_equal() is defined in tinycsocket_common.c // tcs_address_is_any() is defined in tinycsocket_common.c // tcs_address_is_link_local() is defined in tinycsocket_common.c // tcs_address_is_loopback() is defined in tinycsocket_common.c // tcs_address_is_multicast() is defined in tinycsocket_common.c // tcs_address_is_broadcast() is defined in tinycsocket_common.c #endif /**********************************/ /****** tinycsocket_win32.h *******/ /**********************************/ /* * Copyright 2018 Markus Lindelöw * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files(the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and / or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef TINYCSOCKET_INTERNAL_H_ #include "tinycsocket_internal.h" #endif #ifdef TINYCSOCKET_USE_WIN32_IMPL #ifdef DO_WRAP #include "dbg_wrap.h" #endif // Header only should not need other files #ifndef TINYDATASTRUCTURES_H_ #include "tinydatastructures.h" #endif // before windows.h #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include // sockets #include // after windows.h #include // GetAdaptersAddresses #include // getaddrinfo #include // fprintf (debug diagnostics) #include // Malloc for GetAdaptersAddresses #include // memset // SOCKADDR_STORAGE was introduced in Windows XP (0x0501). // For Win2K targets with MSVC, define it ourselves. // mingw-w64 always defines it, so check _MSC_VER to avoid redefinition. #if defined(_MSC_VER) && defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0501 typedef struct { short ss_family; char __ss_pad1[6]; __int64 __ss_align; char __ss_pad2[112]; } SOCKADDR_STORAGE, *PSOCKADDR_STORAGE; #endif #if defined(_MSC_VER) || defined(__clang__) #pragma comment(lib, "wsock32.lib") #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "Iphlpapi.lib") #endif #ifndef ULIST_SOC #define ULIST_SOC TDS_ULIST_IMPL(SOCKET, soc) #endif #ifndef ULIST_PVOID #define ULIST_PVOID TDS_ULIST_IMPL(void*, pvoid) #endif #ifndef TDS_MAP_socket_pvoid #define TDS_MAP_socket_pvoid TDS_MAP_IMPL(SOCKET, void*, socket_user) #endif // Needs to be compatible with fd_set, hopefully this works. Only used when FD_SETSIZE is to small. struct tcs_fd_set { u_int fd_count; SOCKET fd_array[1]; // dynamic memory hack that is compatible with Win32 API fd_set }; struct TcsPool { struct TdsUList_soc read_sockets; struct TdsUList_soc write_sockets; struct TdsUList_soc error_sockets; struct TdsMap_socket_user user_data; }; const TcsSocket TCS_SOCKET_INVALID = INVALID_SOCKET; const int TCS_WAIT_INF = -1; // Addresses const uint32_t TCS_ADDRESS_ANY_IP4 = INADDR_ANY; const uint32_t TCS_ADDRESS_LOOPBACK_IP4 = INADDR_LOOPBACK; const uint32_t TCS_ADDRESS_BROADCAST_IP4 = INADDR_BROADCAST; const uint32_t TCS_ADDRESS_NONE_IP4 = INADDR_NONE; const struct TcsIp6Address TCS_ADDRESS_ANY_IP6 = {{0}}; const struct TcsIp6Address TCS_ADDRESS_LOOPBACK_IP6 = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}; // Type const int TCS_SOCK_STREAM = SOCK_STREAM; const int TCS_SOCK_DGRAM = SOCK_DGRAM; const int TCS_SOCK_RAW = SOCK_RAW; // Protocol const uint16_t TCS_PROTOCOL_IP_TCP = IPPROTO_TCP; const uint16_t TCS_PROTOCOL_IP_UDP = IPPROTO_UDP; // Flags const uint32_t TCS_AI_PASSIVE = AI_PASSIVE; // Recv flags const uint32_t TCS_MSG_PEEK = MSG_PEEK; const uint32_t TCS_MSG_OOB = MSG_OOB; const uint32_t TCS_MSG_WAITALL = 0x8; // Binary compatible when it does not exist // Send flags const uint32_t TCS_MSG_SENDALL = 0x80000000; // Backlog const int TCS_BACKLOG_MAX = SOMAXCONN; // Option levels const int TCS_SOL_SOCKET = SOL_SOCKET; const int TCS_SOL_IP = IPPROTO_IP; // Socket options const int TCS_SO_TYPE = SO_TYPE; const int TCS_SO_BROADCAST = SO_BROADCAST; const int TCS_SO_KEEPALIVE = SO_KEEPALIVE; const int TCS_SO_LINGER = SO_LINGER; const int TCS_SO_REUSEADDR = SO_REUSEADDR; const int TCS_SO_REUSEPORT = -1; const int TCS_SO_RCVBUF = SO_RCVBUF; const int TCS_SO_RCVTIMEO = SO_RCVTIMEO; const int TCS_SO_SNDBUF = SO_SNDBUF; const int TCS_SO_OOBINLINE = SO_OOBINLINE; const int TCS_SO_PRIORITY = -1; // IP options const int TCS_SO_IP_NODELAY = TCP_NODELAY; const int TCS_SO_IP_MEMBERSHIP_ADD = IP_ADD_MEMBERSHIP; const int TCS_SO_IP_MEMBERSHIP_DROP = IP_DROP_MEMBERSHIP; const int TCS_SO_IP_MULTICAST_LOOP = IP_MULTICAST_LOOP; // ######## Internal Helpers ######## static TcsResult wsaerror2retcode(int wsa_error) { switch (wsa_error) { case WSANOTINITIALISED: return TCS_ERROR_LIBRARY_NOT_INITIALIZED; case WSAEWOULDBLOCK: return TCS_ERROR_WOULD_BLOCK; case WSAETIMEDOUT: return TCS_ERROR_TIMED_OUT; case WSAECONNREFUSED: return TCS_ERROR_CONNECTION_REFUSED; case WSAECONNRESET: return TCS_ERROR_CONNECTION_RESET; case WSAENOTCONN: return TCS_ERROR_NOT_CONNECTED; case WSAENETUNREACH: case WSAEHOSTUNREACH: case WSAENETDOWN: return TCS_ERROR_NETWORK_UNREACHABLE; case WSAEACCES: return TCS_ERROR_PERMISSION_DENIED; case WSAEINVAL: return TCS_ERROR_INVALID_ARGUMENT; case WSAEADDRINUSE: return TCS_ERROR_ADDRESS_IN_USE; default: return TCS_ERROR_UNKNOWN; } } static TcsResult socketstatus2retcode(int status) { if (status == 0) { return TCS_SUCCESS; } else if (status == SOCKET_ERROR) { int error_code = WSAGetLastError(); return wsaerror2retcode(error_code); } else { return TCS_ERROR_UNKNOWN; } } static TcsResult family2native(const TcsAddressFamily family, short* native_family) { if (native_family == NULL) return TCS_ERROR_INVALID_ARGUMENT; switch (family) { case TCS_AF_ANY: *native_family = AF_UNSPEC; return TCS_SUCCESS; case TCS_AF_IP4: *native_family = AF_INET; return TCS_SUCCESS; case TCS_AF_IP6: *native_family = AF_INET6; return TCS_SUCCESS; case TCS_AF_PACKET: return TCS_ERROR_NOT_IMPLEMENTED; default: return TCS_ERROR_INVALID_ARGUMENT; } } static TcsResult sockaddr2native(const struct TcsAddress* in_addr, PSOCKADDR out_addr, int* out_addrlen) { if (in_addr == NULL || out_addr == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (in_addr->family == TCS_AF_ANY) { return TCS_ERROR_NOT_IMPLEMENTED; } if (in_addr->family == TCS_AF_IP4) { PSOCKADDR_IN addr = (PSOCKADDR_IN)out_addr; addr->sin_family = (ADDRESS_FAMILY)AF_INET; addr->sin_port = htons((USHORT)in_addr->data.ip4.port); addr->sin_addr.S_un.S_addr = htonl((ULONG)in_addr->data.ip4.address); if (out_addrlen != NULL) *out_addrlen = sizeof(SOCKADDR_IN); return TCS_SUCCESS; } else if (in_addr->family == TCS_AF_IP6) { PSOCKADDR_IN6 addr = (PSOCKADDR_IN6)out_addr; addr->sin6_family = (ADDRESS_FAMILY)AF_INET6; addr->sin6_port = htons((USHORT)in_addr->data.ip6.port); memcpy(&addr->sin6_addr, in_addr->data.ip6.address.bytes, 16); addr->sin6_scope_id = (ULONG)in_addr->data.ip6.scope_id; if (out_addrlen != NULL) *out_addrlen = sizeof(SOCKADDR_IN6); return TCS_SUCCESS; } else if (in_addr->family == TCS_AF_PACKET) { return TCS_ERROR_NOT_IMPLEMENTED; } return TCS_ERROR_NOT_IMPLEMENTED; } static TcsResult native2family(const short native_family, TcsAddressFamily* family) { if (family == NULL) return TCS_ERROR_INVALID_ARGUMENT; switch (native_family) { case AF_UNSPEC: *family = TCS_AF_ANY; return TCS_SUCCESS; case AF_INET: *family = TCS_AF_IP4; return TCS_SUCCESS; case AF_INET6: *family = TCS_AF_IP6; return TCS_SUCCESS; default: return TCS_ERROR_NOT_IMPLEMENTED; } } static TcsResult native2sockaddr(const PSOCKADDR in_addr, struct TcsAddress* out_addr) { if (in_addr == NULL || out_addr == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (in_addr->sa_family == AF_INET) { PSOCKADDR_IN addr = (PSOCKADDR_IN)in_addr; out_addr->family = TCS_AF_IP4; out_addr->data.ip4.port = ntohs((uint16_t)addr->sin_port); out_addr->data.ip4.address = ntohl((uint32_t)addr->sin_addr.S_un.S_addr); } else if (in_addr->sa_family == AF_INET6) { PSOCKADDR_IN6 addr = (PSOCKADDR_IN6)in_addr; out_addr->family = TCS_AF_IP6; out_addr->data.ip6.port = ntohs((uint16_t)addr->sin6_port); memcpy(out_addr->data.ip6.address.bytes, &addr->sin6_addr, 16); out_addr->data.ip6.scope_id = (TcsInterfaceId)addr->sin6_scope_id; } else if (in_addr->sa_family == AF_UNSPEC) { return TCS_ERROR_INVALID_ARGUMENT; } else { return TCS_ERROR_NOT_IMPLEMENTED; } return TCS_SUCCESS; } TcsResult tcs_lib_init(void) { WSADATA wsa_data; if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) return TCS_ERROR_SYSTEM; return TCS_SUCCESS; } TcsResult tcs_lib_free(void) { WSACleanup(); return TCS_SUCCESS; } TcsResult tcs_socket(TcsSocket* socket_ctx, TcsAddressFamily family, int type, int protocol) { if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; short native_family = AF_UNSPEC; TcsResult sts = family2native(family, &native_family); if (sts != TCS_SUCCESS) return sts; TcsSocket new_socket = socket(native_family, type, protocol); if (new_socket != INVALID_SOCKET) { *socket_ctx = new_socket; return TCS_SUCCESS; } else { int error_code = WSAGetLastError(); return wsaerror2retcode(error_code); } } // tcs_socket_tcp() is defined in tinycsocket_common.c // tcs_socket_tcp_str() is defined in tinycsocket_common.c // tcs_socket_udp() is defined in tinycsocket_common.c // tcs_socket_udp_str() is defined in tinycsocket_common.c // tcs_socket_packet() is defined in tinycsocket_common.c // tcs_socket_packet_str() is defined in tinycsocket_common.c TcsResult tcs_close(TcsSocket* socket_ctx) { if (socket_ctx == NULL || *socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; int close_status = closesocket(*socket_ctx); if (close_status != SOCKET_ERROR) { *socket_ctx = TCS_SOCKET_INVALID; return TCS_SUCCESS; } else { return socketstatus2retcode(close_status); } } // ######## Socket Operations ######## TcsResult tcs_bind(TcsSocket socket_ctx, const struct TcsAddress* address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (address == NULL) return TCS_ERROR_INVALID_ARGUMENT; SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = 0; TcsResult convert_addr_status = sockaddr2native(address, (PSOCKADDR)&native_sockaddr, &addrlen); if (convert_addr_status != TCS_SUCCESS) return convert_addr_status; int bind_status = bind(socket_ctx, (PSOCKADDR)&native_sockaddr, addrlen); return socketstatus2retcode(bind_status); } TcsResult tcs_connect(TcsSocket socket_ctx, const struct TcsAddress* address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (address == NULL) return TCS_ERROR_INVALID_ARGUMENT; SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = 0; TcsResult convert_addr_status = sockaddr2native(address, (PSOCKADDR)&native_sockaddr, &addrlen); if (convert_addr_status != TCS_SUCCESS) return convert_addr_status; int connect_status = connect(socket_ctx, (PSOCKADDR)&native_sockaddr, addrlen); if (connect_status == SOCKET_ERROR) { int error_code = WSAGetLastError(); // Windows uses WSAEWOULDBLOCK for non-blocking connect in progress, // while POSIX uses EINPROGRESS. Normalize to TCS_IN_PROGRESS. if (error_code == WSAEWOULDBLOCK) return TCS_IN_PROGRESS; return wsaerror2retcode(error_code); } return TCS_SUCCESS; } // tcs_connect_str() is defined in tinycsocket_common.c TcsResult tcs_listen(TcsSocket socket_ctx, int backlog) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; int status = listen(socket_ctx, (int)backlog); return socketstatus2retcode(status); } TcsResult tcs_accept(TcsSocket socket_ctx, TcsSocket* child_socket_ctx, struct TcsAddress* address) { if (socket_ctx == TCS_SOCKET_INVALID || child_socket_ctx == NULL || *child_socket_ctx != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addr_len = sizeof(native_sockaddr); *child_socket_ctx = accept(socket_ctx, (PSOCKADDR)&native_sockaddr, &addr_len); if (*child_socket_ctx != INVALID_SOCKET) { if (address != NULL) { TcsResult convert_addr_status = native2sockaddr((PSOCKADDR)&native_sockaddr, address); if (convert_addr_status != TCS_SUCCESS) return convert_addr_status; } return TCS_SUCCESS; } else { *child_socket_ctx = TCS_SOCKET_INVALID; int error_code = WSAGetLastError(); return wsaerror2retcode(error_code); } } TcsResult tcs_shutdown(TcsSocket socket_ctx, TcsSocketDirection direction) { const int LUT[] = {SD_RECEIVE, SD_SEND, SD_BOTH}; if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (direction < 0 || direction > 2) return TCS_ERROR_INVALID_ARGUMENT; const int how = LUT[direction]; int shutdown_status = shutdown(socket_ctx, how); return socketstatus2retcode(shutdown_status); } // ######## Data Transfer ######## TcsResult tcs_send(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size, uint32_t flags, size_t* bytes_sent) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (bytes_sent != NULL) *bytes_sent = 0; if (buffer == NULL || buffer_size == 0) return TCS_ERROR_INVALID_ARGUMENT; // Send all if (flags & TCS_MSG_SENDALL) { uint32_t new_flags = flags & ~TCS_MSG_SENDALL; // For recursive call size_t left = buffer_size; const uint8_t* iterator = buffer; while (left > 0) { size_t sent = 0; TcsResult sts = tcs_send(socket_ctx, iterator, left, new_flags, &sent); if (bytes_sent != NULL) *bytes_sent += sent; if (sts != TCS_SUCCESS) return sts; left -= sent; iterator += sent; } return TCS_SUCCESS; } else // Send { int send_status = send(socket_ctx, (const char*)buffer, (int)buffer_size, (int)flags); if (send_status != SOCKET_ERROR) { if (bytes_sent != NULL) *bytes_sent = (size_t)send_status; return TCS_SUCCESS; } else { if (bytes_sent != NULL) *bytes_sent = 0; return socketstatus2retcode(send_status); } } } TcsResult tcs_send_to(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size, uint32_t flags, const struct TcsAddress* destination_address, size_t* bytes_sent) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (buffer == NULL && buffer_size > 0) return TCS_ERROR_INVALID_ARGUMENT; if (destination_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (flags & TCS_MSG_SENDALL) return TCS_ERROR_NOT_IMPLEMENTED; SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = 0; TcsResult convert_addr_status = sockaddr2native(destination_address, (PSOCKADDR)&native_sockaddr, &addrlen); if (convert_addr_status != TCS_SUCCESS) return convert_addr_status; int sendto_status = sendto(socket_ctx, (const char*)buffer, (int)buffer_size, (int)flags, (PSOCKADDR)&native_sockaddr, addrlen); if (sendto_status != SOCKET_ERROR) { if (bytes_sent != NULL) *bytes_sent = (size_t)sendto_status; return TCS_SUCCESS; } else { if (bytes_sent != NULL) *bytes_sent = 0; return socketstatus2retcode(sendto_status); } } TcsResult tcs_sendv(TcsSocket socket_ctx, const struct TcsBuffer* buffers, size_t buffer_count, uint32_t flags, size_t* bytes_sent) { if (socket_ctx == TCS_SOCKET_INVALID || buffers == NULL || buffer_count == 0) return TCS_ERROR_INVALID_ARGUMENT; if (flags & TCS_MSG_SENDALL) return TCS_ERROR_NOT_IMPLEMENTED; WSABUF stack_buffers[TCS_SENDV_STACK_MAX]; WSABUF* native_buffers = stack_buffers; WSABUF* heap_buffers = NULL; if (buffer_count > TCS_SENDV_STACK_MAX) { heap_buffers = (WSABUF*)malloc(sizeof(WSABUF) * buffer_count); if (heap_buffers == NULL) return TCS_ERROR_MEMORY; native_buffers = heap_buffers; } for (size_t i = 0; i < buffer_count; ++i) { if (buffers[i].data == NULL && buffers[i].size > 0) { free(heap_buffers); return TCS_ERROR_INVALID_ARGUMENT; } // WSABUF.buf is non-const by Windows API design, but WSASend does not modify the data. #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" #endif native_buffers[i].buf = (CHAR*)buffers[i].data; #ifdef __GNUC__ #pragma GCC diagnostic pop #endif native_buffers[i].len = (ULONG)buffers[i].size; } DWORD sent = 0; int wsasend_status = WSASend(socket_ctx, native_buffers, (DWORD)buffer_count, &sent, (DWORD)flags, NULL, NULL); free(heap_buffers); if (wsasend_status != SOCKET_ERROR) { if (bytes_sent != NULL) *bytes_sent = (size_t)sent; return TCS_SUCCESS; } else { if (bytes_sent != NULL) *bytes_sent = 0; return socketstatus2retcode(wsasend_status); } } TcsResult tcs_receive(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, uint32_t flags, size_t* bytes_received) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (buffer == NULL && buffer_size > 0) return TCS_ERROR_INVALID_ARGUMENT; #if WINVER <= 0x501 if (flags & TCS_MSG_WAITALL) { uint32_t new_flags = flags & ~TCS_MSG_WAITALL; // Unmask for recursive call size_t received_so_far = 0; while (received_so_far < buffer_size) { size_t received_now = 0; uint8_t* cursor = buffer + received_so_far; size_t left = buffer_size - received_so_far; TcsResult sts = tcs_receive(socket_ctx, cursor, left, new_flags, &received_now); if (sts != TCS_SUCCESS) { if (bytes_received != NULL) *bytes_received = received_so_far; return sts; } received_so_far += received_now; } if (bytes_received != NULL) *bytes_received = received_so_far; return TCS_SUCCESS; } #endif int recv_status = recv(socket_ctx, (char*)buffer, (int)buffer_size, (int)flags); if (recv_status == 0) { if (bytes_received != NULL) *bytes_received = 0; int sock_type = 0; if (tcs_opt_type_get(socket_ctx, &sock_type) == TCS_SUCCESS && sock_type == TCS_SOCK_STREAM) return TCS_SHUTDOWN; return TCS_SUCCESS; } else if (recv_status != SOCKET_ERROR) { if (bytes_received != NULL) *bytes_received = (size_t)recv_status; return TCS_SUCCESS; } else { if (bytes_received != NULL) *bytes_received = 0; return socketstatus2retcode(recv_status); } } TcsResult tcs_receive_from(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, uint32_t flags, struct TcsAddress* source_address, size_t* bytes_received) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (buffer == NULL && buffer_size > 0) return TCS_ERROR_INVALID_ARGUMENT; if (bytes_received != NULL) *bytes_received = 0; SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = sizeof(native_sockaddr); int recvfrom_status = recvfrom(socket_ctx, (char*)buffer, (int)buffer_size, (int)flags, (PSOCKADDR)&native_sockaddr, &addrlen); if (recvfrom_status == 0) { if (bytes_received != NULL) *bytes_received = 0; int sock_type = 0; if (tcs_opt_type_get(socket_ctx, &sock_type) == TCS_SUCCESS && sock_type == TCS_SOCK_STREAM) return TCS_SHUTDOWN; return TCS_SUCCESS; } else if (recvfrom_status != SOCKET_ERROR) { if (bytes_received != NULL) *bytes_received = (size_t)recvfrom_status; if (source_address != NULL) { TcsResult convert_address_status = native2sockaddr((PSOCKADDR)&native_sockaddr, source_address); return convert_address_status; } return TCS_SUCCESS; } else { if (bytes_received != NULL) *bytes_received = 0; return socketstatus2retcode(recvfrom_status); } } // tcs_receive_line() is defined in tinycsocket_common.c // tcs_receive_netstring() is defined in tinycsocket_common.c // ######## Socket Pooling ######## TcsResult tcs_pool_create(struct TcsPool** pool) { if (pool == NULL || *pool != NULL) return TCS_ERROR_INVALID_ARGUMENT; *pool = (struct TcsPool*)malloc(sizeof(struct TcsPool)); if (*pool == NULL) return TCS_ERROR_MEMORY; memset(*pool, 0, sizeof(struct TcsPool)); // Just to be safe int sts_read_array = tds_ulist_soc_create(&(*pool)->read_sockets); int sts_write_array = tds_ulist_soc_create(&(*pool)->write_sockets); int sts_error_array = tds_ulist_soc_create(&(*pool)->error_sockets); int sts_user_data = tds_map_socket_user_create(&(*pool)->user_data); if (sts_read_array != 0 || sts_write_array != 0 || sts_error_array != 0 || sts_user_data != 0) { tcs_pool_destroy(pool); return TCS_ERROR_MEMORY; } return TCS_SUCCESS; } TcsResult tcs_pool_destroy(struct TcsPool** pool) { if (pool == NULL || *pool == NULL) return TCS_ERROR_INVALID_ARGUMENT; // Free away! tds_ulist_soc_destroy(&(*pool)->read_sockets); tds_ulist_soc_destroy(&(*pool)->write_sockets); tds_ulist_soc_destroy(&(*pool)->error_sockets); tds_map_socket_user_destroy(&(*pool)->user_data); free(*pool); *pool = NULL; return TCS_SUCCESS; } TcsResult tcs_pool_add(struct TcsPool* pool, TcsSocket socket_ctx, void* user_data, bool poll_can_read, bool poll_can_write, bool poll_error) { if (pool == NULL || socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (!poll_can_read && !poll_can_write && !poll_error) return TCS_ERROR_INVALID_ARGUMENT; int map_sts = tds_map_socket_user_add(&pool->user_data, socket_ctx, user_data); if (map_sts != 0) return TCS_ERROR_MEMORY; if (poll_can_read) { int sts = tds_ulist_soc_add(&pool->read_sockets, &socket_ctx, 1); if (sts != 0) { tds_map_socket_user_remove(&pool->user_data, pool->user_data.count - 1); return TCS_ERROR_MEMORY; } } if (poll_can_write) { int sts = tds_ulist_soc_add(&pool->write_sockets, &socket_ctx, 1); if (sts != 0) { if (poll_can_read) tds_ulist_soc_remove(&pool->read_sockets, pool->read_sockets.count - 1, 1); tds_map_socket_user_remove(&pool->user_data, pool->user_data.count - 1); return TCS_ERROR_MEMORY; } } if (poll_error) { int sts = tds_ulist_soc_add(&pool->error_sockets, &socket_ctx, 1); if (sts != 0) { if (poll_can_write) tds_ulist_soc_remove(&pool->write_sockets, pool->write_sockets.count - 1, 1); if (poll_can_read) tds_ulist_soc_remove(&pool->read_sockets, pool->read_sockets.count - 1, 1); tds_map_socket_user_remove(&pool->user_data, pool->user_data.count - 1); return TCS_ERROR_MEMORY; } } return TCS_SUCCESS; } TcsResult tcs_pool_remove(struct TcsPool* pool, TcsSocket socket_ctx) { if (pool == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; bool found = false; for (size_t i = 0; i < pool->read_sockets.count; ++i) { if (pool->read_sockets.data[i] == socket_ctx) { tds_ulist_soc_remove(&pool->read_sockets, i, 1); found = true; break; } } for (size_t i = 0; i < pool->write_sockets.count; ++i) { if (pool->write_sockets.data[i] == socket_ctx) { tds_ulist_soc_remove(&pool->write_sockets, i, 1); found = true; break; } } for (size_t i = 0; i < pool->error_sockets.count; ++i) { if (pool->error_sockets.data[i] == socket_ctx) { tds_ulist_soc_remove(&pool->error_sockets, i, 1); found = true; break; } } for (size_t i = 0; i < pool->user_data.count; ++i) { if (pool->user_data.keys[i] == socket_ctx) { tds_map_socket_user_remove(&pool->user_data, i); found = true; break; } } if (!found) return TCS_ERROR_INVALID_ARGUMENT; return TCS_SUCCESS; } TcsResult tcs_pool_poll(struct TcsPool* pool, struct TcsPollEvent* events, size_t events_capacity, size_t* events_populated, int timeout_ms) { if (pool == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (events == NULL || events_populated == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (timeout_ms < 0 && timeout_ms != TCS_WAIT_INF) return TCS_ERROR_INVALID_ARGUMENT; // Todo: add more modern implementation. Maybe dispatch att init? // SELECT IMPLEMENTATION // Copy fds, use auto storage if possible, otherwise use dynamic memory // Allocate so that we can access fd_array out of nominal bounds // We need this hack to be able to use dynamic memory for select const size_t data_offset = offsetof(struct tcs_fd_set, fd_array); struct fd_set rfds_stack; struct fd_set wfds_stack; struct fd_set efds_stack; struct tcs_fd_set* rfds_heap = NULL; struct tcs_fd_set* wfds_heap = NULL; struct tcs_fd_set* efds_heap = NULL; struct tcs_fd_set* rfds_cpy = NULL; struct tcs_fd_set* wfds_cpy = NULL; struct tcs_fd_set* efds_cpy = NULL; if (pool->read_sockets.count <= FD_SETSIZE) { FD_ZERO(&rfds_stack); rfds_cpy = (struct tcs_fd_set*)&rfds_stack; } else { rfds_heap = (struct tcs_fd_set*)malloc(data_offset + sizeof(SOCKET) * pool->read_sockets.count); if (rfds_heap == NULL) return TCS_ERROR_MEMORY; rfds_cpy = rfds_heap; } if (pool->write_sockets.count <= FD_SETSIZE) { FD_ZERO(&wfds_stack); wfds_cpy = (struct tcs_fd_set*)&wfds_stack; } else { wfds_heap = (struct tcs_fd_set*)malloc(data_offset + sizeof(SOCKET) * pool->write_sockets.count); if (wfds_heap == NULL) { free(rfds_heap); return TCS_ERROR_MEMORY; } wfds_cpy = wfds_heap; } if (pool->error_sockets.count <= FD_SETSIZE) { FD_ZERO(&efds_stack); efds_cpy = (struct tcs_fd_set*)&efds_stack; } else { efds_heap = (struct tcs_fd_set*)malloc(data_offset + sizeof(SOCKET) * pool->error_sockets.count); if (efds_heap == NULL) { free(rfds_heap); free(wfds_heap); return TCS_ERROR_MEMORY; } efds_cpy = efds_heap; } rfds_cpy->fd_count = (u_int)pool->read_sockets.count; wfds_cpy->fd_count = (u_int)pool->write_sockets.count; efds_cpy->fd_count = (u_int)pool->error_sockets.count; memcpy(rfds_cpy->fd_array, pool->read_sockets.data, sizeof(SOCKET) * pool->read_sockets.count); memcpy(wfds_cpy->fd_array, pool->write_sockets.data, sizeof(SOCKET) * pool->write_sockets.count); memcpy(efds_cpy->fd_array, pool->error_sockets.data, sizeof(SOCKET) * pool->error_sockets.count); memset(events, 0, sizeof(struct TcsPollEvent) * events_capacity); *events_populated = 0; // Run select struct timeval* t_ptr = NULL; struct timeval t = {0, 0}; if (timeout_ms != TCS_WAIT_INF) { t.tv_sec = (long)(timeout_ms / 1000); t.tv_usec = (long)(timeout_ms % 1000) * 1000; t_ptr = &t; } int no = select(IGNORE, (fd_set*)rfds_cpy, (fd_set*)wfds_cpy, (fd_set*)efds_cpy, t_ptr); size_t events_added = 0; if (no > 0) { for (u_int n = 0; n < rfds_cpy->fd_count && events_added < events_capacity; ++n) { events[events_added].socket = rfds_cpy->fd_array[n]; events[events_added].can_read = true; for (size_t i = 0; i < pool->user_data.count; ++i) { if (events[events_added].socket == pool->user_data.keys[i]) { events[events_added].user_data = pool->user_data.values[i]; break; } } events_added++; } for (u_int n = 0; n < wfds_cpy->fd_count && events_added < events_capacity; ++n) { // Check already added events size_t new_n = events_added; for (size_t m = 0; m < events_added; ++m) { if (events[m].socket == wfds_cpy->fd_array[n]) { new_n = m; break; } } events[new_n].can_write = true; // Check for new events if (events_added == new_n) { events[new_n].socket = wfds_cpy->fd_array[n]; for (size_t i = 0; i < pool->user_data.count; ++i) { if (events[new_n].socket == pool->user_data.keys[i]) { events[new_n].user_data = pool->user_data.values[i]; break; } } events_added++; } } for (u_int n = 0; n < efds_cpy->fd_count && events_added < events_capacity; ++n) { // Check already added events size_t new_n = events_added; for (size_t m = 0; m < events_added; ++m) { if (events[m].socket == efds_cpy->fd_array[n]) { new_n = m; break; } } // Get the actual socket error int so_error = 0; int so_error_size = sizeof(so_error); if (getsockopt((SOCKET)efds_cpy->fd_array[n], SOL_SOCKET, SO_ERROR, (char*)&so_error, &so_error_size) != 0) events[new_n].error = wsaerror2retcode(WSAGetLastError()); else events[new_n].error = so_error != 0 ? wsaerror2retcode(so_error) : TCS_ERROR_UNKNOWN; if (events_added == new_n) { events[new_n].socket = efds_cpy->fd_array[n]; for (size_t i = 0; i < pool->user_data.count; ++i) { if (events[new_n].socket == pool->user_data.keys[i]) { events[new_n].user_data = pool->user_data.values[i]; break; } } events_added++; } } } *events_populated = events_added; // Clean up if (rfds_heap != NULL) free(rfds_heap); if (wfds_heap != NULL) free(wfds_heap); if (efds_heap != NULL) free(efds_heap); if (no == 0) { return TCS_ERROR_TIMED_OUT; } if (no == SOCKET_ERROR) { int error_code = WSAGetLastError(); return wsaerror2retcode(error_code); } return TCS_SUCCESS; } // ######## Socket Options ######## TcsResult tcs_opt_set(TcsSocket socket_ctx, int32_t level, int32_t option_name, const void* option_value, size_t option_size) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (option_name == -1) return TCS_ERROR_NOT_IMPLEMENTED; int sockopt_status = setsockopt(socket_ctx, (int)level, (int)option_name, (const char*)option_value, (int)option_size); return socketstatus2retcode(sockopt_status); } TcsResult tcs_opt_get(TcsSocket socket_ctx, int32_t level, int32_t option_name, void* option_value, size_t* option_size) { if (socket_ctx == TCS_SOCKET_INVALID || option_value == NULL || option_size == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (option_name == -1) return TCS_ERROR_NOT_IMPLEMENTED; int optlen = (int)*option_size; int sockopt_status = getsockopt(socket_ctx, (int)level, (int)option_name, (char*)option_value, &optlen); if (sockopt_status == 0) *option_size = (size_t)optlen; return socketstatus2retcode(sockopt_status); } // tcs_opt_broadcast_set() is defined in tinycsocket_common.c // tcs_opt_broadcast_get() is defined in tinycsocket_common.c // tcs_opt_keep_alive_set() is defined in tinycsocket_common.c // tcs_opt_keep_alive_get() is defined in tinycsocket_common.c TcsResult tcs_opt_reuse_address_set(TcsSocket socket_ctx, bool do_allow_reuse_address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; int b = do_allow_reuse_address ? 1 : 0; TcsResult sts = tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_REUSEADDR, &b, sizeof(b)); if (sts != TCS_SUCCESS) return sts; #if _WIN32_WINNT >= 0x0502 int sock_type = 0; if (tcs_opt_type_get(socket_ctx, &sock_type) == TCS_SUCCESS && sock_type == TCS_SOCK_STREAM) tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, SO_EXCLUSIVEADDRUSE, &b, sizeof(b)); #endif return TCS_SUCCESS; } TcsResult tcs_opt_reuse_address_get(TcsSocket socket_ctx, bool* is_reuse_address_allowed) { if (socket_ctx == TCS_SOCKET_INVALID || is_reuse_address_allowed == NULL) return TCS_ERROR_INVALID_ARGUMENT; int b = 0; size_t s = sizeof(b); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_REUSEADDR, &b, &s); *is_reuse_address_allowed = b; return sts; } TcsResult tcs_opt_reuse_port_set(TcsSocket socket_ctx, bool do_allow_reuse_port) { (void)socket_ctx; (void)do_allow_reuse_port; return TCS_ERROR_NOT_SUPPORTED; } TcsResult tcs_opt_reuse_port_get(TcsSocket socket_ctx, bool* is_reuse_port_allowed) { (void)socket_ctx; (void)is_reuse_port_allowed; return TCS_ERROR_NOT_SUPPORTED; } // tcs_opt_send_buffer_size_set() is defined in tinycsocket_common.c // tcs_opt_send_buffer_size_get() is defined in tinycsocket_common.c // tcs_opt_receive_buffer_size_set() is defined in tinycsocket_common.c // tcs_opt_receive_buffer_size_get() is defined in tinycsocket_common.c TcsResult tcs_opt_receive_timeout_set(TcsSocket socket_ctx, int timeout_ms) { if (socket_ctx == TCS_SOCKET_INVALID || timeout_ms < 0) return TCS_ERROR_INVALID_ARGUMENT; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_RCVTIMEO, &timeout_ms, sizeof(timeout_ms)); } TcsResult tcs_opt_receive_timeout_get(TcsSocket socket_ctx, int* timeout_ms) { if (socket_ctx == TCS_SOCKET_INVALID || timeout_ms == NULL) return TCS_ERROR_INVALID_ARGUMENT; DWORD t = 0; size_t t_size = sizeof(t); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_RCVTIMEO, &t, &t_size); if (sts == TCS_SUCCESS) { *timeout_ms = (int)t; } return sts; } TcsResult tcs_opt_linger_set(TcsSocket socket_ctx, bool do_linger, int timeout_seconds) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; struct linger l = {(u_short)do_linger, (u_short)timeout_seconds}; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_LINGER, &l, sizeof(l)); } TcsResult tcs_opt_linger_get(TcsSocket socket_ctx, bool* do_linger, int* timeout_seconds) { if (socket_ctx == TCS_SOCKET_INVALID || (do_linger == NULL && timeout_seconds == NULL)) return TCS_ERROR_INVALID_ARGUMENT; struct linger l = {0, 0}; size_t l_size = sizeof(l); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_LINGER, &l, &l_size); if (sts == TCS_SUCCESS) { if (do_linger) *do_linger = l.l_onoff; if (timeout_seconds) *timeout_seconds = l.l_linger; } return sts; } // tcs_opt_ip_no_delay_set() is defined in tinycsocket_common.c // tcs_opt_ip_no_delay_get() is defined in tinycsocket_common.c // tcs_opt_out_of_band_inline_set() is defined in tinycsocket_common.c // tcs_opt_out_of_band_inline_get() is defined in tinycsocket_common.c // tcs_opt_priority_set() is defined in tinycsocket_common.c // tcs_opt_priority_get() is defined in tinycsocket_common.c TcsResult tcs_opt_nonblocking_set(TcsSocket socket_ctx, bool do_non_blocking) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; u_long mode = do_non_blocking ? 1 : 0; int sts = ioctlsocket((SOCKET)socket_ctx, (long)FIONBIO, &mode); return socketstatus2retcode(sts); } TcsResult tcs_opt_nonblocking_get(TcsSocket socket_ctx, bool* is_non_blocking) { (void)socket_ctx; (void)is_non_blocking; // Win32 does not provide an API to query the non-blocking state of a socket return TCS_ERROR_NOT_SUPPORTED; } // tcs_opt_membership_add_str() is defined in tinycsocket_common.c TcsResult tcs_opt_membership_add(TcsSocket socket_ctx, const struct TcsAddress* multicast_address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; SOCKADDR_STORAGE address_native_local; memset(&address_native_local, 0, sizeof address_native_local); int address_native_local_size = sizeof address_native_local; if (getsockname((SOCKET)socket_ctx, (PSOCKADDR)&address_native_local, &address_native_local_size) != 0) return wsaerror2retcode(WSAGetLastError()); struct TcsAddress local_address = TCS_ADDRESS_NONE; TcsResult sts = native2sockaddr((PSOCKADDR)&address_native_local, &local_address); if (sts != TCS_SUCCESS) return sts; if (local_address.family != multicast_address->family) return TCS_ERROR_INVALID_ARGUMENT; return tcs_opt_membership_add_to(socket_ctx, &local_address, multicast_address); } TcsResult tcs_opt_membership_add_to(TcsSocket socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* multicast_address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (multicast_address->family == TCS_AF_IP4) { struct ip_mreq imr; memset(&imr, 0, sizeof imr); imr.imr_multiaddr.s_addr = htonl(multicast_address->data.ip4.address); if (local_address != NULL) imr.imr_interface.s_addr = htonl(local_address->data.ip4.address); return tcs_opt_set(socket_ctx, TCS_SOL_IP, TCS_SO_IP_MEMBERSHIP_ADD, &imr, sizeof(imr)); } else if (multicast_address->family == TCS_AF_IP6) { struct ipv6_mreq imr6; memset(&imr6, 0, sizeof imr6); memcpy(&imr6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); if (local_address != NULL) imr6.ipv6mr_interface = (unsigned long)local_address->data.ip6.scope_id; return tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_JOIN_GROUP, &imr6, sizeof(imr6)); } return TCS_ERROR_NOT_IMPLEMENTED; } // tcs_opt_membership_drop_str() is defined in tinycsocket_common.c TcsResult tcs_opt_membership_drop(TcsSocket socket_ctx, const struct TcsAddress* multicast_address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; SOCKADDR_STORAGE address_native_local; memset(&address_native_local, 0, sizeof address_native_local); int address_native_local_size = sizeof address_native_local; if (getsockname((SOCKET)socket_ctx, (PSOCKADDR)&address_native_local, &address_native_local_size) != 0) return wsaerror2retcode(WSAGetLastError()); struct TcsAddress local_address = TCS_ADDRESS_NONE; TcsResult sts = native2sockaddr((PSOCKADDR)&address_native_local, &local_address); if (sts != TCS_SUCCESS) return sts; if (local_address.family != multicast_address->family) return TCS_ERROR_INVALID_ARGUMENT; return tcs_opt_membership_drop_from(socket_ctx, &local_address, multicast_address); } TcsResult tcs_opt_membership_drop_from(TcsSocket socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* multicast_address) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (multicast_address->family == TCS_AF_IP4) { struct ip_mreq imr; memset(&imr, 0, sizeof imr); imr.imr_multiaddr.s_addr = htonl(multicast_address->data.ip4.address); if (local_address != NULL) imr.imr_interface.s_addr = htonl(local_address->data.ip4.address); return tcs_opt_set(socket_ctx, TCS_SOL_IP, TCS_SO_IP_MEMBERSHIP_DROP, &imr, sizeof(imr)); } else if (multicast_address->family == TCS_AF_IP6) { struct ipv6_mreq imr6; memset(&imr6, 0, sizeof imr6); memcpy(&imr6.ipv6mr_multiaddr, multicast_address->data.ip6.address.bytes, 16); if (local_address != NULL) imr6.ipv6mr_interface = (unsigned long)local_address->data.ip6.scope_id; return tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &imr6, sizeof(imr6)); } return TCS_ERROR_NOT_IMPLEMENTED; } TcsResult tcs_opt_multicast_interface_set(TcsSocket socket_ctx, const struct TcsAddress* local_address) { if (socket_ctx == TCS_SOCKET_INVALID || local_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (local_address->family == TCS_AF_IP4) { struct in_addr iface; iface.s_addr = htonl(local_address->data.ip4.address); return tcs_opt_set(socket_ctx, TCS_SOL_IP, IP_MULTICAST_IF, &iface, sizeof(iface)); } else if (local_address->family == TCS_AF_IP6) { unsigned long idx = (unsigned long)local_address->data.ip6.scope_id; return tcs_opt_set(socket_ctx, IPPROTO_IPV6, IPV6_MULTICAST_IF, &idx, sizeof(idx)); } return TCS_ERROR_INVALID_ARGUMENT; } TcsResult tcs_opt_multicast_loop_set(TcsSocket socket_ctx, bool do_loopback) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; DWORD val = do_loopback ? 1 : 0; return tcs_opt_set(socket_ctx, TCS_SOL_IP, IP_MULTICAST_LOOP, &val, sizeof(val)); } TcsResult tcs_opt_multicast_loop_get(TcsSocket socket_ctx, bool* is_loopback) { if (socket_ctx == TCS_SOCKET_INVALID || is_loopback == NULL) return TCS_ERROR_INVALID_ARGUMENT; DWORD val = 0; size_t s = sizeof(val); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_IP, IP_MULTICAST_LOOP, &val, &s); *is_loopback = val; return sts; } // ######## Address and Interface Utilities ######## // Retrieves the user-visible adapter name. // On Vista+ this is the FriendlyName field in IP_ADAPTER_ADDRESSES. // On pre-Vista the FriendlyName field does not exist, so we read the registry: // HKLM\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\\Connection // Value: "Name" (REG_SZ) // Falls back to Description (hardware name) if FriendlyName / registry is unavailable. static TcsResult adapter_get_friendly_name(PIP_ADAPTER_ADDRESSES adapter, char* out_name, int out_name_size) { if (adapter == NULL || out_name == NULL || out_name_size <= 0) return TCS_ERROR_INVALID_ARGUMENT; #if _WIN32_WINNT >= 0x0600 WideCharToMultiByte(CP_UTF8, 0, adapter->FriendlyName, -1, out_name, out_name_size, NULL, NULL); #else const char prefix[] = "SYSTEM\\CurrentControlSet\\Control\\Network\\" "{4D36E972-E325-11CE-BFC1-08002BE10318}\\"; const char suffix[] = "\\Connection"; bool found = false; size_t guid_len = strlen(adapter->AdapterName); size_t key_len = sizeof(prefix) - 1 + guid_len + sizeof(suffix) - 1 + 1; char key_path[256]; if (key_len <= sizeof key_path) { memcpy(key_path, prefix, sizeof(prefix) - 1); memcpy(key_path + sizeof(prefix) - 1, adapter->AdapterName, guid_len); memcpy(key_path + sizeof(prefix) - 1 + guid_len, suffix, sizeof(suffix)); // includes null HKEY hkey = NULL; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, key_path, 0, KEY_READ, &hkey) == ERROR_SUCCESS) { DWORD type = 0; DWORD data_size = (DWORD)out_name_size; if (RegQueryValueExA(hkey, "Name", NULL, &type, (LPBYTE)out_name, &data_size) == ERROR_SUCCESS && type == REG_SZ && data_size > 1) { found = true; } RegCloseKey(hkey); } } if (!found) WideCharToMultiByte(CP_UTF8, 0, adapter->Description, -1, out_name, out_name_size, NULL, NULL); #endif return TCS_SUCCESS; } static TcsResult adapter_is_up(PIP_ADAPTER_ADDRESSES adapter, bool* out_is_up) { if (adapter == NULL || out_is_up == NULL) return TCS_ERROR_INVALID_ARGUMENT; #if _WIN32_WINNT >= 0x0600 *out_is_up = adapter->OperStatus == IfOperStatusUp; #else #ifndef MIB_IF_OPER_STATUS_OPERATIONAL // Not always exposed in all mingw-w64 versions #define MIB_IF_OPER_STATUS_OPERATIONAL 5 #endif MIB_IFROW ifrow; memset(&ifrow, 0, sizeof ifrow); ifrow.dwIndex = adapter->IfIndex; if (GetIfEntry(&ifrow) != NO_ERROR) { *out_is_up = false; return TCS_SUCCESS; } *out_is_up = ifrow.dwOperStatus >= MIB_IF_OPER_STATUS_OPERATIONAL; #endif return TCS_SUCCESS; } TcsResult tcs_interface_list(struct TcsInterface interfaces[], size_t capacity, size_t* out_count) { if (interfaces == NULL && out_count == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (interfaces == NULL && capacity != 0) return TCS_ERROR_INVALID_ARGUMENT; if (out_count != NULL) *out_count = 0; const int MAX_TRIES = 5; ULONG buffer_size = 15000; // From msdn recommendation PIP_ADAPTER_ADDRESSES adapters = NULL; ULONG adapter_sts = ERROR_NO_DATA; for (int i = 0; i < MAX_TRIES; ++i) { adapters = (PIP_ADAPTER_ADDRESSES)malloc(buffer_size); if (adapters == NULL) return TCS_ERROR_MEMORY; adapter_sts = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, NULL, adapters, &buffer_size); if (adapter_sts == ERROR_BUFFER_OVERFLOW) { free(adapters); adapters = NULL; } else { break; } } if (adapter_sts == ERROR_NO_DATA) { if (adapters != NULL) free(adapters); return TCS_SUCCESS; } if (adapter_sts != NO_ERROR) { // Debug: log the actual Windows error code for diagnostics fprintf(stderr, "GetAdaptersAddresses failed with error: %lu (0x%lX)\n", adapter_sts, adapter_sts); if (adapters != NULL) free(adapters); return TCS_ERROR_UNKNOWN; } { size_t i = 0; for (PIP_ADAPTER_ADDRESSES iter = adapters; iter != NULL; iter = iter->Next) { bool is_up = false; TcsResult up_sts = adapter_is_up(iter, &is_up); if (up_sts != TCS_SUCCESS) { free(adapters); return TCS_ERROR_SYSTEM; } if (!is_up) continue; if (interfaces != NULL && i < capacity) { memset(interfaces[i].name, '\0', TCS_INTERFACE_NAME_SIZE); TcsResult name_sts = adapter_get_friendly_name(iter, interfaces[i].name, TCS_INTERFACE_NAME_SIZE - 1); if (name_sts != TCS_SUCCESS) { free(adapters); return TCS_ERROR_SYSTEM; } interfaces[i].id = iter->IfIndex; } if (out_count != NULL) (*out_count)++; ++i; } } free(adapters); return TCS_SUCCESS; } TcsResult tcs_address_resolve(const char* hostname, TcsAddressFamily address_family, struct TcsAddress addresses[], size_t capacity, size_t* out_count) { if (hostname == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (addresses == NULL && out_count == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (out_count != NULL) *out_count = 0; // Fast path: try numeric/MAC parse first to avoid DNS lookup struct TcsAddress parsed = TCS_ADDRESS_NONE; if (tcs_address_parse(hostname, &parsed) == TCS_SUCCESS && parsed.family != TCS_AF_ANY) { if (address_family == TCS_AF_ANY || parsed.family == address_family) { if (addresses != NULL && capacity > 0) addresses[0] = parsed; if (out_count != NULL) *out_count = 1; return TCS_SUCCESS; } } ADDRINFOA native_hints; memset(&native_hints, 0, sizeof native_hints); short native_family; TcsResult sts = family2native(address_family, &native_family); if (sts != TCS_SUCCESS) return sts; native_hints.ai_family = native_family; PADDRINFOA native_addrinfo_list = NULL; int getaddrinfo_status = getaddrinfo(hostname, NULL, &native_hints, &native_addrinfo_list); if (getaddrinfo_status != 0) return TCS_ERROR_ADDRESS_LOOKUP_FAILED; if (native_addrinfo_list == NULL) return TCS_ERROR_UNKNOWN; size_t i = 0; if (addresses == NULL) { for (PADDRINFOA iter = native_addrinfo_list; iter != NULL; iter = iter->ai_next) i++; } else { for (PADDRINFOA iter = native_addrinfo_list; iter != NULL && i < capacity; iter = iter->ai_next) { if (iter->ai_addr == NULL) continue; TcsResult address_convert_status = native2sockaddr(iter->ai_addr, &addresses[i]); if (address_convert_status != TCS_SUCCESS) continue; i++; } } if (out_count != NULL) *out_count = i; freeaddrinfo(native_addrinfo_list); if (i == 0) return TCS_ERROR_ADDRESS_LOOKUP_FAILED; return TCS_SUCCESS; } TcsResult tcs_address_list(unsigned int interface_id_filter, TcsAddressFamily address_family_filter, struct TcsInterfaceAddress interface_addresses[], size_t capacity, size_t* out_count) { if (interface_addresses == NULL && out_count == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (interface_addresses == NULL && capacity != 0) return TCS_ERROR_INVALID_ARGUMENT; if (out_count != NULL) *out_count = 0; const int MAX_TRIES = 5; ULONG buffer_size = 15000; PIP_ADAPTER_ADDRESSES adapters = NULL; ULONG adapter_sts = ERROR_NO_DATA; for (int i = 0; i < MAX_TRIES; ++i) { adapters = (PIP_ADAPTER_ADDRESSES)malloc(buffer_size); if (adapters == NULL) return TCS_ERROR_MEMORY; adapter_sts = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, NULL, adapters, &buffer_size); if (adapter_sts == ERROR_BUFFER_OVERFLOW) { free(adapters); adapters = NULL; } else { break; } } if (adapter_sts == ERROR_NO_DATA) { if (adapters != NULL) free(adapters); return TCS_SUCCESS; } if (adapter_sts != NO_ERROR) { if (adapters != NULL) free(adapters); return TCS_ERROR_UNKNOWN; } size_t populated = 0; for (PIP_ADAPTER_ADDRESSES iter = adapters; iter != NULL; iter = iter->Next) { bool is_up = false; TcsResult up_sts = adapter_is_up(iter, &is_up); if (up_sts != TCS_SUCCESS) { free(adapters); return TCS_ERROR_SYSTEM; } if (!is_up) continue; if (interface_id_filter != 0 && iter->IfIndex != interface_id_filter) continue; for (PIP_ADAPTER_UNICAST_ADDRESS address_iter = iter->FirstUnicastAddress; address_iter != NULL; address_iter = address_iter->Next) { if (address_iter->Address.lpSockaddr == NULL) continue; if (address_family_filter != TCS_AF_ANY) { short native_family; TcsResult family_sts = family2native(address_family_filter, &native_family); if (family_sts == TCS_ERROR_NOT_IMPLEMENTED) continue; if (family_sts != TCS_SUCCESS) { free(adapters); return family_sts; } if (address_iter->Address.lpSockaddr->sa_family != native_family) continue; } struct TcsAddress address = TCS_ADDRESS_NONE; TcsResult convert_sts = native2sockaddr(address_iter->Address.lpSockaddr, &address); if (convert_sts == TCS_ERROR_NOT_IMPLEMENTED) continue; if (convert_sts != TCS_SUCCESS) { free(adapters); return convert_sts; } if (interface_addresses != NULL && populated < capacity) { memset(interface_addresses[populated].iface.name, '\0', TCS_INTERFACE_NAME_SIZE); TcsResult name_sts = adapter_get_friendly_name( iter, interface_addresses[populated].iface.name, TCS_INTERFACE_NAME_SIZE - 1); if (name_sts != TCS_SUCCESS) { free(adapters); return TCS_ERROR_SYSTEM; } interface_addresses[populated].iface.id = iter->IfIndex; interface_addresses[populated].address = address; populated++; if (out_count != NULL) (*out_count)++; } else if (interface_addresses == NULL && out_count != NULL) { (*out_count)++; } } } free(adapters); return TCS_SUCCESS; } TcsResult tcs_address_socket_local(TcsSocket socket_ctx, struct TcsAddress* local_address) { if (socket_ctx == TCS_SOCKET_INVALID || local_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = sizeof native_sockaddr; if (getsockname((SOCKET)socket_ctx, (PSOCKADDR)&native_sockaddr, &addrlen) != 0) return wsaerror2retcode(WSAGetLastError()); return native2sockaddr((PSOCKADDR)&native_sockaddr, local_address); } TcsResult tcs_address_socket_remote(TcsSocket socket_ctx, struct TcsAddress* remote_address) { if (socket_ctx == TCS_SOCKET_INVALID || remote_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = sizeof native_sockaddr; if (getpeername((SOCKET)socket_ctx, (PSOCKADDR)&native_sockaddr, &addrlen) != 0) return wsaerror2retcode(WSAGetLastError()); return native2sockaddr((PSOCKADDR)&native_sockaddr, remote_address); } TcsResult tcs_address_socket_family(TcsSocket socket_ctx, TcsAddressFamily* out_family) { if (socket_ctx == TCS_SOCKET_INVALID || out_family == NULL) return TCS_ERROR_INVALID_ARGUMENT; WSAPROTOCOL_INFOW info; memset(&info, 0, sizeof info); int info_size = sizeof info; if (getsockopt((SOCKET)socket_ctx, SOL_SOCKET, SO_PROTOCOL_INFOW, (char*)&info, &info_size) != 0) return wsaerror2retcode(WSAGetLastError()); TcsAddressFamily family = TCS_AF_ANY; TcsResult sts = native2family((short)info.iAddressFamily, &family); if (sts != TCS_SUCCESS) return sts; *out_family = family; return TCS_SUCCESS; } // tcs_address_parse() is defined in tinycsocket_common.c // tcs_address_to_str() is defined in tinycsocket_common.c // tcs_address_is_equal() is defined in tinycsocket_common.c // tcs_address_is_any() is defined in tinycsocket_common.c // tcs_address_is_link_local() is defined in tinycsocket_common.c // tcs_address_is_loopback() is defined in tinycsocket_common.c // tcs_address_is_multicast() is defined in tinycsocket_common.c // tcs_address_is_broadcast() is defined in tinycsocket_common.c #endif /**********************************/ /****** tinycsocket_common.h ******/ /**********************************/ /* * Copyright 2018 Markus Lindelöw * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files(the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and / or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef TINYCSOCKET_INTERNAL_H_ #include "tinycsocket_internal.h" #endif // This file should never call OS dependent code. Do not include OS files of OS specific ifdefs #ifdef DO_WRAP #include "dbg_wrap.h" #endif #include #include //sprintf #include // malloc, free #include // memset // ######## Library Management ######## // tcs_lib_init() is defined in OS specific files // tcs_lib_free() is defined in OS specific files // ######## Socket Creation ######## // tcs_socket() is defined in OS specific files TcsResult tcs_socket_tcp(TcsSocket* socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* remote_address, int timeout_ms) { if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (local_address == NULL && remote_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (local_address != NULL && remote_address != NULL && local_address->family != remote_address->family) return TCS_ERROR_INVALID_ARGUMENT; if (timeout_ms < 0 && timeout_ms != TCS_WAIT_INF) return TCS_ERROR_INVALID_ARGUMENT; TcsAddressFamily family = local_address != NULL ? local_address->family : remote_address->family; TcsResult res = tcs_socket(socket_ctx, family, TCS_SOCK_STREAM, TCS_PROTOCOL_IP_TCP); if (res != TCS_SUCCESS) return res; if (local_address != NULL) { res = tcs_opt_reuse_address_set(*socket_ctx, true); if (res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } res = tcs_bind(*socket_ctx, local_address); if (res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } } if (remote_address != NULL) { if (timeout_ms == TCS_WAIT_INF) { res = tcs_connect(*socket_ctx, remote_address); if (res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } } else { res = tcs_opt_nonblocking_set(*socket_ctx, true); if (res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } res = tcs_connect(*socket_ctx, remote_address); if (res != TCS_IN_PROGRESS && res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } if (res == TCS_IN_PROGRESS) { struct TcsPool* pool = NULL; res = tcs_pool_create(&pool); if (res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } res = tcs_pool_add(pool, *socket_ctx, NULL, false, true, true); if (res != TCS_SUCCESS) { tcs_pool_destroy(&pool); tcs_close(socket_ctx); return res; } struct TcsPollEvent event = TCS_POOL_EVENT_EMPTY; size_t events_populated = 0; res = tcs_pool_poll(pool, &event, 1, &events_populated, timeout_ms); tcs_pool_destroy(&pool); if (res == TCS_ERROR_TIMED_OUT) { tcs_close(socket_ctx); return TCS_ERROR_TIMED_OUT; } if (res != TCS_SUCCESS || events_populated == 0) { tcs_close(socket_ctx); return res != TCS_SUCCESS ? res : TCS_ERROR_UNKNOWN; } if (event.error != TCS_SUCCESS) { tcs_close(socket_ctx); return TCS_ERROR_CONNECTION_REFUSED; } } res = tcs_opt_nonblocking_set(*socket_ctx, false); if (res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } } } return TCS_SUCCESS; } TcsResult tcs_socket_tcp_str(TcsSocket* socket_ctx, const char* local_address, const char* remote_address, int timeout_ms) { if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (local_address == NULL && remote_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct TcsAddress local_addr = TCS_ADDRESS_NONE; struct TcsAddress remote_addr = TCS_ADDRESS_NONE; TcsAddressFamily family = TCS_AF_ANY; if (local_address != NULL) { size_t count = 0; TcsResult res = tcs_address_resolve(local_address, TCS_AF_ANY, &local_addr, 1, &count); if (res != TCS_SUCCESS) return res; if (count == 0) return TCS_ERROR_ADDRESS_LOOKUP_FAILED; family = local_addr.family; } if (remote_address != NULL) { size_t count = 0; TcsResult res = tcs_address_resolve(remote_address, family, &remote_addr, 1, &count); if (res != TCS_SUCCESS) return res; if (count == 0) return TCS_ERROR_ADDRESS_LOOKUP_FAILED; } return tcs_socket_tcp(socket_ctx, local_address != NULL ? &local_addr : NULL, remote_address != NULL ? &remote_addr : NULL, timeout_ms); } TcsResult tcs_socket_udp(TcsSocket* socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* remote_address) { if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (local_address == NULL && remote_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (local_address != NULL && remote_address != NULL && local_address->family != remote_address->family) return TCS_ERROR_INVALID_ARGUMENT; TcsAddressFamily family = local_address != NULL ? local_address->family : remote_address->family; TcsResult res = tcs_socket(socket_ctx, family, TCS_SOCK_DGRAM, TCS_PROTOCOL_IP_UDP); if (res != TCS_SUCCESS) return res; if (local_address != NULL) { res = tcs_opt_reuse_address_set(*socket_ctx, true); if (res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } res = tcs_bind(*socket_ctx, local_address); if (res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } } if (remote_address != NULL) { bool is_multicast = tcs_address_is_multicast(remote_address); if (is_multicast) { res = tcs_opt_membership_add(*socket_ctx, remote_address); if (res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } } if (!is_multicast || local_address == NULL) { res = tcs_connect(*socket_ctx, remote_address); if (res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } } } return TCS_SUCCESS; } TcsResult tcs_socket_udp_str(TcsSocket* socket_ctx, const char* local_address, const char* remote_address) { if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (local_address == NULL && remote_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct TcsAddress local_addr = TCS_ADDRESS_NONE; struct TcsAddress remote_addr = TCS_ADDRESS_NONE; TcsAddressFamily family = TCS_AF_ANY; if (local_address != NULL) { size_t count = 0; TcsResult res = tcs_address_resolve(local_address, TCS_AF_ANY, &local_addr, 1, &count); if (res != TCS_SUCCESS) return res; if (count == 0) return TCS_ERROR_ADDRESS_LOOKUP_FAILED; family = local_addr.family; } if (remote_address != NULL) { size_t count = 0; TcsResult res = tcs_address_resolve(remote_address, family, &remote_addr, 1, &count); if (res != TCS_SUCCESS) return res; if (count == 0) return TCS_ERROR_ADDRESS_LOOKUP_FAILED; } return tcs_socket_udp( socket_ctx, local_address != NULL ? &local_addr : NULL, remote_address != NULL ? &remote_addr : NULL); } TcsResult tcs_socket_packet(TcsSocket* socket_ctx, const struct TcsAddress* bind_address, int type) { if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (bind_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (bind_address->family != TCS_AF_PACKET) return TCS_ERROR_INVALID_ARGUMENT; if (type != TCS_SOCK_RAW && type != TCS_SOCK_DGRAM) return TCS_ERROR_INVALID_ARGUMENT; TcsResult res = tcs_socket(socket_ctx, TCS_AF_PACKET, type, bind_address->data.packet.protocol); if (res != TCS_SUCCESS) return res; res = tcs_bind(*socket_ctx, bind_address); if (res != TCS_SUCCESS) { tcs_close(socket_ctx); return res; } return TCS_SUCCESS; } TcsResult tcs_socket_packet_str(TcsSocket* socket_ctx, const char* interface_name, uint16_t protocol, int type) { if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (interface_name == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct TcsInterface stack_buf[16]; struct TcsInterface* interfaces = stack_buf; size_t count = 0; TcsResult res = tcs_interface_list(stack_buf, 16, &count); if (res != TCS_SUCCESS) return res; size_t search_count = count < 16 ? count : 16; for (size_t i = 0; i < search_count; ++i) { if (strcmp(stack_buf[i].name, interface_name) == 0) { struct TcsAddress bind_address = TCS_ADDRESS_NONE; bind_address.family = TCS_AF_PACKET; bind_address.data.packet.interface_id = stack_buf[i].id; bind_address.data.packet.protocol = protocol; return tcs_socket_packet(socket_ctx, &bind_address, type); } } if (count > 16) { interfaces = (struct TcsInterface*)malloc(count * sizeof(struct TcsInterface)); if (interfaces == NULL) return TCS_ERROR_MEMORY; res = tcs_interface_list(interfaces, count, &count); if (res != TCS_SUCCESS) { free(interfaces); return res; } } for (size_t i = 0; i < count; ++i) { if (strcmp(interfaces[i].name, interface_name) == 0) { struct TcsAddress bind_address = TCS_ADDRESS_NONE; bind_address.family = TCS_AF_PACKET; bind_address.data.packet.interface_id = interfaces[i].id; bind_address.data.packet.protocol = protocol; if (interfaces != stack_buf) free(interfaces); return tcs_socket_packet(socket_ctx, &bind_address, type); } } if (interfaces != stack_buf) free(interfaces); return TCS_ERROR_INVALID_ARGUMENT; } // tcs_close() is defined in OS specific files // ######## Socket Operations ######## // tcs_bind() is defined in OS specific files // tcs_connect() is defined in OS specific files TcsResult tcs_connect_str(TcsSocket socket_ctx, const char* remote_address, uint16_t port) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (remote_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; TcsAddressFamily socket_family = TCS_AF_ANY; TcsResult res = tcs_address_socket_family(socket_ctx, &socket_family); if (res != TCS_SUCCESS) return res; if (socket_family != TCS_AF_IP4 && socket_family != TCS_AF_IP6) return TCS_ERROR_NOT_IMPLEMENTED; struct TcsAddress found_addresses; size_t no_of_found_addresses = 0; res = tcs_address_resolve(remote_address, socket_family, &found_addresses, 1, &no_of_found_addresses); if (res != TCS_SUCCESS) return res; if (no_of_found_addresses == 0) return TCS_ERROR_ADDRESS_LOOKUP_FAILED; switch (socket_family) { case TCS_AF_IP4: if (found_addresses.data.ip4.port == 0) found_addresses.data.ip4.port = port; else if (port != 0) return TCS_ERROR_INVALID_ARGUMENT; break; case TCS_AF_IP6: if (found_addresses.data.ip6.port == 0) found_addresses.data.ip6.port = port; else if (port != 0) return TCS_ERROR_INVALID_ARGUMENT; break; case TCS_AF_ANY: break; default: return TCS_ERROR_INVALID_ARGUMENT; } return tcs_connect(socket_ctx, &found_addresses); } // tcs_listen() is defined in OS specific files // tcs_accept() is defined in OS specific files // tcs_shutdown() is defined in OS specific files // ######## Data Transfer ######## // tcs_send() is defined in OS specific files // tcs_send_to() is defined in OS specific files // tcs_sendv() is defined in OS specific files TcsResult tcs_send_netstring(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_length) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; if (buffer == NULL || buffer_length == 0) return TCS_ERROR_INVALID_ARGUMENT; #if SIZE_MAX > 0xffffffffffffffffULL // buffer_length bigger than 64 bits? (size_t can be bigger on some systems) if (buffer_length > 0xffffffffffffffffULL) return TCS_ERROR_INVALID_ARGUMENT; #endif int header_length = 0; char netstring_header[21]; memset(netstring_header, 0, sizeof netstring_header); // %zu is not supported by all compilers, therefor we cast it to llu header_length = snprintf(netstring_header, 21, "%llu:", (unsigned long long)buffer_length); if (header_length < 0) return TCS_ERROR_INVALID_ARGUMENT; TcsResult sts = TCS_SUCCESS; sts = tcs_send(socket_ctx, (uint8_t*)netstring_header, (size_t)header_length, TCS_MSG_SENDALL, NULL); if (sts != TCS_SUCCESS) return sts; sts = tcs_send(socket_ctx, buffer, buffer_length, TCS_MSG_SENDALL, NULL); if (sts != TCS_SUCCESS) return sts; sts = tcs_send(socket_ctx, (const uint8_t*)",", 1, TCS_MSG_SENDALL, NULL); if (sts != TCS_SUCCESS) return sts; return TCS_SUCCESS; } // tcs_receive() is defined in OS specific files // tcs_receive_from() is defined in OS specific files TcsResult tcs_receive_line(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_length, size_t* bytes_received, uint8_t delimiter) { if (socket_ctx == TCS_SOCKET_INVALID || buffer == NULL || buffer_length == 0) return TCS_ERROR_INVALID_ARGUMENT; /* * data in kernel buffer * |12345yyyyyyyyyyyyyyyyyyyyyyyy..............| * * buffer_length ----------------------------------. * searched ----------------------. | * | | * bytes_peeked ------------------. | * bytes_read ---------------. | | * v v v * data in arg buffer * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx12345................| */ size_t bytes_read = 0; size_t bytes_peeked = 0; size_t bytes_searched = 0; while (bytes_read < buffer_length) { TcsResult sts = TCS_SUCCESS; size_t bytes_free_in_buffer = buffer_length - bytes_read; size_t current_peeked = 0; sts = tcs_receive(socket_ctx, buffer + bytes_read, bytes_free_in_buffer, TCS_MSG_PEEK, ¤t_peeked); if (sts != TCS_SUCCESS) { if (bytes_received != NULL) *bytes_received = bytes_read; return sts; } bytes_peeked += current_peeked; if (current_peeked == 0) { // Make sure we block so we do not fast loop previous PEEK. // Can not assume that peek with waitall is not crossplatform, needs to read size_t current_read = 0; sts = tcs_receive(socket_ctx, buffer + bytes_read, 1, TCS_MSG_WAITALL, ¤t_read); bytes_read += current_read; bytes_peeked += current_read; if (sts != TCS_SUCCESS) { if (bytes_received != NULL) *bytes_received = bytes_read; return sts; } } bool found_delimiter = false; while (bytes_searched < bytes_peeked) { if (buffer[bytes_searched++] == delimiter) { found_delimiter = true; break; } } // byte_searched == bytes_peeked if no delimiter was found // after this block, bytes_read will also has the same value as they have if (bytes_searched > bytes_read) { size_t bytes = 0; size_t bytes_to_read_to_catch_up = bytes_searched - bytes_read; sts = tcs_receive(socket_ctx, buffer + bytes_read, bytes_to_read_to_catch_up, TCS_MSG_WAITALL, &bytes); bytes_read += bytes; if (sts != TCS_SUCCESS) { if (bytes_received != NULL) *bytes_received = bytes_read; return sts; } } if (found_delimiter) { if (bytes_received != NULL) *bytes_received = bytes_read; return sts; } } if (bytes_received != NULL) *bytes_received = bytes_read; return TCS_AGAIN; } TcsResult tcs_receive_netstring(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_length, size_t* bytes_received) { if (socket_ctx == TCS_SOCKET_INVALID || buffer == NULL || buffer_length == 0) return TCS_ERROR_INVALID_ARGUMENT; size_t expected_length = 0; int parsed = 0; TcsResult sts = TCS_SUCCESS; char t = '\0'; const int max_header = 21; while (t != ':' && parsed < max_header) { sts = tcs_receive(socket_ctx, (uint8_t*)&t, 1, TCS_MSG_WAITALL, NULL); if (sts != TCS_SUCCESS) return sts; parsed += 1; bool is_num = t >= '0' && t <= '9'; bool is_end = t == ':'; if (!is_num && !is_end) return TCS_ERROR_ILL_FORMED_MESSAGE; if (is_end) break; size_t digit = (size_t)t - '0'; if (expected_length > (SIZE_MAX - digit) / 10) return TCS_ERROR_ILL_FORMED_MESSAGE; expected_length = expected_length * 10 + digit; } if (parsed >= max_header) return TCS_ERROR_ILL_FORMED_MESSAGE; if (buffer_length < expected_length) return TCS_ERROR_MEMORY; sts = tcs_receive(socket_ctx, buffer, expected_length, TCS_MSG_WAITALL, NULL); if (sts != TCS_SUCCESS) return sts; sts = tcs_receive(socket_ctx, (uint8_t*)&t, 1, TCS_MSG_WAITALL, NULL); if (sts != TCS_SUCCESS) return sts; if (t != ',') return TCS_ERROR_ILL_FORMED_MESSAGE; if (bytes_received != NULL) *bytes_received = expected_length; return TCS_SUCCESS; } // ######## Socket Pooling ######## // tcs_pool_create() is defined in OS specific files // tcs_pool_destroy() is defined in OS specific files // tcs_pool_add() is defined in OS specific files // tcs_pool_remove() is defined in OS specific files // tcs_pool_poll() is defined in OS specific files // ######## Socket Options ######## // tcs_opt_set() is defined in OS specific files // tcs_opt_get() is defined in OS specific files TcsResult tcs_opt_broadcast_set(TcsSocket socket_ctx, bool do_allow_broadcast) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; int b = do_allow_broadcast ? 1 : 0; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_BROADCAST, &b, sizeof(b)); } TcsResult tcs_opt_type_get(TcsSocket socket_ctx, int* type) { if (socket_ctx == TCS_SOCKET_INVALID || type == NULL) return TCS_ERROR_INVALID_ARGUMENT; int t = 0; size_t s = sizeof(t); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_TYPE, &t, &s); *type = t; return sts; } TcsResult tcs_opt_broadcast_get(TcsSocket socket_ctx, bool* is_broadcast_allowed) { if (socket_ctx == TCS_SOCKET_INVALID || is_broadcast_allowed == NULL) return TCS_ERROR_INVALID_ARGUMENT; int b = 0; size_t s = sizeof(b); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_BROADCAST, &b, &s); *is_broadcast_allowed = b; return sts; } TcsResult tcs_opt_keep_alive_set(TcsSocket socket_ctx, bool do_keep_alive) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; int b = do_keep_alive ? 1 : 0; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_KEEPALIVE, &b, sizeof(b)); } TcsResult tcs_opt_keep_alive_get(TcsSocket socket_ctx, bool* is_keep_alive_enabled) { if (socket_ctx == TCS_SOCKET_INVALID || is_keep_alive_enabled == NULL) return TCS_ERROR_INVALID_ARGUMENT; int b = 0; size_t s = sizeof(b); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_KEEPALIVE, &b, &s); *is_keep_alive_enabled = b; return sts; } // tcs_opt_reuse_address_set() is defined in platform-specific files // tcs_opt_reuse_address_get() is defined in platform-specific files TcsResult tcs_opt_send_buffer_size_set(TcsSocket socket_ctx, size_t send_buffer_size) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; unsigned int b = (unsigned int)send_buffer_size; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_SNDBUF, &b, sizeof(b)); } TcsResult tcs_opt_send_buffer_size_get(TcsSocket socket_ctx, size_t* send_buffer_size) { if (socket_ctx == TCS_SOCKET_INVALID || send_buffer_size == NULL) return TCS_ERROR_INVALID_ARGUMENT; unsigned int b = 0; size_t s = sizeof(b); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_SNDBUF, &b, &s); *send_buffer_size = (size_t)b; return sts; } TcsResult tcs_opt_receive_buffer_size_set(TcsSocket socket_ctx, size_t receive_buffer_size) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; unsigned int b = (unsigned int)receive_buffer_size; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_RCVBUF, &b, sizeof(b)); } TcsResult tcs_opt_receive_buffer_size_get(TcsSocket socket_ctx, size_t* receive_buffer_size) { if (socket_ctx == TCS_SOCKET_INVALID || receive_buffer_size == NULL) return TCS_ERROR_INVALID_ARGUMENT; unsigned int b = 0; size_t s = sizeof(b); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_RCVBUF, &b, &s); *receive_buffer_size = (size_t)b; return sts; } // tcs_opt_receive_timout_set() is defined in OS specific files // tcs_opt_receive_timout_get() is defined in OS specific files // tcs_opt_linger_set() is defined in OS specific files // tcs_opt_linger_get() is defined in OS specific files TcsResult tcs_opt_ip_no_delay_set(TcsSocket socket_ctx, bool use_no_delay) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; int b = use_no_delay ? 1 : 0; return tcs_opt_set(socket_ctx, TCS_SOL_IP, TCS_SO_IP_NODELAY, &b, sizeof(b)); } TcsResult tcs_opt_ip_no_delay_get(TcsSocket socket_ctx, bool* is_no_delay_used) { if (socket_ctx == TCS_SOCKET_INVALID || is_no_delay_used == NULL) return TCS_ERROR_INVALID_ARGUMENT; int b = 0; size_t s = sizeof(b); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_IP, TCS_SO_IP_NODELAY, &b, &s); *is_no_delay_used = b; return sts; } TcsResult tcs_opt_out_of_band_inline_set(TcsSocket socket_ctx, bool enable_oob) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; int b = enable_oob ? 1 : 0; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_OOBINLINE, &b, sizeof(b)); } TcsResult tcs_opt_out_of_band_inline_get(TcsSocket socket_ctx, bool* is_oob_enabled) { if (socket_ctx == TCS_SOCKET_INVALID || is_oob_enabled == NULL) return TCS_ERROR_INVALID_ARGUMENT; int b = 0; size_t s = sizeof(b); TcsResult sts = tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_OOBINLINE, &b, &s); *is_oob_enabled = b; return sts; } TcsResult tcs_opt_priority_set(TcsSocket socket_ctx, int priority) { if (socket_ctx == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; return tcs_opt_set(socket_ctx, TCS_SOL_SOCKET, TCS_SO_PRIORITY, &priority, sizeof(priority)); } TcsResult tcs_opt_priority_get(TcsSocket socket_ctx, int* priority) { if (socket_ctx == TCS_SOCKET_INVALID || priority == NULL) return TCS_ERROR_INVALID_ARGUMENT; size_t s = sizeof(*priority); return tcs_opt_get(socket_ctx, TCS_SOL_SOCKET, TCS_SO_PRIORITY, priority, &s); } // tcs_opt_nonblocking_set() is defined in OS specific files // tcs_opt_nonblocking_get() is defined in OS specific files // tcs_opt_membership_add() is defined in OS specific files TcsResult tcs_opt_membership_add_str(TcsSocket socket_ctx, const char* multicast_address) { if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct TcsAddress addr = TCS_ADDRESS_NONE; TcsResult res = tcs_address_parse(multicast_address, &addr); if (res != TCS_SUCCESS) return res; return tcs_opt_membership_add(socket_ctx, &addr); } // tcs_opt_membership_add_to() is defined in OS specific files // tcs_opt_membership_drop() is defined in OS specific files TcsResult tcs_opt_membership_drop_str(TcsSocket socket_ctx, const char* multicast_address) { if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; struct TcsAddress addr = TCS_ADDRESS_NONE; TcsResult res = tcs_address_parse(multicast_address, &addr); if (res != TCS_SUCCESS) return res; return tcs_opt_membership_drop(socket_ctx, &addr); } // tcs_opt_membership_drop_from() is defined in OS specific files // ######## Address and Interface Utilities ######## // tcs_interface_list() is defined in OS specific files // tcs_address_resolve() is defined in OS specific files // tcs_address_list() is defined in OS specific files // tcs_address_socket_local() is defined in OS specific files // tcs_address_socket_remote() is defined in OS specific files // tcs_address_socket_family() is defined in OS specific files TcsResult tcs_address_parse(const char str[], struct TcsAddress* out_address) { if (out_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (str == NULL) return TCS_ERROR_INVALID_ARGUMENT; if (str[0] == '\0') { *out_address = TCS_ADDRESS_NONE; return TCS_SUCCESS; } memset(out_address, 0, sizeof(struct TcsAddress)); // Slow but easy parser int n_colons = 0; int n_dots = 0; int double_colons = 0; for (int i = 0; str[i] != '\0' && i < 70; ++i) { if (str[i] == '.') { n_dots++; } else if (str[i] == ':') { n_colons++; if (i > 0 && str[i - 1] == ':') double_colons++; } } bool is_ipv4 = n_dots == 3 && n_colons <= 1; bool is_mac = n_colons == 5 && double_colons == 0; bool is_ipv6 = !is_ipv4 && !is_mac && n_colons > 1 && double_colons <= 1; if (is_ipv4 + is_mac + is_ipv6 != 1) return TCS_ERROR_INVALID_ARGUMENT; if (is_ipv4) { int b1; int b2; int b3; int b4; int p = 0; int parsed_args = sscanf(str, "%i.%i.%i.%i:%i", &b1, &b2, &b3, &b4, &p); if (parsed_args != 4 && parsed_args != 5) return TCS_ERROR_INVALID_ARGUMENT; if ((uint8_t)(b1 & 0xFF) != b1 || (uint8_t)(b2 & 0xFF) != b2 || (uint8_t)(b3 & 0xFF) != b3 || (uint8_t)(b4 & 0xFF) != b4) return TCS_ERROR_INVALID_ARGUMENT; if (p < 0 || p > 65535) return TCS_ERROR_INVALID_ARGUMENT; out_address->family = TCS_AF_IP4; out_address->data.ip4.address = (uint32_t)b1 << 24 | (uint32_t)b2 << 16 | (uint32_t)b3 << 8 | (uint32_t)b4; out_address->data.ip4.port = (uint16_t)p; } else if (is_ipv6) { // Table-driven DFA transducer (RFC 4291 + RFC 3986 + RFC 4007 + RFC 5952) // Character classes (CC_OTHER = 0 so uninitialized LUT entries default to it) enum { CC_OTHER = 0, CC_DIGIT, CC_HEX, CC_COLON, CC_DOT, CC_PERCENT, CC_LBRACKET, CC_RBRACKET, CC_NUL, CC_COUNT }; // clang-format off static const uint8_t cc_lut[256] = { /* 0x00 NUL */ CC_NUL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 SPACE ! " # $ % & ' ( ) * + , - . / */ 0, 0, 0, 0, 0, CC_PERCENT, 0, 0, 0, 0, 0, 0, 0, 0, CC_DOT, 0, /* 0x30 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_DIGIT, CC_COLON, 0, 0, 0, 0, 0, /* 0x40 @ A B C D E F G H I J K L M N O */ 0, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 P Q R S T U V W X Y Z [ \ ] ^ _ */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CC_LBRACKET,0, CC_RBRACKET,0, 0, /* 0x60 ` a b c d e f */ 0, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, CC_HEX, /* ... zeros ... */ }; // clang-format on // DFA states enum { S_START, S_BRACKET, S_HEX, S_HEX_DEC, S_COLON, S_DCOLON, S_DOT, S_IPV4, S_RBRACKET, S_PORT_COLON, S_PORT, S_PERCENT, S_SCOPE, S_ACCEPT, S_REJECT, S_COUNT }; // Actions enum { A_NONE, A_OPEN_BRACKET, A_HEX_ACCUM, A_HEX_DEC_ACCUM, A_COMMIT, A_COMMIT_BRACKET, A_SET_GAP, A_CHECK_BRACKET, A_START_IPV4, A_IPV4_ACCUM, A_IPV4_DOT, A_IPV4_DONE, A_IPV4_DONE_BRACKET, A_PORT_ACCUM, A_SCOPE_ACCUM, A_SCOPE_BRACKET }; // clang-format off static const struct { /* StateType */ uint8_t next; /* ActionType */ uint8_t action; } dfa_table[S_COUNT][CC_COUNT] /* States x Characters */ = { /* CC_OTHER CC_DIGIT CC_HEX CC_COLON CC_DOT CC_PERCENT CC_LBRACKET CC_RBRACKET CC_NUL */ /* S_START */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_BRACKET,A_OPEN_BRACKET}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, /* S_BRACKET */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, /* S_HEX */ {{S_REJECT,A_NONE}, {S_HEX,A_HEX_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_COMMIT}, {S_REJECT,A_NONE}, {S_PERCENT,A_COMMIT}, {S_REJECT,A_NONE}, {S_RBRACKET,A_COMMIT_BRACKET}, {S_ACCEPT,A_COMMIT}}, /* S_HEX_DEC */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_COLON,A_COMMIT}, {S_DOT,A_START_IPV4}, {S_PERCENT,A_COMMIT}, {S_REJECT,A_NONE}, {S_RBRACKET,A_COMMIT_BRACKET}, {S_ACCEPT,A_COMMIT}}, /* S_COLON */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_DCOLON,A_SET_GAP}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, /* S_DCOLON */ {{S_REJECT,A_NONE}, {S_HEX_DEC,A_HEX_DEC_ACCUM}, {S_HEX,A_HEX_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_RBRACKET,A_CHECK_BRACKET}, {S_ACCEPT,A_NONE}}, /* S_DOT */ {{S_REJECT,A_NONE}, {S_IPV4,A_IPV4_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, /* S_IPV4 */ {{S_REJECT,A_NONE}, {S_IPV4,A_IPV4_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_DOT,A_IPV4_DOT}, {S_PERCENT,A_IPV4_DONE},{S_REJECT,A_NONE}, {S_RBRACKET,A_IPV4_DONE_BRACKET},{S_ACCEPT,A_IPV4_DONE}}, /* S_RBRACKET */ {{S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_PORT_COLON,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_ACCEPT,A_NONE}}, /* S_PORT_COLON*/ {{S_REJECT,A_NONE}, {S_PORT,A_PORT_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, /* S_PORT */ {{S_REJECT,A_NONE}, {S_PORT,A_PORT_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_ACCEPT,A_NONE}}, /* S_PERCENT */ {{S_REJECT,A_NONE}, {S_SCOPE,A_SCOPE_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, /* S_SCOPE */ {{S_REJECT,A_NONE}, {S_SCOPE,A_SCOPE_ACCUM}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_RBRACKET,A_SCOPE_BRACKET}, {S_ACCEPT,A_NONE}}, /* S_ACCEPT */ {{S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, /* S_REJECT */ {{S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}, {S_REJECT,A_NONE}}, }; // clang-format on struct { uint16_t groups[8]; int group_count; int gap_pos; int32_t hex_val; int32_t dec_val; int digits; int32_t port_val; int64_t scope_id; int32_t ipv4_octets[4]; int ipv4_index; uint8_t state; bool in_bracket; } ctx = {{0}, 0, -1, 0, 0, 0, 0, 0, {0}, 0, S_START, false}; const char* c = str; while (ctx.state != S_ACCEPT && ctx.state != S_REJECT) { int cc = cc_lut[(unsigned char)*c]; int next = dfa_table[ctx.state][cc].next; int action = dfa_table[ctx.state][cc].action; // Execute action switch (action) { case A_NONE: break; case A_OPEN_BRACKET: ctx.in_bracket = true; break; case A_HEX_ACCUM: { int32_t d = (*c >= '0' && *c <= '9') ? (*c - '0') : (*c >= 'a' && *c <= 'f') ? (*c - 'a' + 10) : (*c - 'A' + 10); ctx.hex_val = (ctx.hex_val << 4) | d; ctx.digits++; if (ctx.digits > 4) return TCS_ERROR_INVALID_ARGUMENT; break; } case A_HEX_DEC_ACCUM: { int32_t d = *c - '0'; ctx.hex_val = (ctx.hex_val << 4) | d; ctx.dec_val = ctx.dec_val * 10 + d; ctx.digits++; if (ctx.digits > 4) return TCS_ERROR_INVALID_ARGUMENT; break; } case A_COMMIT: if (ctx.group_count >= 8) return TCS_ERROR_INVALID_ARGUMENT; ctx.groups[ctx.group_count++] = (uint16_t)ctx.hex_val; ctx.hex_val = 0; ctx.dec_val = 0; ctx.digits = 0; break; case A_COMMIT_BRACKET: if (!ctx.in_bracket) return TCS_ERROR_INVALID_ARGUMENT; if (ctx.group_count >= 8) return TCS_ERROR_INVALID_ARGUMENT; ctx.groups[ctx.group_count++] = (uint16_t)ctx.hex_val; ctx.hex_val = 0; ctx.dec_val = 0; ctx.digits = 0; ctx.in_bracket = false; break; case A_SET_GAP: if (ctx.gap_pos >= 0) return TCS_ERROR_INVALID_ARGUMENT; ctx.gap_pos = ctx.group_count; break; case A_CHECK_BRACKET: if (!ctx.in_bracket) return TCS_ERROR_INVALID_ARGUMENT; ctx.in_bracket = false; break; case A_START_IPV4: if (ctx.dec_val > 255) return TCS_ERROR_INVALID_ARGUMENT; ctx.ipv4_octets[0] = ctx.dec_val; ctx.ipv4_index = 1; ctx.dec_val = 0; ctx.hex_val = 0; ctx.digits = 0; break; case A_IPV4_ACCUM: ctx.dec_val = ctx.dec_val * 10 + (*c - '0'); if (ctx.dec_val > 255) return TCS_ERROR_INVALID_ARGUMENT; break; case A_IPV4_DOT: if (ctx.ipv4_index >= 4) return TCS_ERROR_INVALID_ARGUMENT; ctx.ipv4_octets[ctx.ipv4_index++] = ctx.dec_val; ctx.dec_val = 0; break; case A_IPV4_DONE: case A_IPV4_DONE_BRACKET: if (action == A_IPV4_DONE_BRACKET) { if (!ctx.in_bracket) return TCS_ERROR_INVALID_ARGUMENT; ctx.in_bracket = false; } if (ctx.ipv4_index != 3) return TCS_ERROR_INVALID_ARGUMENT; ctx.ipv4_octets[3] = ctx.dec_val; if (ctx.group_count + 2 > 8) return TCS_ERROR_INVALID_ARGUMENT; ctx.groups[ctx.group_count++] = (uint16_t)(ctx.ipv4_octets[0] << 8 | ctx.ipv4_octets[1]); ctx.groups[ctx.group_count++] = (uint16_t)(ctx.ipv4_octets[2] << 8 | ctx.ipv4_octets[3]); break; case A_PORT_ACCUM: ctx.port_val = ctx.port_val * 10 + (*c - '0'); if ((uint32_t)ctx.port_val > UINT16_MAX) return TCS_ERROR_INVALID_ARGUMENT; break; case A_SCOPE_ACCUM: ctx.scope_id = ctx.scope_id * 10 + (*c - '0'); if (ctx.scope_id > UINT32_MAX) return TCS_ERROR_INVALID_ARGUMENT; break; case A_SCOPE_BRACKET: if (!ctx.in_bracket) return TCS_ERROR_INVALID_ARGUMENT; ctx.in_bracket = false; break; } ctx.state = (uint8_t)next; if (*c != '\0') c++; } if (ctx.state == S_REJECT) return TCS_ERROR_INVALID_ARGUMENT; // Unclosed bracket if (ctx.in_bracket) return TCS_ERROR_INVALID_ARGUMENT; // Expand :: gap into zero groups if (ctx.gap_pos >= 0) { int32_t gap_size = 8 - ctx.group_count; if (gap_size < 1) return TCS_ERROR_INVALID_ARGUMENT; int32_t after_gap = ctx.group_count - ctx.gap_pos; for (int32_t i = after_gap - 1; i >= 0; i--) ctx.groups[ctx.gap_pos + gap_size + i] = ctx.groups[ctx.gap_pos + i]; for (int32_t i = 0; i < gap_size; i++) ctx.groups[ctx.gap_pos + i] = 0; } else if (ctx.group_count != 8) { return TCS_ERROR_INVALID_ARGUMENT; } out_address->family = TCS_AF_IP6; for (int i = 0; i < 8; i++) { out_address->data.ip6.address.bytes[i * 2] = (uint8_t)(ctx.groups[i] >> 8); out_address->data.ip6.address.bytes[i * 2 + 1] = (uint8_t)(ctx.groups[i] & 0xFF); } out_address->data.ip6.port = (uint16_t)ctx.port_val; out_address->data.ip6.scope_id = (TcsInterfaceId)ctx.scope_id; } else if (is_mac) { unsigned int b1; unsigned int b2; unsigned int b3; unsigned int b4; unsigned int b5; unsigned int b6; int parsed_args = sscanf(str, "%x:%x:%x:%x:%x:%x", &b1, &b2, &b3, &b4, &b5, &b6); if (parsed_args != 6) return TCS_ERROR_INVALID_ARGUMENT; if ((uint8_t)(b1 & 0xFF) != b1 || (uint8_t)(b2 & 0xFF) != b2 || (uint8_t)(b3 & 0xFF) != b3 || (uint8_t)(b4 & 0xFF) != b4 || (uint8_t)(b5 & 0xFF) != b5 || (uint8_t)(b6 & 0xFF) != b6) return TCS_ERROR_INVALID_ARGUMENT; out_address->family = TCS_AF_PACKET; out_address->data.packet.mac[0] = (uint8_t)b1; out_address->data.packet.mac[1] = (uint8_t)b2; out_address->data.packet.mac[2] = (uint8_t)b3; out_address->data.packet.mac[3] = (uint8_t)b4; out_address->data.packet.mac[4] = (uint8_t)b5; out_address->data.packet.mac[5] = (uint8_t)b6; out_address->data.packet.interface_id = 0; // Must be set before use out_address->data.packet.protocol = 0; // Must be set before use // TODO: Check if we have a local interface with this mac address } else { return TCS_ERROR_NOT_IMPLEMENTED; } return TCS_SUCCESS; } TcsResult tcs_address_to_str(const struct TcsAddress* address, char str[70]) { if (address == NULL || str == NULL) return TCS_ERROR_INVALID_ARGUMENT; memset(str, 0, 70); if (address->family == TCS_AF_IP4) { uint32_t d = address->data.ip4.address; uint16_t p = address->data.ip4.port; uint8_t b1 = (uint8_t)(d & 0xFF); uint8_t b2 = (uint8_t)((d >> 8) & 0xFF); uint8_t b3 = (uint8_t)((d >> 16) & 0xFF); uint8_t b4 = (uint8_t)((d >> 24) & 0xFF); if (p == 0) snprintf(str, 70, "%i.%i.%i.%i", b4, b3, b2, b1); else snprintf(str, 70, "%i.%i.%i.%i:%i", b4, b3, b2, b1, p); } else if (address->family == TCS_AF_IP6) { uint16_t groups[8]; for (int i = 0; i < 8; i++) groups[i] = (uint16_t)((unsigned int)address->data.ip6.address.bytes[i * 2] << 8 | address->data.ip6.address.bytes[i * 2 + 1]); // Find longest run of consecutive zero groups for :: compression (RFC 5952) int best_start = -1; int best_len = 0; int cur_start = -1; int cur_len = 0; for (int i = 0; i < 8; i++) { if (groups[i] == 0) { if (cur_start < 0) cur_start = i; cur_len++; if (cur_len > best_len) { best_start = cur_start; best_len = cur_len; } } else { cur_start = -1; cur_len = 0; } } if (best_len < 2) best_start = -1; char addr_str[46]; int pos = 0; for (int i = 0; i < 8; i++) { if (i == best_start) { pos += snprintf(addr_str + pos, sizeof addr_str - (size_t)pos, "::"); i += best_len - 1; continue; } if (i > 0 && (best_start < 0 || i != best_start + best_len)) addr_str[pos++] = ':'; pos += snprintf(addr_str + pos, sizeof addr_str - (size_t)pos, "%x", (unsigned int)groups[i]); } addr_str[pos] = '\0'; uint16_t p = address->data.ip6.port; TcsInterfaceId sc = address->data.ip6.scope_id; if (p != 0 && sc != 0) snprintf(str, 70, "[%s%%%u]:%u", addr_str, (unsigned int)sc, (unsigned int)p); else if (p != 0) snprintf(str, 70, "[%s]:%u", addr_str, (unsigned int)p); else if (sc != 0) snprintf(str, 70, "%s%%%u", addr_str, (unsigned int)sc); else snprintf(str, 70, "%s", addr_str); } else if (address->family == TCS_AF_PACKET) { snprintf(str, 70, "%02X:%02X:%02X:%02X:%02X:%02X", address->data.packet.mac[0], address->data.packet.mac[1], address->data.packet.mac[2], address->data.packet.mac[3], address->data.packet.mac[4], address->data.packet.mac[5]); } else { return TCS_ERROR_NOT_IMPLEMENTED; } return TCS_SUCCESS; } bool tcs_address_is_equal(const struct TcsAddress* l, const struct TcsAddress* r) { if (l == r) // pointer equality also covers NULL == NULL return true; if (l == NULL || r == NULL) return false; if (l->family != r->family) return false; switch (l->family) { case TCS_AF_ANY: return true; // We consider any address equal to any address case TCS_AF_IP4: return l->data.ip4.address == r->data.ip4.address && l->data.ip4.port == r->data.ip4.port; case TCS_AF_IP6: return memcmp(l->data.ip6.address.bytes, r->data.ip6.address.bytes, 16) == 0 && l->data.ip6.port == r->data.ip6.port; case TCS_AF_PACKET: return memcmp(l->data.packet.mac, r->data.packet.mac, 6) == 0 && l->data.packet.protocol == r->data.packet.protocol && l->data.packet.interface_id == r->data.packet.interface_id; default: return false; } } bool tcs_address_is_any(const struct TcsAddress* addr) { if (addr == NULL) return false; switch (addr->family) { case TCS_AF_IP4: return addr->data.ip4.address == TCS_ADDRESS_ANY_IP4; case TCS_AF_IP6: { static const uint8_t any6[16] = {0}; return memcmp(addr->data.ip6.address.bytes, any6, 16) == 0; } default: return false; } } bool tcs_address_is_link_local(const struct TcsAddress* addr) { if (addr == NULL) return false; switch (addr->family) { case TCS_AF_IP4: return (addr->data.ip4.address >> 16) == 0xA9FE; // 169.254.0.0/16 case TCS_AF_IP6: return addr->data.ip6.address.bytes[0] == 0xFE && (addr->data.ip6.address.bytes[1] & 0xC0) == 0x80; // fe80::/10 default: return false; } } bool tcs_address_is_loopback(const struct TcsAddress* addr) { if (addr == NULL) return false; switch (addr->family) { case TCS_AF_IP4: return addr->data.ip4.address == TCS_ADDRESS_LOOPBACK_IP4; case TCS_AF_IP6: return addr->data.ip6.address.bytes[0] == 0 && addr->data.ip6.address.bytes[1] == 0 && addr->data.ip6.address.bytes[2] == 0 && addr->data.ip6.address.bytes[3] == 0 && addr->data.ip6.address.bytes[4] == 0 && addr->data.ip6.address.bytes[5] == 0 && addr->data.ip6.address.bytes[6] == 0 && addr->data.ip6.address.bytes[7] == 0 && addr->data.ip6.address.bytes[8] == 0 && addr->data.ip6.address.bytes[9] == 0 && addr->data.ip6.address.bytes[10] == 0 && addr->data.ip6.address.bytes[11] == 0 && addr->data.ip6.address.bytes[12] == 0 && addr->data.ip6.address.bytes[13] == 0 && addr->data.ip6.address.bytes[14] == 0 && addr->data.ip6.address.bytes[15] == 1; case TCS_AF_PACKET: { static const uint8_t zero_mac[6] = {0}; return memcmp(addr->data.packet.mac, zero_mac, 6) == 0; } default: return false; } } bool tcs_address_is_multicast(const struct TcsAddress* addr) { if (addr == NULL) return false; switch (addr->family) { case TCS_AF_IP4: return (addr->data.ip4.address >> 24) >= 224 && (addr->data.ip4.address >> 24) <= 239; case TCS_AF_IP6: return addr->data.ip6.address.bytes[0] == 0xFF; case TCS_AF_PACKET: return (addr->data.packet.mac[0] & 0x01) != 0; default: return false; } } bool tcs_address_is_broadcast(const struct TcsAddress* addr) { if (addr == NULL) return false; switch (addr->family) { case TCS_AF_IP4: return addr->data.ip4.address == TCS_ADDRESS_BROADCAST_IP4; case TCS_AF_PACKET: return addr->data.packet.mac[0] == 0xFF && addr->data.packet.mac[1] == 0xFF && addr->data.packet.mac[2] == 0xFF && addr->data.packet.mac[3] == 0xFF && addr->data.packet.mac[4] == 0xFF && addr->data.packet.mac[5] == 0xFF; default: return false; } } #endif #endif