/* 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_

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

static const char* const TCS_VERSION_TXT = "v0.4-dev";
static const char* const TCS_LICENSE_TXT =
    "Copyright 2018 Markus Lindelöw"
    "\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"
    "The above copyright notice and this permission notice shall be included in all"
    "copies or substantial portions of the Software."
    "\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_preset(TcsSocket* socket_ctx, TcsPreset socket_type);
* - TcsResult tcs_close(TcsSocket* socket_ctx);
*
* High-level Socket Creation:
* - TcsResult tcs_tcp_server(TcsSocket* socket_ctx, const struct TcsAddress* local_address);
* - TcsResult tcs_tcp_server_str(TcsSocket* socket_ctx, const char* local_address, uint16_t port);
* - TcsResult tcs_tcp_client(TcsSocket* socket_ctx, const struct TcsAddress* remote_address, int timeout_ms);
* - TcsResult tcs_tcp_client_str(TcsSocket* socket_ctx, const char* remote_address, uint16_t port, int timeout_ms);
* - TcsResult tcs_udp_receiver(TcsSocket* socket_ctx, const struct TcsAddress* local_address);
* - TcsResult tcs_udp_receiver_str(TcsSocket* socket_ctx, const char* local_address, uint16_t port);
* - TcsResult tcs_udp_sender(TcsSocket* socket_ctx, const struct TcsAddress* remote_address);
* - TcsResult tcs_udp_sender_str(TcsSocket* socket_ctx, const char* remote_address, uint16_t port);
* - TcsResult tcs_udp_peer(TcsSocket* socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* remote_address);
* - TcsResult tcs_udp_peer_str(TcsSocket* socket_ctx, const char* local_address, uint16_t local_port, const char* remote_address, uint16_t remote_port);
*
* High-level Raw L2-Packet Sockets (Experimental):
* - TcsResult tcs_packet_sender(TcsSocket* socket_ctx, const struct TcsAddress* remote_address);
* - TcsResult tcs_packet_sender_str(TcsSocket* socket_ctx, const char* interface_name, const uint8_t destination_mac[6], uint16_t protocol);
* - TcsResult tcs_packet_peer(TcsSocket* socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* remote_address);
* - TcsResult tcs_packet_peer_str(TcsSocket* socket_ctx, const char* interface_name, const uint8_t destination_mac[6], uint16_t protocol);
* - TcsResult tcs_packet_capture_iface(TcsSocket* socket_ctx, const struct TcsInterface* iface);
* - TcsResult tcs_packet_capture_ifname(TcsSocket* socket_ctx, const char* interface_name);
*
* 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, int64_t timeout_in_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_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_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_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_from(TcsSocket socket_ctx, const struct TcsAddress* local_address, const struct TcsAddress* multicast_address);

*
* 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_resolve_timeout(const char* hostname, TcsAddressFamily address_family, struct TcsAddress addresses[], size_t capacity, size_t* out_count, int timeout_ms);
* - 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_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);
*/

// First we have some code to 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 <basetsd.h>
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)
typedef int TcsSocket;
typedef unsigned int TcsInterfaceId;
#endif

#ifndef TCS_SENDV_MAX
#ifdef TCS_SMALL_STACK
#define TCS_SENDV_MAX 128
#else
#define TCS_SENDV_MAX 1024
#endif
#endif

/**
 * @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 Network Address
 */
struct TcsAddress
{
    TcsAddressFamily family;
    union
    {
        struct
        {
            uint32_t address; /**< Same byte order as the host */
            uint16_t port;    /**< Same byte order as the host */

        } ip4;
        struct
        {
            uint8_t address[16]; /**< Same byte order as the host */
            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;
};

/**
 * @brief Network Interface Information
 */
struct TcsInterface
{
    TcsInterfaceId id;
    char name[32];
};

struct TcsInterfaceAddress
{
    struct TcsInterface iface;
    struct TcsAddress address;
};

// 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 = {TCS_AF_ANY, {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;

/**
 * @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) */

// Simple socket creation
typedef enum
{
    TCS_PRESET_TCP_IP4,
    TCS_PRESET_UDP_IP4,
    TCS_PRESET_TCP_IP6,
    TCS_PRESET_UDP_IP6,
    TCS_PRESET_PACKET, // Layer 2, CAP_NET_RAW permission may be needed
} TcsPreset;

// 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_BROADCAST;
extern const int TCS_SO_KEEPALIVE;
extern const int TCS_SO_LINGER;
extern const int TCS_SO_REUSEADDR; /**< This is a tricky one for crossplatform independency! */
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,

    /* -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_TEMPRORARY_FAILURE = -38,

    /* -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 ::tcs_socket_preset() instead, or one of the helper functions to create and setup a socket directly:
 *   - ::tcs_tcp_server_str()
 *   - ::tcs_tcp_client_str()
 *   - ::tcs_udp_receiver_str()
 *   - ::tcs_udp_sender_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
 *   - ::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. Suck 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_preset()
 * @see tcs_tcp_server_str()
 * @see tcs_tcp_client_str()
 * @see tcs_udp_receiver_str()
 * @see tcs_udp_sender_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 Creates a new socket with simplified options.
 *
 * This is a simple wrapper around tcs_socket() to make it easier to create common socket types.
 * Consider using one of the helper functions instead to create and setup a socket directly:
 *   - ::tcs_tcp_server_str()
 *   - ::tcs_tcp_client_str()
 *   - ::tcs_udp_receiver_str()
 *   - ::tcs_udp_sender_str()
 *
 * @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_preset(&my_socket, TCS_PRESET_TCP_IP4);
 *   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]  socket_type Socket family / transport combination:  
 *   - ::TCS_PRESET_TCP_IP4   — TCP over IPv4  
 *   - ::TCS_PRESET_UDP_IP4   — UDP over IPv4  
 *   - ::TCS_PRESET_TCP_IP6   — TCP over IPv6  
 *   - ::TCS_PRESET_UDP_IP6   — UDP over IPv6  
 *   - ::TCS_PRESET_PACKET    — Raw Layer-2 (may require *CAP_NET_RAW*)
 *
 * @return #TCS_SUCCESS if successful, otherwise the error code.
 *
 * @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument. Suck as a socket value that is not #TCS_SOCKET_INVALID.
 *
 * @see tcs_socket()
 * @see tcs_tcp_server_str()
 * @see tcs_tcp_client_str()
 * @see tcs_udp_receiver_str()
 * @see tcs_udp_sender_str()
 * @see tcs_close()
 * @see tcs_lib_init()
 * @see tcs_lib_free()
 */
TcsResult tcs_socket_preset(TcsSocket* socket_ctx, TcsPreset socket_type);

/**
* @brief Closes the socket, stop communication and free all recourses 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_preset()
* @see tcs_tcp_server_str()
* @see tcs_tcp_client_str()
* @see tcs_udp_receiver_str()
* @see tcs_udp_sender_str()
* @see tcs_lib_init()
* @see tcs_lib_free()
*/
TcsResult tcs_close(TcsSocket* socket_ctx);

// ######## High-level Socket Creation ########

/**
* @brief Setup a socket to listen for incoming tcp connections given an address struct.
*
* @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
*
*   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 IPv4 interfaces
*   local_address.data.ip4.port = 1212;
*   
*   TcsSocket server_socket = TCS_SOCKET_INVALID; // Always initialize TcsSocket to TCS_SOCKET_INVALID.
*   TcsResult tcs_server_res = tcs_tcp_server_str(&server_socket, &local_address);
*   if (tcs_server_res != TCS_SUCCESS)
*   {
*     tcs_lib_free();
*     return -2; // Failed to create server socket
*   }
*
*   TcsSocket client_socket = TCS_SOCKET_INVALID;
*   TcsResult accept_result = tcs_accept(&server_socket, &client_socket, NULL); // Accept incoming connections
*   if (accept_result != TCS_SUCCESS)
*   {
*     tcs_close(&server_socket);
*     tcs_lib_free();
*     return -3; // Failed to accept incoming connection
*   }
*
*   // Do stuff with client_socket here. See examples in the examples folder.
*
*   tcs_shutdown(&client_socket, TCS_SD_BOTH); // Shutdown the accepted socket
*   tcs_close(&client_socket); // Close the accepted socket
*   tcs_close(&server_socket); // Safe to call even if server_socket is TCS_SOCKET_INVALID
*   tcs_lib_free();
* }
* @endcode
* @param[out] socket_ctx is a pointer to your socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use.
* @param[in] local_address
*
* @return #TCS_SUCCESS if successful, otherwise the error code.
* @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument. Such as a socket value that is not #TCS_SOCKET_INVALID.
*
* @see tcs_socket_preset()
 */
TcsResult tcs_tcp_server(TcsSocket* socket_ctx, const struct TcsAddress* local_address);

/**
* @brief Setup a socket to listen for incoming tcp connections.
*
* @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 server_socket = TCS_SOCKET_INVALID; // Always initialize TcsSocket to TCS_SOCKET_INVALID.
*   TcsResult tcs_server_res = tcs_tcp_server_str(&server_socket, "0.0.0.0", 1212);
*   if (tcs_server_res != TCS_SUCCESS)
*   {
*     tcs_lib_free();
*     return -2; // Failed to create server socket
*   }
*
*   TcsSocket client_socket = TCS_SOCKET_INVALID;
*   TcsResult accept_result = tcs_accept(&server_socket, &client_socket, NULL); // Accept incoming connections
*   if (accept_result != TCS_SUCCESS)
*   {
*     tcs_close(&server_socket);
*     tcs_lib_free();
*     return -3; // Failed to accept incoming connection
*   }
*
*   // Do stuff with server_socket here. See examples in the documentation.
*
*   tcs_shutdown(&client_socket, TCS_SD_BOTH); // Shutdown the accepted socket
*   tcs_close(&client_socket); // Close the accepted socket
*   tcs_close(&server_socket); // Safe to call even if server_socket is TCS_SOCKET_INVALID
*   tcs_lib_free();
* }
* @endcode
*
* @param[out] socket_ctx is a pointer to your socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use.
* @param[in] local_address is the local address to bind to. Use NULL or "0.0.0.0" to bind to all interfaces. Port number in string will result in invalid argument error.
* @param[in] port is the local port to bind to. 0 will result in a random free port being assigned.
*
* @return #TCS_SUCCESS if successful, otherwise the error code.
* @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument. Such as a socket value that is not #TCS_SOCKET_INVALID.
*
* @see tcs_socket()
* @see tcs_socket_preset()
* @see tcs_tcp_client_str()
* @see tcs_close()
* @see tcs_lib_init()
* @see tcs_lib_free()
*/
TcsResult tcs_tcp_server_str(TcsSocket* socket_ctx, const char* local_address, uint16_t port);

/**
* @brief Setup a socket to connect to a remote TCP server using a specific address structure.
*
* This will create a TCP client socket and attempt to connect to the specified remote address.
* The function blocks until connected or until the specified timeout elapses.
* Use tcs_tcp_client_str() if you want to specify the address as a hostname and port instead.
*
* @code
* #include "tinycsocket.h"
* int main()
* {
*   TcsResult tcs_init_res = tcs_lib_init();
*   if (tcs_init_res != TCS_SUCCESS)
*     return -1;
*
*   struct TcsAddress remote_addr = {0};
*   remote_addr.family = TCS_AF_IP4;
*   remote_addr.data.ip4.address = 0x7F000001; // 127.0.0.1 loopback
*   remote_addr.data.ip4.port = 8080;
*
*   TcsSocket client_socket = TCS_SOCKET_INVALID;
*   TcsResult connect_res = tcs_tcp_client(&client_socket, &remote_addr, 1000); // 1000 milliseconds timeout
*   if (connect_res != TCS_SUCCESS)
*   {
*     tcs_lib_free();
*     return -2;
*   }
*
*   // Use the connected socket
*   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();
* }
* @endcode
*
* @param[out] socket_ctx is a pointer to your socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use
* @param[in] remote_address is the remote address structure to connect to
* @param[in] timeout_ms is the maximum time in milliseconds to wait until connected, use #TCS_WAIT_INF to wait indefinitely
*
* @return #TCS_SUCCESS if successful, otherwise the error code
* @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument, such as a socket value that is not #TCS_SOCKET_INVALID
* @retval #TCS_ERROR_CONNECTION_REFUSED if the remote server refused the connection
* @retval #TCS_ERROR_TIMED_OUT if the connection attempt timed out
* @retval #TCS_ERROR_ADDRESS_LOOKUP_FAILED if the address could not be resolved
*
* @see tcs_tcp_client_str()
* @see tcs_close()
*/
TcsResult tcs_tcp_client(TcsSocket* socket_ctx, const struct TcsAddress* remote_address, int timeout_ms);

/**
* @brief Setup a socket and connect to a remote TCP server.
*
* This will create a TCP client socket and attempt to connect to the specified remote address.
* The function blocks until connected or until the specified timeout elapses.
* Use tcs_tcp_client() if you want to specify the address as a struct TcsAddress instead.
*
* @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 connect_res = tcs_tcp_client_str(&client_socket, "127.0.0.1", 8080, 1000); // 1000 milliseconds timeout
*   if (connect_res != TCS_SUCCESS)
*   {
*     tcs_lib_free();
*     return -2;
*   }
*
*   // Use the connected socket
*   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[out] socket_ctx is a pointer to your socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use
* @param[in] remote_address is the remote address to connect to. Hostname or IP address string.
* @param[in] port is the remote port to connect to
* @param[in] timeout_ms is the maximum time in milliseconds to wait until connected, use #TCS_WAIT_INF to wait indefinitely
*
* @return #TCS_SUCCESS if successful, otherwise the error code
* @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument, such as a socket value that is not #TCS_SOCKET_INVALID
* @retval #TCS_ERROR_CONNECTION_REFUSED if the remote server refused the connection
* @retval #TCS_ERROR_TIMED_OUT if the connection attempt timed out
* @retval #TCS_ERROR_ADDRESS_LOOKUP_FAILED if the address could not be resolved
*
* @see tcs_tcp_client()
* @see tcs_close()
*/
TcsResult tcs_tcp_client_str(TcsSocket* socket_ctx, const char* remote_address, uint16_t port, int timeout_ms);

/**
* @brief Creates a UDP socket bound to a local address structure for receiving datagrams.
*
* This function creates a UDP socket and binds it to the specified local address structure,
* allowing it to receive incoming UDP datagrams sent to that address/port combination.
* Use tcs_udp_receiver_str() if you want to specify the address as a hostname and port instead.
*
* @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
*
*   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 = 8888;
*   
*   TcsSocket receiver = TCS_SOCKET_INVALID; // Always initialize TcsSocket to TCS_SOCKET_INVALID
*   TcsResult udp_res = tcs_udp_receiver(&receiver, &local_address);
*   if (udp_res != TCS_SUCCESS)
*   {
*     tcs_lib_free();
*     return -2; // Failed to create UDP receiver
*   }
*
*   // Receive data
*   uint8_t buffer[2048];
*   size_t bytes_received = 0;
*   struct TcsAddress sender_address;
*   
*   TcsResult recv_res = tcs_receive_from(receiver, buffer, sizeof(buffer), 
*                                       TCS_FLAG_NONE, &sender_address, &bytes_received);
*   if (recv_res == TCS_SUCCESS && bytes_received > 0)
*   {
*     // Process received data...
*   }
*
*   tcs_close(&receiver);
*   tcs_lib_free();
*   return 0;
* }
* @endcode
*
* @param[out] socket_ctx is a pointer to your socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use.
* @param[in] local_address is the local address structure to bind to.
*
* @return #TCS_SUCCESS if successful, otherwise the error code.
* @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument, such as a socket value that is not #TCS_SOCKET_INVALID.
* @retval #TCS_ERROR_PERMISSION_DENIED if binding to the specified address/port requires elevated privileges.
*
* @see tcs_udp_receiver_str()
* @see tcs_udp_sender()
* @see tcs_udp_peer()
* @see tcs_receive_from()
* @see tcs_close()
*/
TcsResult tcs_udp_receiver(TcsSocket* socket_ctx, const struct TcsAddress* local_address);

/**
* @brief Creates a UDP socket bound to a local address for receiving datagrams.
*
* This function creates a UDP socket and binds it to the specified local address and port,
* allowing it to receive incoming UDP datagrams sent to that address/port combination.
* Use tcs_udp_receiver() if you want to specify the address as a TcsAddress structure.
*
* @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 receiver = TCS_SOCKET_INVALID; // Always initialize TcsSocket to TCS_SOCKET_INVALID
*   TcsResult udp_res = tcs_udp_receiver_str(&receiver, "0.0.0.0", 8888);
*   if (udp_res != TCS_SUCCESS)
*   {
*     tcs_lib_free();
*     return -2; // Failed to create UDP receiver
*   }
*
*   // Receive data
*   uint8_t buffer[2048];
*   size_t bytes_received = 0;
*   struct TcsAddress remote_address;
*   
*   TcsResult recv_res = tcs_receive_from(receiver, buffer, sizeof(buffer), TCS_FLAG_NONE, &remote_address, &bytes_received);
*   if (recv_res == TCS_SUCCESS)
*   {
*     // Process received data...
*   }
*
*   tcs_close(&receiver);
*   tcs_lib_free();
*   return 0;
* }
* @endcode
*
* @param[out] socket_ctx is a pointer to your socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use.
* @param[in] local_address is the local address to bind to. Use NULL or "0.0.0.0" to bind to all interfaces.
* @param[in] port is the local port to bind to. 0 will result in a random free port being assigned.
*
* @return #TCS_SUCCESS if successful, otherwise the error code.
* @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument, such as a socket value that is not #TCS_SOCKET_INVALID.
* @retval #TCS_ERROR_PERMISSION_DENIED if binding to the specified address/port requires elevated privileges.
* @retval #TCS_ERROR_ADDRESS_LOOKUP_FAILED if the local address could not be resolved.
*
* @see tcs_udp_receiver()
* @see tcs_udp_sender_str()
* @see tcs_udp_peer_str()
* @see tcs_receive_from()
* @see tcs_close()
*/
TcsResult tcs_udp_receiver_str(TcsSocket* socket_ctx, const char* local_address, uint16_t port);

/**
* @brief Creates a UDP socket configured to send datagrams to a specific remote address structure.
*
* This function creates a UDP socket that's pre-configured to send datagrams to the specified
* remote address structure. The socket is not bound to a specific local address, so the OS
* will automatically assign a local address and port when sending data.
* Use tcs_udp_sender_str() if you want to specify the address as a hostname and port instead.
*
* @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
*
*   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 = 8888;
*   
*   TcsSocket sender = TCS_SOCKET_INVALID; // Always initialize TcsSocket to TCS_SOCKET_INVALID
*   TcsResult udp_res = tcs_udp_sender(&sender, &remote_address);
*   if (udp_res != TCS_SUCCESS)
*   {
*     tcs_lib_free();
*     return -2; // Failed to create UDP sender
*   }
*
*   // Send data
*   uint8_t buffer[] = "Hello, UDP receiver!";
*   size_t bytes_sent = 0;
*   
*   TcsResult send_res = tcs_send(sender, buffer, sizeof(buffer)-1, TCS_FLAG_NONE, &bytes_sent);
*   if (send_res == TCS_SUCCESS)
*   {
*     // Data sent successfully
*   }
*
*   tcs_close(&sender);
*   tcs_lib_free();
*   return 0;
* }
* @endcode
*
* @param[out] socket_ctx is a pointer to your socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use.
* @param[in] remote_address is the remote address structure to send to.
*
* @return #TCS_SUCCESS if successful, otherwise the error code.
* @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument, such as a socket value that is not #TCS_SOCKET_INVALID.
*
* @see tcs_udp_sender_str()
* @see tcs_udp_receiver()
* @see tcs_udp_peer()
* @see tcs_send()
* @see tcs_close()
*/
TcsResult tcs_udp_sender(TcsSocket* socket_ctx, const struct TcsAddress* remote_address);

/**
* @brief Creates a UDP socket configured to send datagrams to a specific remote address.
*
* This function creates a UDP socket that's pre-configured to send datagrams to the specified
* remote address and port. The socket is not bound to a specific local address, so the OS
* will automatically assign a local address and port when sending data.
* Use tcs_udp_sender() if you want to specify the address as a TcsAddress structure.
*
* @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 sender = TCS_SOCKET_INVALID; // Always initialize TcsSocket to TCS_SOCKET_INVALID
*   TcsResult udp_res = tcs_udp_sender_str(&sender, "127.0.0.1", 8888);
*   if (udp_res != TCS_SUCCESS)
*   {
*     tcs_lib_free();
*     return -2; // Failed to create UDP sender
*   }
*
*   // Send data
*   uint8_t buffer[] = "Hello, UDP receiver!";
*   size_t bytes_sent = 0;
*   
*   TcsResult send_res = tcs_send(sender, buffer, sizeof(buffer)-1, TCS_FLAG_NONE, &bytes_sent);
*   if (send_res == TCS_SUCCESS)
*   {
*     // Data sent successfully
*   }
*
*   tcs_close(&sender);
*   tcs_lib_free();
*   return 0;
* }
* @endcode
*
* @param[out] socket_ctx is a pointer to your socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use.
* @param[in] remote_address is the remote address to send to. Hostname or IP address string.
* @param[in] port is the remote port to send to.
*
* @return #TCS_SUCCESS if successful, otherwise the error code.
* @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument, such as a socket value that is not #TCS_SOCKET_INVALID.
* @retval #TCS_ERROR_ADDRESS_LOOKUP_FAILED if the remote address could not be resolved.
*
* @see tcs_udp_sender()
* @see tcs_udp_receiver_str()
* @see tcs_udp_peer_str()
* @see tcs_send()
* @see tcs_close()
*/
TcsResult tcs_udp_sender_str(TcsSocket* socket_ctx, const char* remote_address, uint16_t port);

/**
* @brief Creates a UDP socket bound to a local address structure that can send to a specific remote address structure.
*
* This function creates a UDP socket that's both bound to a local address structure for receiving
* datagrams and pre-configured to send datagrams to a specified remote address structure. This
* creates a complete bidirectional UDP communication channel in a single call.
* Use tcs_udp_peer_str() if you want to specify the addresses as hostname and port pairs instead.
*
* @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
*
*   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 = 8888;
*   
*   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 = 9999;
*   
*   TcsSocket peer = TCS_SOCKET_INVALID; // Always initialize TcsSocket to TCS_SOCKET_INVALID
*   TcsResult udp_res = tcs_udp_peer(&peer, &local_address, &remote_address);
*   if (udp_res != TCS_SUCCESS)
*   {
*     tcs_lib_free();
*     return -2; // Failed to create UDP peer
*   }
*
*   // Send data - goes to the pre-configured remote address
*   uint8_t send_buffer[] = "Hello, remote peer!";
*   size_t bytes_sent = 0;
*   tcs_send(peer, send_buffer, sizeof(send_buffer)-1, TCS_FLAG_NONE, &bytes_sent);
*
*   // Receive data from any sender
*   uint8_t recv_buffer[2048];
*   size_t bytes_received = 0;
*   struct TcsAddress sender_address;
*   tcs_receive_from(peer, recv_buffer, sizeof(recv_buffer), TCS_FLAG_NONE, 
*                   &sender_address, &bytes_received);
*
*   tcs_close(&peer);
*   tcs_lib_free();
*   return 0;
* }
* @endcode
*
* @param[out] socket_ctx is a pointer to your socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use.
* @param[in] local_address is the local address structure to bind to.
* @param[in] remote_address is the remote address structure to send to.
*
* @return #TCS_SUCCESS if successful, otherwise the error code.
* @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument, such as a socket value that is not #TCS_SOCKET_INVALID.
* @retval #TCS_ERROR_PERMISSION_DENIED if binding to the specified address/port requires elevated privileges.
*
* @see tcs_udp_peer_str()
* @see tcs_udp_receiver()
* @see tcs_udp_sender()
* @see tcs_send()
* @see tcs_receive_from()
* @see tcs_close()
*/
TcsResult tcs_udp_peer(TcsSocket* socket_ctx,
                       const struct TcsAddress* local_address,
                       const struct TcsAddress* remote_address);

/**
* @brief Creates a UDP socket bound to a local address that can send to a specific remote address.
*
* This function creates a UDP socket that's both bound to a local address/port for receiving
* datagrams and pre-configured to send datagrams to a specified remote address/port. This
* creates a complete bidirectional UDP communication channel in a single call.
* Use tcs_udp_peer() if you want to specify the addresses as TcsAddress structures.
*
* @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 peer = TCS_SOCKET_INVALID; // Always initialize TcsSocket to TCS_SOCKET_INVALID
*   TcsResult udp_res = tcs_udp_peer_str(&peer, "0.0.0.0", 8888, "192.168.1.100", 9999);
*   if (udp_res != TCS_SUCCESS)
*   {
*     tcs_lib_free();
*     return -2; // Failed to create UDP peer
*   }
*
*   // Send data - goes to the pre-configured remote address
*   uint8_t send_buffer[] = "Hello, remote peer!";
*   size_t bytes_sent = 0;
*   tcs_send(peer, send_buffer, sizeof(send_buffer)-1, TCS_FLAG_NONE, &bytes_sent);
*
*   // Receive data from any sender
*   uint8_t recv_buffer[2048];
*   size_t bytes_received = 0;
*   struct TcsAddress sender_address;
*   tcs_receive_from(peer, recv_buffer, sizeof(recv_buffer), TCS_FLAG_NONE, 
*                   &sender_address, &bytes_received);
*
*   tcs_close(&peer);
*   tcs_lib_free();
*   return 0;
* }
* @endcode
*
* @param[out] socket_ctx is a pointer to your socket context to be created, which must have been initialized to #TCS_SOCKET_INVALID before use.
* @param[in] local_address is the local address to bind to. Use NULL or "0.0.0.0" to bind to all interfaces.
* @param[in] local_port is the local port to bind to. 0 will result in a random free port being assigned.
* @param[in] remote_address is the remote address to send to. Hostname or IP address string.
* @param[in] remote_port is the remote port to send to.
*
* @return #TCS_SUCCESS if successful, otherwise the error code.
* @retval #TCS_ERROR_INVALID_ARGUMENT if you have provided an invalid argument, such as a socket value that is not #TCS_SOCKET_INVALID.
* @retval #TCS_ERROR_PERMISSION_DENIED if binding to the specified address/port requires elevated privileges.
* @retval #TCS_ERROR_ADDRESS_LOOKUP_FAILED if the local or remote address could not be resolved.
*
* @see tcs_udp_peer()
* @see tcs_udp_receiver_str()
* @see tcs_udp_sender_str()
* @see tcs_send()
* @see tcs_receive_from()
* @see tcs_close()
*/
TcsResult tcs_udp_peer_str(TcsSocket* socket_ctx,
                           const char* local_address,
                           uint16_t local_port,
                           const char* remote_address,
                           uint16_t remote_port);

// ######## High-level Raw L2-Packet Sockets (Experimental) ########

/**
 * @brief Create a raw packet socket targeting a specific remote address.
 *
 * @warning This API is **experimental** and not recommended for production use.
 *
 * @param[out] socket_ctx     Pointer to a #TcsSocket handle that will be initialized on success. Must be set to
 *                            #TCS_SOCKET_INVALID before the call.
 * @param[in]  remote_address Pointer to the remote ::TcsAddress to which the socket will send packets.
 *
 * @retval TCS_SUCCESS        Socket created successfully.
 * @retval TCS_ERR_PERMISSION Operation not permitted (may require CAP_NET_RAW).
 * @retval TCS_ERR_INVALID    The supplied address was invalid or unsupported.
 * @retval TCS_ERR_SYS        Underlying OS error.
 *
 * @see tcs_packet_sender_str()
 */
TcsResult tcs_packet_sender(TcsSocket* socket_ctx, const struct TcsAddress* remote_address);

/**
 * @brief Create a raw packet socket for Ethernet frame transmission given interface name, destination MAC, and EtherType.
 *
 * @warning This API is **experimental** and must not be used in production.
 *
 * @param[out] socket_ctx       Pointer to a #TcsSocket handle that will be initialized on success. Must be set to
 *                              #TCS_SOCKET_INVALID before the call.
 * @param[in]  interface_name   Null-terminated name of the network interface to bind (e.g. `"eth0"`).
 * @param[in]  destination_mac  Destination hardware address (6 bytes).
 * @param[in]  protocol         EtherType value in host byte order.
 *
 * @retval TCS_SUCCESS          Socket created successfully.
 * @retval TCS_ERR_PERMISSION   Operation not permitted (may require CAP_NET_RAW).
 * @retval TCS_ERR_NOT_FOUND    Interface not found.
 * @retval TCS_ERR_SYS          Underlying OS error.
 */
TcsResult tcs_packet_sender_str(TcsSocket* socket_ctx,
                                const char* interface_name,
                                const uint8_t destination_mac[6],
                                uint16_t protocol);

/**
 * @brief Create a raw packet socket bound to a local/remote address pair.
 *
 * @warning This API is **experimental** and not recommended for production use.
 *
 * @param[out] socket_ctx     Pointer to a #TcsSocket handle that will be initialized on success. Must be set to
 *                            #TCS_SOCKET_INVALID before the call.
 * @param[in]  local_address  Pointer to a ::TcsAddress that specifies the local endpoint to bind.
 * @param[in]  remote_address Pointer to a ::TcsAddress that specifies the
 *                            remote peer address.
 *
 * @retval TCS_SUCCESS        Socket created successfully.
 * @retval TCS_ERR_PERMISSION Operation not permitted (may require CAP_NET_RAW).
 * @retval TCS_ERR_NOT_FOUND  Interface or address not found.
 * @retval TCS_ERR_INVALID    One or more addresses were invalid.
 * @retval TCS_ERR_SYS        Underlying OS error.
 *
 * @see tcs_packet_peer_str()
 * @see tcs_packet_sender_str()
 * @see tcs_packet_sender()
 */
TcsResult tcs_packet_peer(TcsSocket* socket_ctx,
                          const struct TcsAddress* local_address,
                          const struct TcsAddress* remote_address);

/**
 * @brief Create a raw packet socket bound to a specific peer.
 *
 * @warning This API is **experimental** and not recommended for production use.
 *
 * @param[out] socket_ctx       Pointer to a #TcsSocket handle that will be initialized on success. Must be set to
 *                              #TCS_SOCKET_INVALID before the call.
 * @param[in]  interface_name   Null-terminated name of the network interface
 *                              to bind (e.g. `"eth0"`).
 * @param[in]  destination_mac  Destination hardware address (6 bytes).
 * @param[in]  protocol         EtherType value in host byte order.
 *
 * @retval TCS_SUCCESS          Socket created successfully.
 * @retval TCS_ERR_PERMISSION   Operation not permitted (may require CAP_NET_RAW).
 * @retval TCS_ERR_NOT_FOUND    Interface not found.
 * @retval TCS_ERR_INVALID      Invalid arguments (e.g. malformed MAC).
 * @retval TCS_ERR_SYS          Underlying OS error.
 *
 * @see tcs_packet_sender_str()
 * @see tcs_packet_sender()
 */
TcsResult tcs_packet_peer_str(TcsSocket* socket_ctx,
                              const char* interface_name,
                              const uint8_t destination_mac[6],
                              uint16_t protocol);

/**
 * @brief Create a raw packet socket in capture mode on a given interface.
 *
 * @warning This API is **experimental** and not recommended for production use.
 *
 * @param[out] socket_ctx  Pointer to a #TcsSocket handle that will be initialized on success. Must be set to
 *                         #TCS_SOCKET_INVALID before the call.
 * @param[in]  interface   Pointer to a ::TcsInterface structure that identifies the interface to capture from.
 *
 * @retval TCS_SUCCESS          Socket created successfully.
 * @retval TCS_ERR_PERMISSION   Operation not permitted (may require CAP_NET_RAW).
 * @retval TCS_ERR_NOT_FOUND    Interface not found or not available.
 * @retval TCS_ERR_INVALID      Invalid argument(s).
 * @retval TCS_ERR_SYS          Underlying OS error.
 *
 * @see tcs_packet_capture()
 * @see tcs_packet_peer_str()
 */
TcsResult tcs_packet_capture_iface(TcsSocket* socket_ctx, const struct TcsInterface* iface);

/**
 * @brief Create a raw packet socket in capture mode on a given interface using its name.
 *
 * @warning This API is **experimental** and not recommended for production use.
 *
 * @param[out] socket_ctx      Pointer to a #TcsSocket handle that will be initialized on success. Must be set to
 *                             #TCS_SOCKET_INVALID before the call.
 * @param[in]  interface_name  Null-terminated name of the network interface to capture from (e.g. "eth0", "wlan0").
 *
 * @retval TCS_SUCCESS                  Socket created successfully.
 * @retval TCS_ERROR_PERMISSION_DENIED  Operation not permitted (may require CAP_NET_RAW).
 * @retval TCS_ERROR_INVALID_ARGUMENT   Invalid argument(s) provided.
 * @retval TCS_ERROR_SYSTEM             Interface not found or other underlying OS error.
 *
 * @see tcs_packet_capture_iface()
 * @see tcs_interface_list()
 * @see tcs_receive_from()
 */
TcsResult tcs_packet_capture_ifname(TcsSocket* socket_ctx, const char* interface_name);

/**
 * @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_preset(&server_socket, TCS_PRESET_TCP_IP4);
 *   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() or tcs_socket_preset().
 * @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_tcp_server_str()
 * @see tcs_udp_receiver_str()
 * @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_preset(&client_socket, TCS_PRESET_TCP_IP4);
 *   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() or tcs_socket_preset().
 * @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_tcp_client()
 * @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_preset(&client_socket, TCS_PRESET_TCP_IP4);
 *   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() or tcs_socket_preset().
 * @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_tcp_client_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()
 * @see tcs_listen_to()
 */
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_NULLSOCKET;
 * tcs_create(&listen_socket, TCS_PRESET_TCP_IP4);
 * tcs_listen_to(listen_socket, 1212);
 * while (true)
 * {
 *   TcsSocket client_socket = TCS_NULLSOCKET;
 *   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()
 * @see tcs_getaddrinfo()
 */
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);

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()
* @see tcs_getaddrinfo()
*/
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);

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();
* TCS_SOCKET socket1 = TCS_NULLSOCKET;
* TCS_SOCKET socket2 = TCS_NULLSOCKET;
* tcs_create(&socket1, TCS_PRESET_UDP_IP4);
* tcs_create(&socket2, TCS_PRESET_UDP_IP4);
* tcs_bind(socket, 1000)
* tcs_bind(socket, 1001)
*
* 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;
* TcsPollEvent ev[2] = {TCS_NULLEVENT, TCS_NULLEVENT};
* tcs_pool_poll(pool, ev, 2, &populated, 1000);  // Will wait 1000 ms for data on port 1000 or 1001
* for (int 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_NO_FLAGS, &bytes_received);
*         recv_buffer[bytes_received] = '\n';
*         printf(recv_buffer);
*     }
* }
* 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 in-out pool context pointer created with tcs_pool_create()
* @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 in-out 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 to be able to read.
* @param poll_can_write true if you want to poll @p socket_ctx for to be able to write.
* @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_remove()
*/
TcsResult tcs_pool_remove(struct TcsPool* pool, TcsSocket socket_ctx);

/**
* @brief Remove a socket from the pool.
*
* @param[in] pool is your in-out 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_EVENT_NONE.
* @param events_count number of in elements in your events array. Does not make sense to have more events than number of sockets int the pool. If to short, all events may not be returned.
* @param[out] events_populated will contain the number of events the parameter ev has been populated with by the call.
* @param timeout_in_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,
                        int64_t timeout_in_ms);

/**
* @brief Set parameters on a socket. It is recommended to use tcs_set_xxx 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_get_xxx 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 Enable the socket to be allowed to use broadcast.
*
* 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);
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_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_membership_add_to(TcsSocket socket_ctx,
                                    const struct TcsAddress* local_address,
                                    const struct TcsAddress* multicast_address);

TcsResult tcs_opt_membership_drop_from(TcsSocket socket_ctx,
                                       const struct TcsAddress* local_address,
                                       const struct TcsAddress* multicast_address);

TcsResult tcs_opt_membership_add(TcsSocket socket_ctx, const struct TcsAddress* multicast_address);
TcsResult tcs_opt_membership_drop(TcsSocket socket_ctx, const struct TcsAddress* multicast_address);

TcsResult tcs_opt_nonblocking_set(TcsSocket socket_ctx, bool do_nonblocking);
TcsResult tcs_opt_nonblocking_get(TcsSocket socket_ctx, bool* is_nonblocking);

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_resolve_timeout(const char* hostname,
                                      TcsAddressFamily address_family,
                                      struct TcsAddress addresses[],
                                      size_t capacity,
                                      size_t* out_count,
                                      int timeout_ms);
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);

/**
 * @brief Get an address from a string.
 *
 * For example:
 * - "192.168.0.1:1212"
 * - "localhost:80"
 * - "[::1]:443".
 *
 * Note that this function will not perform DNS resolution. Use ::tcs_address_resolve() for that.
 *
 * @param address
 * @param out_str
 * @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]);

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_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);

#ifdef __cplusplus
}
#endif

#endif

#ifdef TINYCSOCKET_IMPLEMENTATION
/**********************************/
/****** tinydatastructures.h ******/
/**********************************/
#ifndef TINYDATASTRUCTURES_H_
#define TINYDATASTRUCTURES_H_

#include <stdlib.h>
#include <string.h>

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;                                                                                               \
    };                                                                                                                 \
                                                                                                                       \
    static inline int tds_ulist_##NAME##_create(struct TdsUList_##NAME* ulist)                                         \
    {                                                                                                                  \
        return tds_ulist_create((void**)&ulist->data, &ulist->count, &ulist->capacity, sizeof(TYPE));                  \
    }                                                                                                                  \
    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;                                                                                                    \
    }                                                                                                                  \
    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); \
    }                                                                                                                  \
    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);           \
    }                                                                                                                  \
    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, &value_count, &value_capacity);
        tds_ulist_destroy(values, &key_count, &key_capacity);
        if (key_sts != 0)
            return key_sts;
        if (value_sts != 0)
            return value_sts;
    }
    if (key_capacity != value_capacity)
    {
        tds_ulist_destroy(keys, &value_count, &value_capacity);
        tds_ulist_destroy(values, &key_count, &key_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;
    }
    *count += 1;
    *capacity = key_capacity <= value_capacity ? key_capacity : value_capacity; // min(key_capacity, value_capacity);

    if (key_capacity != value_capacity)
        return -1;

    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;                                                                                  \
    };                                                                                                    \
                                                                                                          \
    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));                                                        \
    }                                                                                                     \
    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;                                                                                         \
    }                                                                                                     \
    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);                                                                       \
    }                                                                                                     \
    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);                                                                        \
    }                                                                                                     \
    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.
 */

#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#endif
#ifndef _ISOC99_SOURCE
#define _ISOC99_SOURCE
#endif
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE
#endif

// 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

#include <errno.h>
#include <fcntl.h>       // fcntl() for non-blocking
#include <ifaddrs.h>     // getifaddr()
#include <limits.h>      // IOV_MAX
#include <net/if.h>      // Flags for ifaddrs (?)
#include <netdb.h>       // Protocols and custom return codes
#include <netinet/in.h>  // IPPROTO_XXP
#include <netinet/tcp.h> // TCP_NODELAY
#include <poll.h>        // poll()
#include <stdlib.h>      // malloc()/free()
#include <string.h>      // strcpy, memset
#include <sys/ioctl.h>   // Flags for ifaddrs
#include <sys/socket.h>  // pretty much everything
#include <sys/types.h>   // POSIX.1 compatibility
#include <sys/uio.h>     // UIO_MAXIOV
#include <unistd.h>      // close()

// The logic might seem a bit reversed but it is to allow header only usage without defining TCS_AVAILABLE_XXX
// You need to disable the default if your system does not support it. (Optimized for most common usage and easy to detect)

// If you no not use cmake you may need to define TCS_MISSING_AF_PACKET yourself if your system does not support it
#if defined(__linux__) && !defined(TCS_MISSING_AF_PACKET) // __linux__ to disable default for header only no-linux users
#define TCS_AVAILABLE_AF_PACKET 1
#else
#define TCS_AVAILABLE_AF_PACKET 0
#endif

// If you no not use cmake you may need to define TCS_MISSING_IFADDRS yourself if your system does not support it
#if !defined(TCS_MISSING_IFADDRS)
#define TCS_AVAILABLE_IFADDRS 1
#else
#define TCS_AVAILABLE_IFADDRS 0
#endif

#if TCS_AVAILABLE_AF_PACKET
#include <linux/if_arp.h>    // sll_hatype (ethernet and not can or firewire etc.)
#include <linux/if_packet.h> // 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;

// 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;

// 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;
const uint16_t TCS_PROTOCOL_TSN = 0xF022; // htons(ETH_P_TSN)

// 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_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_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 = SO_PRIORITY;

// 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_AVAILABLE_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:
            return TCS_ERROR_PERMISSION_DENIED;
        case ECONNREFUSED:
            return TCS_ERROR_CONNECTION_REFUSED;
        case EINVAL:
            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)
{
    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_AVAILABLE_AF_PACKET
            *native_family = AF_PACKET;
#else
            return TCS_ERROR_NOT_IMPLEMENTED;
#endif
            return TCS_SUCCESS;
        default:
            return TCS_ERROR_INVALID_ARGUMENT;
    }
    return TCS_SUCCESS;
}

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)
    {
        return TCS_ERROR_NOT_IMPLEMENTED;
    }
    else if (tcs_address->family == TCS_AF_PACKET)
    {
#if TCS_AVAILABLE_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)
{
    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_AVAILABLE_AF_PACKET
        case AF_PACKET:
            *family = TCS_AF_PACKET;
            return TCS_SUCCESS;
#endif
        default:
            return TCS_ERROR_NOT_IMPLEMENTED;
    }
    return TCS_SUCCESS;
}

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)
    {
        return TCS_ERROR_NOT_IMPLEMENTED;
    }
#if TCS_AVAILABLE_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)
{
    // Not needed for posix
    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;
    *socket_ctx = socket(native_family, type, protocol);

    if (*socket_ctx != -1) // Same as TCS_NULLSOCKET
        return TCS_SUCCESS;
    else
        return errno2retcode(errno);
}

// tcs_socket_preset() 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);
    }
}

// ######## High-level Socket Creation ########

// tcs_tcp_server_str() is defined in tinycsocket_common.c
// tcs_tcp_server() is defined in tinycsocket_common.c
// tcs_tcp_client_str() is defined in tinycsocket_common.c
// tcs_tcp_client() is defined in tinycsocket_common.c
// tcs_udp_receiver_str() is defined in tinycsocket_common.c
// tcs_udp_receiver() is defined in tinycsocket_common.c
// tcs_udp_sender_str() is defined in tinycsocket_common.c
// tcs_udp_sender() is defined in tinycsocket_common.c
// tcs_udp_peer_str() is defined in tinycsocket_common.c
// tcs_udp_peer() is defined in tinycsocket_common.c

// ######## High-level Raw L2-Packet Sockets (Experimental) ########

// tcs_packet_sender_str() is defined in tinycsocket_common.c
// tcs_packet_sender() is defined in tinycsocket_common.c
// tcs_packet_peer_str() is defined in tinycsocket_common.c
// tcs_packet_peer() is defined in tinycsocket_common.c
// tcs_packet_capture_iface() is defined in tinycsocket_common.c
// tcs_packet_capture_ifname() is defined in tinycsocket_common.c

// ######## 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;

    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 (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;

    // TCS_SENDV_MAX is default set to 1024. define TCS_SMALL_STACK to use a value of 128.
    const size_t max_supported_iov =
        UIO_MAXIOV > TCS_SENDV_MAX ? TCS_SENDV_MAX : UIO_MAXIOV; // min(TCS_SENDV_MAX, UIO_MAXIOV)
    if (buffer_count > max_supported_iov)
        return TCS_ERROR_INVALID_ARGUMENT;

    // Some plattforms (Glibc) do not follow posix. We have mixed size_t and int types for msg_iovlen.
    // We cast it to a narrower unsigned type to avoid warnings for later assignment.
    // If you read this and doesn't like it, you can use probably use the compiler extension typeof() instead
    // to cast it to correct type without warnigns. We can not, since we want to support more compilers.

// Check if buffer_count can be placed in an unsigned short
#if (UIO_MAXIOV > USHRT_MAX && TCS_SENDV_MAX > USHRT_MAX)
    // You are using a plattform with very narrow unsigned short. Let's hope that your plattform follows POSIX standards here.
    typedef int SAFE_IOVLEN;
#else
    typedef unsigned short SAFE_IOVLEN_TYPE;
#endif

    SAFE_IOVLEN_TYPE narrow_casted_iovlen = (SAFE_IOVLEN_TYPE)buffer_count;

    static struct iovec my_iovec[TCS_SENDV_MAX];
    for (size_t i = 0; i < buffer_count; i++)
    {
        // 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.msg_iovlen = narrow_casted_iovlen; // msg_iovlen is size_t or int type.
    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);

    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;

    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;
        return TCS_SHUTDOWN;
    }
    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;

    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;
        return TCS_ERROR_SOCKET_CLOSED; // TODO: think about this
    }
    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,
                        int64_t timeout_in_ms)
{
    if (pool == NULL)
        return TCS_ERROR_INVALID_ARGUMENT;
    if (events_populated == NULL)
        return TCS_ERROR_INVALID_ARGUMENT;

    // We do not support more more elements or time than signed int32 supports.
    // todo(markusl): Add support for int64 timeout
    if (events_count > 0x7FFFFFFF || timeout_in_ms > 0x7FFFFFFF)
        return TCS_ERROR_INVALID_ARGUMENT;

    struct TdsMap_poll* map = &pool->backend.poll.map;

    int poll_ret = poll(map->keys, map->count, (int)timeout_in_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;
            events[filled].error = (map->keys[i].revents & POLLERR) == 0
                                       ? TCS_SUCCESS
                                       : TCS_ERROR_NOT_IMPLEMENTED; // TODO: implement error codes
            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;

    if (getsockopt(socket_ctx, (int)level, (int)option_name, (void*)option_value, (socklen_t*)option_size) == 0)
    {
        // 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 tinycocket_common.c
// tcs_opt_broadcast_get() is defined in tinycocket_common.c
// tcs_opt_keep_alive_set() is defined in inycocket_common.c
// tcs_opt_keep_alive_get() is defined in tinycocket_common.c
// tcs_opt_reuse_address_set() is defined in tiinycocket_common.c
// tcs_opt_reuse_address_get() is defined in tiinycocket_common.c
// tcs_opt_send_buffer_size_set() is defined in tininycocket_common.c
// tcs_opt_send_buffer_size_get() is defined in tininycocket_common.c
// tcs_opt_receive_buffer_size_set() is defined in tinyinycocket_common.c
// tcs_opt_receive_buffer_size_get() is defined in tinyinycocket_common.c

TcsResult tcs_opt_receive_timeout_set(TcsSocket socket_ctx, int timeout_ms)
{
    if (socket_ctx == TCS_SOCKET_INVALID)
        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;

    // Todo: Replace with tcs_address_socket() when implemented
    struct sockaddr_storage address_native_local;
    memset(&address_native_local, 0, sizeof(struct sockaddr_storage));
    socklen_t address_native_local_size = sizeof(struct sockaddr_storage);
    if (getsockname(socket_ctx, (struct sockaddr*)&address_native_local, &address_native_local_size) != 0)
        return errno2retcode(errno);

    struct TcsAddress local_address = TCS_ADDRESS_NONE;
    TcsResult sts = native2sockaddr((struct sockaddr*)&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 (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 = sizeof(struct sockaddr_storage);
    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 = sizeof(struct sockaddr_storage);
    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)
    {
        return TCS_ERROR_NOT_IMPLEMENTED;
    }
    else if (multicast_address->family == TCS_AF_PACKET)
    {
#if TCS_AVAILABLE_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
    }
    else
    {
        return TCS_ERROR_NOT_IMPLEMENTED;
    }

    return TCS_ERROR_UNKNOWN; // Not reachable
}

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;

    // Todo: Replace with tcs_address_socket() when implemented
    struct sockaddr_storage address_native_local;
    memset(&address_native_local, 0, sizeof address_native_local);
    socklen_t address_native_local_size = sizeof(struct sockaddr_storage);
    if (getsockname(socket_ctx, (struct sockaddr*)&address_native_local, &address_native_local_size) != 0)
        return errno2retcode(errno);

    struct TcsAddress local_address = TCS_ADDRESS_NONE;
    TcsResult sts = native2sockaddr((struct sockaddr*)&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 (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)
    {
        return TCS_ERROR_NOT_IMPLEMENTED;
    }
    else if (multicast_address->family == TCS_AF_PACKET)
    {
#if TCS_AVAILABLE_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
    }
    else
    {
        return TCS_ERROR_NOT_IMPLEMENTED;
    }

    return TCS_ERROR_UNKNOWN; // Not reachable
}

// ######## Address and Interface Utilities ########

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);

    if (found_interfaces != NULL)
    {
        for (size_t i = 0; i < interfaces_length && interfaces[i].if_index != 0; ++i)
        {
            strncpy(found_interfaces[i].name, interfaces[i].if_name, 31);
            found_interfaces[i].name[31] = '\0';
            found_interfaces[i].id = interfaces[i].if_index;
            if (interfaces_populated != NULL)
                *interfaces_populated += 1;
        }
    }
    else // found_interfaces == NULL && interface_populated != NULL
    {
        for (size_t i = 0; interfaces[i].if_index != 0; ++i)
            *interfaces_populated += 1;
    }

    if_freenameindex(interfaces);
    return TCS_SUCCESS;
}

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;

    struct addrinfo native_hints;
    memset(&native_hints, 0, sizeof native_hints);
    TcsResult family_convert_status = family2native(address_family, (sa_family_t*)&native_hints.ai_family);
    if (family_convert_status != TCS_SUCCESS)
        return family_convert_status;
    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_TEMPRORARY_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)
        {
            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;
}

TcsResult tcs_address_resolve_timeout(const char* hostname,
                                      TcsAddressFamily address_family,
                                      struct TcsAddress addresses[],
                                      size_t capacity,
                                      size_t* out_count,
                                      int timeout_ms)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

#if TCS_AVAILABLE_IFADDRS // acquired from CMake
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, 31);
            interface_addresses[populated].iface.name[31] = '\0';
            interface_addresses[populated].iface.id = interface_id;
            interface_addresses[populated].address = address;
            populated++;

            if (out_count != NULL)
                (*out_count)++;
        }
        else if (interface_addresses == NULL && out_count != NULL)
        {
            (*out_count)++;
        }
    }

    freeifaddrs(ifap);
    return TCS_SUCCESS;
}
#else
// SunOS before 2010, HP and AIX does not support getifaddrs
// ioctl implementation,
// https://stackoverflow.com/questions/4139405/how-can-i-get-to-know-the-ip-address-for-interfaces-in-c/4139811#4139811
TcsResult tcs_address_list(unsigned int interface_id_filter,
                           TcsAddressFamily address_family_filter,
                           struct TcsInterfaceAddress interface_addresses[],
                           size_t capacity,
                           size_t* out_count)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}
#endif

TcsResult tcs_address_socket_local(TcsSocket socket_ctx, struct TcsAddress* local_address)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_address_socket_remote(TcsSocket socket_ctx, struct TcsAddress* remote_address)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

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_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

#if !defined(NTDDI_VERSION) && !defined(_WIN32_WINNT) && !defined(WINVER)
#ifdef _WIN64
#define NTDDI_VERSION 0x05020000
#define _WIN32_WINNT 0x0502
#define WINVER 0x0502
#else
#define NTDDI_VERSION 0x05010300
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501
#endif
#endif
#define WIN32_LEAN_AND_MEAN
// Header only should not need other files
#ifndef TINYDATASTRUCTURES_H_
#include "tinydatastructures.h"
#endif
// before windows.h
#include <winsock2.h> // sockets

#include <windows.h>

// after windows.h
#include <iphlpapi.h> // GetAdaptersAddresses
#include <ws2tcpip.h> // getaddrinfo

#include <stdlib.h> // Malloc for GetAdaptersAddresses
#include <string.h> // memset

#if defined(_MSC_VER) || defined(__clang__)
#pragma comment(lib, "wsock32.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "Iphlpapi.lib")
#endif

#ifdef __cplusplus
using std::min;
#endif

// Forwards declaration due to winver dispatch
// We will dispatch at lib_init() which OS functions to call depending on OS support

#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 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_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;

// 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_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_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;

int g_init_count = 0;

// ######## 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;
        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)
{
    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_AVAILABLE_AF_PACKET
            *native_family = AF_PACKET;
#else
            return TCS_ERROR_NOT_IMPLEMENTED;
#endif
            return TCS_SUCCESS;
        default:
            return TCS_ERROR_INVALID_ARGUMENT;
    }
    return TCS_SUCCESS;
}

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)
    {
        return TCS_ERROR_NOT_IMPLEMENTED;
    }
    else if (in_addr->family == TCS_AF_PACKET)
    {
        return TCS_ERROR_NOT_IMPLEMENTED;
    }
    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)
    {
        return TCS_ERROR_NOT_IMPLEMENTED;
    }
    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)
{
    if (g_init_count <= 0)
    {
        WSADATA wsa_data;
        int wsa_startup_status_code = WSAStartup(MAKEWORD(2, 2), &wsa_data);
        if (wsa_startup_status_code != 0)
            return TCS_ERROR_SYSTEM;
    }
    ++g_init_count;
    return TCS_SUCCESS;
}

TcsResult tcs_lib_free(void)
{
    g_init_count--;
    if (g_init_count <= 0)
    {
        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;
    if (*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_preset() 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);
    }
}

// ######## High-level Socket Creation ########

// tcs_tcp_server_str() is defined in tinycsocket_common.c
// tcs_tcp_server() is defined in tinycsocket_common.c
// tcs_tcp_client_str() is defined in tinycsocket_common.c
// tcs_tcp_client() is defined in tinycsocket_common.c
// tcs_udp_receiver_str() is defined in tinycsocket_common.c
// tcs_udp_receiver() is defined in tinycsocket_common.c
// tcs_udp_sender_str() is defined in tinycsocket_common.c
// tcs_udp_sender() is defined in tinycsocket_common.c
// tcs_udp_peer_str() is defined in tinycsocket_common.c
// tcs_udp_peer() is defined in tinycsocket_common.c

// ######## High-level Raw L2-Packet Sockets (Experimental) ########

// tcs_packet_sender_str() is defined in tinycsocket_common.c
// tcs_packet_sender() is defined in tinycsocket_common.c
// tcs_packet_peer_str() is defined in tinycsocket_common.c
// tcs_packet_peer() is defined in tinycsocket_common.c
// tcs_packet_capture_iface() is defined in tinycsocket_common.c
// tcs_packet_capture_ifname() is defined in tinycsocket_common.c

// ######## Socket Operations ########

TcsResult tcs_bind(TcsSocket socket_ctx, const struct TcsAddress* address)
{
    if (socket_ctx == TCS_SOCKET_INVALID)
        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);
    return socketstatus2retcode(connect_status);
}

// 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;

    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 (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;

    if (buffer_count > TCS_SENDV_MAX)
        return TCS_ERROR_INVALID_ARGUMENT;

    WSABUF native_buffers[TCS_SENDV_MAX];
    for (size_t i = 0; i < buffer_count; ++i)
    {
        native_buffers[i].buf = (CHAR*)buffers[i].data;
        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);

    if (bytes_sent != NULL)
        *bytes_sent = (size_t)sent;

    if (wsasend_status != SOCKET_ERROR)
        return TCS_SUCCESS;
    else
        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 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;
        return TCS_ERROR_SOCKET_CLOSED;
    }
    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;

    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)
    {
        return TCS_ERROR_SOCKET_CLOSED;
    }
    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)
        return TCS_ERROR_INVALID_ARGUMENT;
    if (!poll_can_read && !poll_can_write && !poll_error)
        return TCS_ERROR_INVALID_ARGUMENT;

    tds_map_socket_user_add(&pool->user_data, socket_ctx, user_data);

    if (poll_can_read)
    {
        int sts = tds_ulist_soc_add(&pool->read_sockets, &socket_ctx, 1);
        if (sts != 0)
        {
            return TCS_ERROR_MEMORY;
        }
    }
    if (poll_can_write)
    {
        int sts = tds_ulist_soc_add(&pool->write_sockets, &socket_ctx, 1);
        if (sts != 0)
        {
            tds_ulist_soc_remove(&pool->read_sockets, pool->read_sockets.count, 1);
            return TCS_ERROR_MEMORY;
        }
    }
    if (poll_error)
    {
        int sts = tds_ulist_soc_add(&pool->error_sockets, &socket_ctx, 1);
        if (sts != 0)
        {
            tds_ulist_soc_remove(&pool->read_sockets, pool->read_sockets.count, 1);
            tds_ulist_soc_remove(&pool->write_sockets, pool->write_sockets.count, 1);
            return TCS_ERROR_MEMORY;
        }
    }
    return TCS_SUCCESS;
}

TcsResult tcs_pool_remove(struct TcsPool* pool, TcsSocket socket_ctx)
{
    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);
            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);
            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);
            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);
        }
    }

    return TCS_SUCCESS;
}

TcsResult tcs_pool_poll(struct TcsPool* pool,
                        struct TcsPollEvent* events,
                        size_t events_capacity,
                        size_t* events_populated,
                        int64_t timeout_in_ms)
{
    if (pool == NULL)
        return TCS_ERROR_INVALID_ARGUMENT;
    if (timeout_in_ms != TCS_INF && (timeout_in_ms > 0xffffffff || timeout_in_ms < 0))
        return TCS_ERROR_INVALID_ARGUMENT;
    if (events == NULL || events_populated == NULL)
        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);
        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);
        wfds_cpy = wfds_heap;
    }

    if (pool->error_sockets.count <= FD_SETSIZE)
    {
        FD_ZERO(&efds_stack);
        efds_cpy = (struct tcs_fd_set*)&efds_stack;
    }
    else
    {
        wfds_heap = (struct tcs_fd_set*)malloc(data_offset + sizeof(SOCKET) * pool->error_sockets.count);
        wfds_cpy = wfds_heap;
    }

    if (rfds_cpy == NULL || wfds_cpy == NULL || efds_cpy == NULL)
    {
        if (rfds_heap != NULL)
            free(rfds_heap);
        if (wfds_heap != NULL)
            free(wfds_heap);
        if (efds_heap != NULL)
            free(efds_heap);

        return TCS_ERROR_MEMORY;
    }
    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_in_ms != TCS_INF)
    {
        t.tv_sec = (long)(timeout_in_ms / 1000);
        t.tv_usec = (long)(timeout_in_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;
                }
            }
            // Check for new events
            events[new_n].error = TCS_ERROR_NOT_IMPLEMENTED; //TODO(markusl): Make this a proper error
            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 sockopt_status = getsockopt(socket_ctx, (int)level, (int)option_name, (char*)option_value, (int*)option_size);
    return socketstatus2retcode(sockopt_status);
}

// tcs_opt_broadcast_set() is defined in tinycocket_common.c
// tcs_opt_broadcast_get() is defined in tinycocket_common.c
// tcs_opt_keep_alive_set() is defined in inycocket_common.c
// tcs_opt_keep_alive_get() is defined in tinycocket_common.c
// tcs_opt_reuse_address_set() is defined in tiinycocket_common.c
// tcs_opt_reuse_address_get() is defined in tiinycocket_common.c
// tcs_opt_send_buffer_size_set() is defined in tininycocket_common.c
// tcs_opt_send_buffer_size_get() is defined in tininycocket_common.c
// tcs_opt_receive_buffer_size_set() is defined in tinyinycocket_common.c
// tcs_opt_receive_buffer_size_get() is defined in tinyinycocket_common.c

TcsResult tcs_opt_receive_timeout_set(TcsSocket socket_ctx, int timeout_ms)
{
    if (socket_ctx == TCS_SOCKET_INVALID)
        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)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_opt_nonblocking_get(TcsSocket socket_ctx, bool* is_non_blocking)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_opt_membership_add(TcsSocket socket_ctx, const struct TcsAddress* multicast_address)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_opt_membership_add_to(TcsSocket socket_ctx,
                                    const struct TcsAddress* local_address,
                                    const struct TcsAddress* multicast_address)
{
    if (multicast_address == NULL)
        return TCS_ERROR_INVALID_ARGUMENT;

    // TODO(markusl): Add ipv6 support
    if (multicast_address->family != TCS_AF_IP4)
        return TCS_ERROR_NOT_IMPLEMENTED;

    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));
}

TcsResult tcs_opt_membership_drop(TcsSocket socket_ctx, const struct TcsAddress* multicast_address)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_opt_membership_drop_from(TcsSocket socket_ctx,
                                       const struct TcsAddress* local_address,
                                       const struct TcsAddress* multicast_address)
{
    if (multicast_address == NULL)
        return TCS_ERROR_INVALID_ARGUMENT;

    // TODO(markusl): Add ipv6 support
    if (multicast_address->family != TCS_AF_IP4)
        return TCS_ERROR_NOT_IMPLEMENTED;

    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));
}

// ######## Address and Interface Utilities ########

TcsResult tcs_interface_list(struct TcsInterface interfaces[], size_t capacity, size_t* out_count)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

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;

    ADDRINFOA native_hints;
    memset(&native_hints, 0, sizeof native_hints);
    TcsResult sts = family2native(address_family, (short*)&native_hints.ai_family);
    if (sts != TCS_SUCCESS)
        return sts;

    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)
        {
            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_resolve_timeout(const char* hostname,
                                      TcsAddressFamily address_family,
                                      struct TcsAddress addresses[],
                                      size_t capacity,
                                      size_t* out_count,
                                      int timeout_ms)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_address_list(unsigned int interface_id_filter,
                           TcsAddressFamily address_family_filter,
                           struct TcsInterfaceAddress interface_addresses[],
                           size_t capacity,
                           size_t* out_count)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
    /*
    if (found_interfaces == NULL && no_of_found_interfaces == NULL)
        return TCS_ERROR_INVALID_ARGUMENT;

    if (found_interfaces == NULL && found_interfaces_length != 0)
        return TCS_ERROR_INVALID_ARGUMENT;

    if (no_of_found_interfaces != NULL)
        *no_of_found_interfaces = 0;

    const int MAX_TRIES = 3;
    ULONG adapeters_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(adapeters_buffer_size);
        adapter_sts = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, adapters, &adapeters_buffer_size);
        if (adapter_sts == ERROR_BUFFER_OVERFLOW)
        {
            free(adapters);
            adapters = NULL;
        }
        else
        {
            break;
        }
    }
    if (adapter_sts != NO_ERROR)
    {
        if (adapters != NULL)
            free(adapters);
        return TCS_ERROR_UNKNOWN;
    }

    size_t i = 0;
    for (PIP_ADAPTER_ADDRESSES iter = adapters;
         iter != NULL && (found_interfaces == NULL || i < found_interfaces_length);
         iter = iter->Next)
    {
        if (iter->OperStatus != IfOperStatusUp)
            continue;
        for (PIP_ADAPTER_UNICAST_ADDRESS address_iter = iter->FirstUnicastAddress; address_iter != NULL;
             address_iter = address_iter->Next)
        {
            struct TcsAddress t;
            if (native2sockaddr(address_iter->Address.lpSockaddr, &t) != TCS_SUCCESS)
                continue;
            if (found_interfaces != NULL)
            {
                found_interfaces[i].address = t;
                memset(found_interfaces[i].name, '\0', 32);
                WideCharToMultiByte(CP_UTF8, 0, iter->FriendlyName, -1, found_interfaces[i].name, 31, NULL, NULL);
            }
            ++i;
        }
    }
    if (adapters != NULL)
        free(adapters);
    if (no_of_found_interfaces != NULL)
        *no_of_found_interfaces = i;

    return TCS_SUCCESS;
    */
}

TcsResult tcs_address_socket_local(TcsSocket socket_ctx, struct TcsAddress* local_address)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_address_socket_remote(TcsSocket socket_ctx, struct TcsAddress* remote_address)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_address_socket_family(TcsSocket socket_ctx, TcsAddressFamily* out_family)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

// 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_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 <stdbool.h>
#include <stdio.h>  //sprintf
#include <string.h> // 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_preset(TcsSocket* socket_ctx, TcsPreset socket_type)
{
    if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID)
        return TCS_ERROR_INVALID_ARGUMENT;

    TcsAddressFamily family = TCS_AF_ANY;
    int type = 0;
    int protocol = 0;

    switch (socket_type)
    {
        case TCS_PRESET_TCP_IP4:
            family = TCS_AF_IP4;
            type = TCS_SOCK_STREAM;
            protocol = TCS_PROTOCOL_IP_TCP;
            break;
        case TCS_PRESET_UDP_IP4:
            family = TCS_AF_IP4;
            type = TCS_SOCK_DGRAM;
            protocol = TCS_PROTOCOL_IP_UDP;
            break;
        case TCS_PRESET_PACKET:
            family = TCS_AF_PACKET;
            type = TCS_SOCK_RAW;
            protocol = 0; // Block all traffic, unblock with tcs_bind_addr()
            break;
        case TCS_PRESET_TCP_IP6:
            family = TCS_AF_IP6;
            type = TCS_SOCK_STREAM;
            protocol = TCS_PROTOCOL_IP_TCP;
            break;
        case TCS_PRESET_UDP_IP6:
            family = TCS_AF_IP6;
            type = TCS_SOCK_DGRAM;
            protocol = TCS_PROTOCOL_IP_UDP;
            break;
        default:
            return TCS_ERROR_NOT_IMPLEMENTED;
            break;
    }
    return tcs_socket(socket_ctx, family, type, protocol);
}

// tcs_close() is defined in OS specific files

// ######## High-level Socket Creation ########

TcsResult tcs_tcp_server(TcsSocket* socket_ctx, const struct TcsAddress* local_address)
{
    if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID)
        return TCS_ERROR_INVALID_ARGUMENT;
    if (local_address == NULL)
        return TCS_ERROR_INVALID_ARGUMENT;

    TcsResult res = tcs_socket(socket_ctx, local_address->family, TCS_SOCK_STREAM, TCS_PROTOCOL_IP_TCP);
    if (res != TCS_SUCCESS)
        return res;
    res = tcs_bind(*socket_ctx, local_address);
    if (res != TCS_SUCCESS)
    {
        tcs_close(socket_ctx);
        return res;
    }
    res = tcs_listen(*socket_ctx, TCS_BACKLOG_MAX);
    if (res != TCS_SUCCESS)
    {
        tcs_close(socket_ctx);
        return res;
    }
    return TCS_SUCCESS;
}

TcsResult tcs_tcp_server_str(TcsSocket* socket_ctx, const char* local_address, uint16_t port)
{
    if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID)
        return TCS_ERROR_INVALID_ARGUMENT;
    if (local_address == NULL)
        return TCS_ERROR_INVALID_ARGUMENT;

    struct TcsAddress bind_address = TCS_ADDRESS_NONE;
    TcsResult res = tcs_address_parse(local_address, &bind_address);
    if (res != TCS_SUCCESS)
        return res;
    if (bind_address.family != TCS_AF_IP4 && bind_address.family != TCS_AF_IP6)
        return TCS_ERROR_INVALID_ARGUMENT;

    uint16_t* parsed_port = NULL;
    if (bind_address.family == TCS_AF_IP4)
    {
        parsed_port = &bind_address.data.ip4.port;
    }
    else if (bind_address.family == TCS_AF_IP6)
    {
        parsed_port = &bind_address.data.ip6.port;
    }
    else
    {
        return TCS_ERROR_UNKNOWN;
    }

    if (port == 0 && *parsed_port == 0)
        return TCS_ERROR_INVALID_ARGUMENT; // No port specified
    if (port != 0 && *parsed_port != 0)
        return TCS_ERROR_INVALID_ARGUMENT; // Port specified in both string and argument

    if (port != 0 && *parsed_port == 0)
        *parsed_port = port;

    return tcs_tcp_server(socket_ctx, &bind_address);
}

TcsResult tcs_tcp_client(TcsSocket* socket_ctx, const struct TcsAddress* remote_address, int timeout_ms)
{
    if (socket_ctx == NULL || *socket_ctx != TCS_SOCKET_INVALID)
        return TCS_ERROR_INVALID_ARGUMENT;
    if (timeout_ms < 0)
        return TCS_ERROR_INVALID_ARGUMENT;
    if (remote_address == NULL)
        return TCS_ERROR_INVALID_ARGUMENT;
    if (remote_address->family != TCS_AF_IP4 && remote_address->family != TCS_AF_IP6)
        return TCS_ERROR_NOT_IMPLEMENTED;
    if (remote_address->family == TCS_AF_IP4 && remote_address->data.ip4.port == 0)
        return TCS_ERROR_INVALID_ARGUMENT;
    if (remote_address->family == TCS_AF_IP6 && remote_address->data.ip6.port == 0)
        return TCS_ERROR_INVALID_ARGUMENT;

    TcsResult res = tcs_socket(socket_ctx, remote_address->family, TCS_SOCK_STREAM, TCS_PROTOCOL_IP_TCP);
    if (res != TCS_SUCCESS)
        return res;

    struct TcsPool* timeout_pool = NULL;
    res = tcs_pool_create(&timeout_pool);
    if (res != TCS_SUCCESS)
    {
        tcs_close(socket_ctx);
        return res;
    }

    res = tcs_opt_nonblocking_set(*socket_ctx, true);
    if (res != TCS_SUCCESS)
    {
        tcs_pool_destroy(&timeout_pool);
        tcs_close(socket_ctx);
        return res;
    }
    res = tcs_connect(*socket_ctx, remote_address);
    // // TODO: kolla vad tcs_connect returnerar i icke-blockande läge

    // tcs_pool_add(timeout_pool, *socket_ctx, NULL, true, true, true);

    // struct TcsPollEvent event;
    // size_t events_populated = 0;
    // tcs_pool_poll(timeout_pool, &event, 1, &events_populated, timeout_ms);
    // if (events_populated == 0)
    //     res = TCS_ERROR_TIMED_OUT;
    // else if ((event.events & TCS_POLLERR) != 0)
    //     res = TCS_ERROR_CONNECTION_REFUSED;
    // else if ((event.events & TCS_POLLHUP) != 0)
    //     res = TCS_ERROR_CONNECTION_REFUSED;
    // else if ((event.events & TCS_POLLOUT) != 0)
    //     res = TCS_SUCCESS;
    // else
    //     res = TCS_ERROR_UNKNOWN;
    return TCS_ERROR_UNKNOWN;
}

TcsResult tcs_tcp_client_str(TcsSocket* socket_ctx, const char* remote_address, uint16_t port, int timeout_ms)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_udp_receiver(TcsSocket* socket_ctx, const struct TcsAddress* local_address)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_udp_receiver_str(TcsSocket* socket_ctx, const char* local_address, uint16_t port)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_udp_sender(TcsSocket* socket_ctx, const struct TcsAddress* remote_address)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_udp_sender_str(TcsSocket* socket_ctx, const char* remote_address, uint16_t port)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_udp_peer(TcsSocket* socket_ctx,
                       const struct TcsAddress* local_address,
                       const struct TcsAddress* remote_address)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_udp_peer_str(TcsSocket* socket_ctx,
                           const char* local_address,
                           uint16_t local_port,
                           const char* remote_address,
                           uint16_t remote_port)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

// ######## High-level Raw L2-Packet Sockets (Experimental) ########

TcsResult tcs_packet_sender_str(TcsSocket* socket_ctx,
                                const char* interface_name,
                                const uint8_t destination_mac[6],
                                uint16_t protocol)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_packet_sender(TcsSocket* socket_ctx, const struct TcsAddress* remote_address)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_packet_peer_str(TcsSocket* socket_ctx,
                              const char* interface_name,
                              const uint8_t destination_mac[6],
                              uint16_t protocol)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_packet_peer(TcsSocket* socket_ctx,
                          const struct TcsAddress* local_address,
                          const struct TcsAddress* remote_address)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_packet_capture_iface(TcsSocket* socket_ctx, const struct TcsInterface* iface)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

TcsResult tcs_packet_capture_ifname(TcsSocket* socket_ctx, const char* interface_name)
{
    return TCS_ERROR_NOT_IMPLEMENTED;
}

// ######## 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, &current_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, &current_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;

        expected_length += (size_t)t - '0';
    }

    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_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;
}

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_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
// tcs_opt_membership_add_to() is defined in OS specific files
// tcs_opt_membership_drop() is defined in OS specific files
// 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_resolve_timeout() 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; i < 21; ++i) // max ipv4 string length with port colon
    {
        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;

    // AVTP Multicast address format:
    // 91:E0:F0:00:FE:00 - 91:E0:F0:00:FE:FF

    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)
    {
        return TCS_ERROR_NOT_IMPLEMENTED;
    }
    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 (str == NULL)
        return TCS_ERROR_INVALID_ARGUMENT;

    memset(str, 0, 40);
    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)
            sprintf(str, "%i.%i.%i.%i", b4, b3, b2, b1);
        else
            sprintf(str, "%i.%i.%i.%i:%i", b4, b3, b2, b1, p);
    }
    else if (address->family == TCS_AF_IP6)
    {
        return TCS_ERROR_NOT_IMPLEMENTED;
    }
    else if (address->family == TCS_AF_PACKET)
    {
        sprintf(str,
                "%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, r->data.ip6.address, 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;
    }
    return false;
}

bool tcs_address_is_any(const struct TcsAddress* addr)
{
    return false; // Not implemented
}

bool tcs_address_is_local(const struct TcsAddress* addr)
{
    return false; // Not implemented
}
bool tcs_address_is_loopback(const struct TcsAddress* addr)
{
    return false; // Not implemented
}
bool tcs_address_is_multicast(const struct TcsAddress* addr)
{
    return false; // Not implemented
}
bool tcs_address_is_broadcast(const struct TcsAddress* addr)
{
    return false; // Not implemented
}

#endif
#endif
