/*
* Author: Christian Huitema
* Copyright (c) 2017, Private Octopus, Inc.
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "picosocks.h"
#include "picoquic_utils.h"

int picoquic_bind_to_port(SOCKET_TYPE fd, int af, int port)
{
    struct sockaddr_storage sa;
    int addr_length = 0;

    memset(&sa, 0, sizeof(sa));

    if (af == AF_INET) {
        struct sockaddr_in* s4 = (struct sockaddr_in*)&sa;
#ifdef _WINDOWS
        s4->sin_family = (ADDRESS_FAMILY)af;
#else
        s4->sin_family = af;
#endif
        s4->sin_port = htons((unsigned short)port);
        addr_length = sizeof(struct sockaddr_in);
    } else {
        struct sockaddr_in6* s6 = (struct sockaddr_in6*)&sa;

        s6->sin6_family = AF_INET6;
        s6->sin6_port = htons((unsigned short)port);
        addr_length = sizeof(struct sockaddr_in6);
    }

    return bind(fd, (struct sockaddr*)&sa, addr_length);
}

int picoquic_get_local_address(SOCKET_TYPE sd, struct sockaddr_storage * addr)
{
    socklen_t name_len = sizeof(struct sockaddr_storage);
    return getsockname(sd, (struct sockaddr *)addr, &name_len);
}

int picoquic_socket_set_pkt_info(SOCKET_TYPE sd, int af)
{
    int ret;
#ifdef _WINDOWS
    int option_value = 1;
    if (af == AF_INET6) {
        ret = setsockopt(sd, IPPROTO_IPV6, IPV6_PKTINFO, (char*)&option_value, sizeof(int));
    }
    else {
        ret = setsockopt(sd, IPPROTO_IP, IP_PKTINFO, (char*)&option_value, sizeof(int));
    }
#else
    if (af == AF_INET6) {
        int val = 1;
        ret = setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY,
            &val, sizeof(val));
        if (ret == 0) {
            val = 1;
            ret = setsockopt(sd, IPPROTO_IPV6, IPV6_RECVPKTINFO, (char*)&val, sizeof(int));
        }
    }
    else {
        int val = 1;
#ifdef IP_PKTINFO
        ret = setsockopt(sd, IPPROTO_IP, IP_PKTINFO, (char*)&val, sizeof(int));
#else
        /* The IP_PKTINFO structure is not defined on BSD */
        ret = setsockopt(sd, IPPROTO_IP, IP_RECVDSTADDR, (char*)&val, sizeof(int));
#endif
    }
#endif

    return ret;
}

int picoquic_socket_set_ecn_options(SOCKET_TYPE sd, int af, int * recv_set, int * send_set)
{
    int ret = -1;
#ifdef _WINDOWS

    if (af == AF_INET6) {
#ifdef IPV6_ECN
        {
            DWORD ecn = PICOQUIC_ECN_ECT_0;
            DWORD recvEcn = 1;
            /* Request receiving ECN reports in recvmsg */
            ret = setsockopt(sd, IPPROTO_IPV6, IPV6_ECN, (char *)&recvEcn, sizeof(recvEcn));
            if (ret < 0) {
                DBG_PRINTF("setsockopt IPV6_ECN (0x%x) fails, errno: %d\n", recvEcn, GetLastError());
                ret = -1;
                *recv_set = 0;
            }
            else {
                *recv_set = 1;
                ret = 0;
            }

            /* Request setting ECN_ECT_0 in outgoing packets */
            if (setsockopt(sd, IPPROTO_IP, IPV6_ECN, (const char *)&ecn, sizeof(ecn)) < 0) {
                DBG_PRINTF("setsockopt IPv6_ECN (0x%x) fails, errno: %d\n", ecn, errno);
                *send_set = 0;
            }
            else {
                *send_set = 1;
            }
        }
#else
        * recv_set = 0;
        * send_set = 0;
#endif
    }
    else {
        /* Using IPv4 options. */
#if defined(IP_ECN)
        {
            DWORD ecn = PICOQUIC_ECN_ECT_0;
            INT recvEcn =1;

            /* Request receiving ECN reports in recvmsg */
            ret = setsockopt(sd, IPPROTO_IP, IP_ECN, (CHAR*)&recvEcn, sizeof(recvEcn));
            if (ret < 0) {
                DBG_PRINTF("setsockopt IP_ECN (0x%x) fails, errno: %d\n", recvEcn, GetLastError());
                ret = -1;
                *recv_set = 0;
            }
            else {
                *recv_set = 1;
                ret = 0;
            }

            /* Request setting ECN_ECT_0 in outgoing packets */
            if (setsockopt(sd, IPPROTO_IP, IP_ECN, (const char*)&ecn, sizeof(ecn)) < 0) {
                DBG_PRINTF("setsockopt IP_ECN (0x%x) fails, errno: %d\n", ecn, errno);
                *send_set = 0;
            }
            else {
                *send_set = 1;
            }
        }
#else
        * recv_set = 0;
#endif
        *send_set = 0;
    }
#else
    if (af == AF_INET6) {
#if defined(IPV6_TCLASS)
        {
            unsigned int ecn = PICOQUIC_ECN_ECT_0; /* Setting ECN_ECT_0 in outgoing packets */
            if (setsockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, &ecn, sizeof(ecn)) < 0) {
                DBG_PRINTF("setsockopt IPV6_TCLASS (0x%x) fails, errno: %d\n", ecn, errno);
                *send_set = 0;
            }
            else {
                *send_set = 1;
            }
        }
#else
        DBG_PRINTF("%s", "IPV6_TCLASS is not defined\n");
        *send_set = 0;
#endif
#ifdef IPV6_RECVTCLASS
        {
            unsigned int set = 0x01;

            /* Request receiving TOS reports in recvmsg */
            if (setsockopt(sd, IPPROTO_IPV6, IPV6_RECVTCLASS, &set, sizeof(set)) < 0) {
                DBG_PRINTF("setsockopt IPv6 IPV6_RECVTCLASS (0x%x) fails, errno: %d\n", set, errno);
                ret = -1;
                *recv_set = 0;
            }
            else {
                *recv_set = 1;
                ret = 0;
            }
        }
#else
        DBG_PRINTF("%s", "IPV6_RECVTCLASS is not defined\n");
        *recv_set = 0;
#endif 

    }
    else {
#if defined(IP_TOS)
        {
            unsigned int ecn = PICOQUIC_ECN_ECT_0;
            /* Request setting ECN_ECT_0 in outgoing packets */
            if (setsockopt(sd, IPPROTO_IP, IP_TOS, &ecn, sizeof(ecn)) < 0) {
                DBG_PRINTF("setsockopt IPv4 IP_TOS (0x%x) fails, errno: %d\n", ecn, errno);
                *send_set = 0;
            }
            else {
                *send_set = 1;
            }
        }
#else
        *send_set = 0;
        DBG_PRINTF("%s", "IP_TOS is not defined\n");
#endif

#ifdef IP_RECVTOS
        {
            unsigned int set = 1;

            /* Request receiving TOS reports in recvmsg */
            if (setsockopt(sd, IPPROTO_IP, IP_RECVTOS, &set, sizeof(set)) < 0) {
                DBG_PRINTF("setsockopt IPv4 IP_RECVTOS (0x%x) fails, errno: %d\n", set, errno);
                ret = -1;
                *recv_set = 0;
            }
            else {
                *recv_set = 1;
                ret = 0;
            }
        }
#else
        *recv_set = 0;
        DBG_PRINTF("%s", "IP_RECVTOS is not defined\n");
#endif
    }
#endif

    return ret;
}

SOCKET_TYPE picoquic_open_client_socket(int af)
{
#ifdef _WINDOWS
    WSADATA wsaData = { 0 };
    (void)WSA_START(MAKEWORD(2, 2), &wsaData);
    SOCKET_TYPE sd = WSASocket(af, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, WSA_FLAG_OVERLAPPED);
#else
    SOCKET_TYPE sd = socket(af, SOCK_DGRAM, IPPROTO_UDP);
#endif

    if (sd != INVALID_SOCKET) {
        int send_set = 0;
        int recv_set = 0;

        if (picoquic_socket_set_pkt_info(sd, af) != 0) {
            DBG_PRINTF("Cannot set PKTINFO option (af=%d)\n", af);
        }
        if (picoquic_socket_set_ecn_options(sd, af, &recv_set, &send_set) != 0) {
            DBG_PRINTF("Cannot set ECN options (af=%d)\n", af);
        }
    }
    else {
#ifdef _WINDOWS
        DBG_PRINTF("Cannot open socket(AF=%d), error: %d\n", af, GetLastError());
#else
        DBG_PRINTF("Cannot open socket(AF=%d), error: %d\n", af, errno);
#endif
    }

    return sd;
}

int picoquic_open_server_sockets(picoquic_server_sockets_t* sockets, int port)
{
    int ret = 0;

#ifdef _WINDOWS
    WSADATA wsaData = { 0 };
    if (WSA_START(MAKEWORD(2, 2), &wsaData)) {
        ret = -1;
    }
#endif

    const int sock_af[] = { AF_INET6, AF_INET };

    for (int i = 0; i < PICOQUIC_NB_SERVER_SOCKETS; i++) {
        if (ret == 0) {
            sockets->s_socket[i] = socket(sock_af[i], SOCK_DGRAM, IPPROTO_UDP);
        } else {
            sockets->s_socket[i] = INVALID_SOCKET;
        }

        if (sockets->s_socket[i] == INVALID_SOCKET) {
            ret = -1;
        }
        else {
            int recv_set = 0;
            int send_set = 0;
            if (picoquic_socket_set_ecn_options(sockets->s_socket[i], sock_af[i], &recv_set, &send_set) != 0) {
                DBG_PRINTF("Cannot set ECN options (af=%d)\n", sock_af[i]);
            }
            ret = picoquic_socket_set_pkt_info(sockets->s_socket[i], sock_af[i]);
            if (ret == 0) {
                ret = picoquic_bind_to_port(sockets->s_socket[i], sock_af[i], port);
            }
        }
    }

    return ret;
}

void picoquic_close_server_sockets(picoquic_server_sockets_t* sockets)
{
    for (int i = 0; i < PICOQUIC_NB_SERVER_SOCKETS; i++) {
        if (sockets->s_socket[i] != INVALID_SOCKET) {
            SOCKET_CLOSE(sockets->s_socket[i]);
            sockets->s_socket[i] = INVALID_SOCKET;
        }
    }
}

void picoquic_socks_cmsg_parse(
    void* vmsg,
    struct sockaddr_storage* addr_dest,
    int* dest_if,
    unsigned char* received_ecn,
    size_t * udp_coalesced_size)
{
    /* Assume that msg has been filled by a call to recvmsg */
#if _WINDOWS
    struct cmsghdr* cmsg;
    WSAMSG* msg = (WSAMSG*)vmsg;

    /* Get the control information */
    for (cmsg = WSA_CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = WSA_CMSG_NXTHDR(msg, cmsg)) {
        if (cmsg->cmsg_level == IPPROTO_IP) {
            if (cmsg->cmsg_type == IP_PKTINFO) {
                if (addr_dest != NULL) {
                    IN_PKTINFO* pPktInfo = (IN_PKTINFO*)WSA_CMSG_DATA(cmsg);
                    ((struct sockaddr_in*)addr_dest)->sin_family = AF_INET;
                    ((struct sockaddr_in*)addr_dest)->sin_port = 0;
                    ((struct sockaddr_in*)addr_dest)->sin_addr.s_addr = pPktInfo->ipi_addr.s_addr;

                    if (dest_if != NULL) {
                        *dest_if = (int)pPktInfo->ipi_ifindex;
                    }
                }
            }
            else if (cmsg->cmsg_type == IP_TOS
#ifdef IP_ECN
                || cmsg->cmsg_type == IP_ECN
#endif
                ) {
                if (cmsg->cmsg_len > 0) {
                    if (received_ecn != NULL) {
                        *received_ecn = *((unsigned char*)WSA_CMSG_DATA(cmsg));
                    }
                }
            }
            else {
                DBG_PRINTF("Cmsg level: %d, type: %d\n", cmsg->cmsg_level, cmsg->cmsg_type);
            }
        }
        else if (cmsg->cmsg_level == IPPROTO_IPV6) {
            if (cmsg->cmsg_type == IPV6_PKTINFO) {
                if (addr_dest != NULL) {
                    IN6_PKTINFO* pPktInfo6 = (IN6_PKTINFO*)WSA_CMSG_DATA(cmsg);
                    ((struct sockaddr_in6*)addr_dest)->sin6_family = AF_INET6;
                    ((struct sockaddr_in6*)addr_dest)->sin6_port = 0;
                    memcpy(&((struct sockaddr_in6*)addr_dest)->sin6_addr, &pPktInfo6->ipi6_addr, sizeof(IN6_ADDR));

                    if (dest_if != NULL) {
                        *dest_if = (int)pPktInfo6->ipi6_ifindex;
                    }
                }
            }
            else if (cmsg->cmsg_type == IPV6_TCLASS
#ifdef IPV6_ECN
                || cmsg->cmsg_type == IPV6_ECN
#endif
                ) {
                if (cmsg->cmsg_len > 0 && received_ecn != NULL) {
                    *received_ecn = *((unsigned char*)WSA_CMSG_DATA(cmsg));
                }
            }
            else {
                DBG_PRINTF("Cmsg level: %d, type: %d\n", cmsg->cmsg_level, cmsg->cmsg_type);
            }
        }
#ifdef UDP_COALESCED_INFO
        else if (cmsg->cmsg_level == UDP_COALESCED_INFO) {
            if (cmsg->cmsg_len > 0) {
                if (udp_coalesced_size != NULL) {
                    *udp_coalesced_size = *((DWORD*)WSA_CMSG_DATA(cmsg));
                }
            }
            else {
                DBG_PRINTF("Cmsg level: %d, type: %d\n", cmsg->cmsg_level, cmsg->cmsg_type);
            }
        }
#endif
        else {
            DBG_PRINTF("Cmsg level: %d, type: %d\n", cmsg->cmsg_level, cmsg->cmsg_type);
        }
    }
#else
    /* Get the control information */
    struct msghdr* msg = (struct msghdr*)vmsg;
    struct cmsghdr* cmsg;

    for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) {
        if (cmsg->cmsg_level == IPPROTO_IP) {
#ifdef IP_PKTINFO
            if (cmsg->cmsg_type == IP_PKTINFO) {
                if (addr_dest != NULL) {
                    struct in_pktinfo* pPktInfo = (struct in_pktinfo*)CMSG_DATA(cmsg);
                    ((struct sockaddr_in*)addr_dest)->sin_family = AF_INET;
                    ((struct sockaddr_in*)addr_dest)->sin_port = 0;
                    ((struct sockaddr_in*)addr_dest)->sin_addr.s_addr = pPktInfo->ipi_addr.s_addr;

                    if (dest_if != NULL) {
                        *dest_if = (int)pPktInfo->ipi_ifindex;
                    }
                }
            }
#else
            /* The IP_PKTINFO structure is not defined on BSD */
            if (cmsg->cmsg_type == IP_RECVDSTADDR) {
                if (addr_dest != NULL) {
                    struct in_addr* pPktInfo = (struct in_addr*)CMSG_DATA(cmsg);
                    ((struct sockaddr_in*)addr_dest)->sin_family = AF_INET;
                    ((struct sockaddr_in*)addr_dest)->sin_port = 0;
                    ((struct sockaddr_in*)addr_dest)->sin_addr.s_addr = pPktInfo->s_addr;

                    if (dest_if != NULL) {
                        *dest_if = 0;
                    }
                }
            }
#endif
            else if ((cmsg->cmsg_type == IP_TOS || cmsg->cmsg_type == IP_RECVTOS) && cmsg->cmsg_len > 0) {
                if (received_ecn != NULL) {
                    *received_ecn = *((unsigned char*)CMSG_DATA(cmsg));
                }
            }
        }
        else if (cmsg->cmsg_level == IPPROTO_IPV6) {
            if (cmsg->cmsg_type == IPV6_PKTINFO) {
                if (addr_dest != NULL) {
                    struct in6_pktinfo* pPktInfo6 = (struct in6_pktinfo*)CMSG_DATA(cmsg);

                    ((struct sockaddr_in6*)addr_dest)->sin6_family = AF_INET6;
                    ((struct sockaddr_in6*)addr_dest)->sin6_port = 0;
                    memcpy(&((struct sockaddr_in6*)addr_dest)->sin6_addr, &pPktInfo6->ipi6_addr, sizeof(struct in6_addr));

                    if (dest_if != NULL) {
                        *dest_if = (int)pPktInfo6->ipi6_ifindex;
                    }
                }
            }
            else if (cmsg->cmsg_type == IPV6_TCLASS) {
                if (cmsg->cmsg_len > 0 && received_ecn != NULL) {
                    *received_ecn = *((unsigned char*)CMSG_DATA(cmsg));
                }
            }
        }
    }
#endif
}

#ifdef _WINDOWS
static void* cmsg_format_header_return_data_ptr(WSAMSG* msg, struct cmsghdr** last_cmsg, int * control_length,
    INT cmsg_level, INT cmsg_type, size_t cmsg_data_len)
{
    void* cmsg_data_ptr = NULL;
    WSACMSGHDR* cmsg = (*last_cmsg == NULL)? WSA_CMSG_FIRSTHDR(msg): WSA_CMSG_NXTHDR(msg, *last_cmsg);

    if (cmsg != NULL) {
        size_t cmsg_required_space = WSA_CMSG_SPACE(cmsg_data_len);
        *control_length += (INT)cmsg_required_space;
        memset(cmsg, 0, cmsg_required_space);
        cmsg->cmsg_level = cmsg_level;
        cmsg->cmsg_type = cmsg_type;
        cmsg->cmsg_len = WSA_CMSG_LEN(cmsg_data_len);
        cmsg_data_ptr = (void*)WSA_CMSG_DATA(cmsg);
        *last_cmsg = cmsg;
    }

    return cmsg_data_ptr;
}
#endif

void picoquic_socks_cmsg_format(
    void* vmsg,
    size_t message_length,
    size_t send_msg_size,
    struct sockaddr* addr_from,
    int dest_if)
{
#ifdef _WINDOWS
    WSAMSG* msg = (WSAMSG*)vmsg;
    int control_length = 0;
    struct cmsghdr* last_cmsg = NULL;
    int is_null = 0;

    /* Format the control message */
    if (addr_from != NULL && addr_from->sa_family != 0) {
        if (addr_from->sa_family == AF_INET) {
            struct in_pktinfo* pktinfo = (struct in_pktinfo*)cmsg_format_header_return_data_ptr(msg, &last_cmsg,
                &control_length, IPPROTO_IP, IP_PKTINFO, sizeof(struct in_pktinfo));
            if (pktinfo != NULL) {
                pktinfo->ipi_addr.s_addr = ((struct sockaddr_in*)addr_from)->sin_addr.s_addr;
                pktinfo->ipi_ifindex = (unsigned long)dest_if;
            }
            else {
                is_null = 1;
            }

            if (!is_null && message_length > PICOQUIC_INITIAL_MTU_IPV4) {
                int* pval = (int*)cmsg_format_header_return_data_ptr(msg, &last_cmsg,
                    &control_length, IPPROTO_IP, IP_DONTFRAGMENT, sizeof(int));
                if (pval != NULL) {
                    *pval = 1;
                }
                else {
                    is_null = 1;
                }
            }
        }
        else {
            struct in6_pktinfo* pktinfo6 = (struct in6_pktinfo*)cmsg_format_header_return_data_ptr(msg, &last_cmsg,
                &control_length, IPPROTO_IPV6, IPV6_PKTINFO, sizeof(struct in6_pktinfo));
            if (pktinfo6 != NULL) {
                memcpy(&pktinfo6->ipi6_addr.u, &((struct sockaddr_in6*)addr_from)->sin6_addr.u, sizeof(IN6_ADDR));
                pktinfo6->ipi6_ifindex = (unsigned long)dest_if;
            }
            else {
                is_null = 1;
            }
            if (!is_null) {
                int* pval = (int*)cmsg_format_header_return_data_ptr(msg, &last_cmsg,
                    &control_length, IPPROTO_IPV6, IPV6_DONTFRAG, sizeof(int));
                if (pval != NULL) {
                    *pval = 1;
                }
                else {
                    is_null = 1;
                }
            }
        }
    }
    if (!is_null && send_msg_size > 0) {
        DWORD* pdw = (DWORD*)cmsg_format_header_return_data_ptr(msg, &last_cmsg,
            &control_length, IPPROTO_UDP, UDP_SEND_MSG_SIZE, sizeof(DWORD));
        if (pdw != NULL) {
            *pdw = (DWORD)send_msg_size;
        }
    }

    msg->Control.len = control_length;
    if (control_length == 0) {
        msg->Control.buf = NULL;
    }

#else
    struct msghdr* msg = (struct msghdr*)vmsg;
    int control_length = 0;
    struct cmsghdr* cmsg;
    /* Format the control message */
    cmsg = CMSG_FIRSTHDR(msg);

    if (addr_from != NULL && addr_from->sa_family != 0) {
        if (addr_from->sa_family == AF_INET) {
#ifdef IP_PKTINFO
            memset(cmsg, 0, CMSG_SPACE(sizeof(struct in_pktinfo)));
            cmsg->cmsg_level = IPPROTO_IP;
            cmsg->cmsg_type = IP_PKTINFO;
            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
            struct in_pktinfo* pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsg);
            pktinfo->ipi_addr.s_addr = ((struct sockaddr_in*)addr_from)->sin_addr.s_addr;
            pktinfo->ipi_ifindex = (unsigned int)dest_if;
            control_length += CMSG_SPACE(sizeof(struct in_pktinfo));
#else
            /* The IP_PKTINFO structure is not defined on BSD */
            memset(cmsg, 0, CMSG_SPACE(sizeof(struct in_addr)));
            cmsg->cmsg_level = IPPROTO_IP;
            cmsg->cmsg_type = IP_SENDSRCADDR;
            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
            struct in_addr* pktinfo = (struct in_addr*)CMSG_DATA(cmsg);
            pktinfo->s_addr = ((struct sockaddr_in*)addr_from)->sin_addr.s_addr;
            control_length += CMSG_SPACE(sizeof(struct in_addr));
#endif
        }
        else if (addr_from->sa_family == AF_INET6) {
            memset(cmsg, 0, CMSG_SPACE(sizeof(struct in6_pktinfo)));
            cmsg->cmsg_level = IPPROTO_IPV6;
            cmsg->cmsg_type = IPV6_PKTINFO;
            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
            struct in6_pktinfo* pktinfo6 = (struct in6_pktinfo*)CMSG_DATA(cmsg);
            memcpy(&pktinfo6->ipi6_addr, &((struct sockaddr_in6*)addr_from)->sin6_addr, sizeof(struct in6_addr));
            pktinfo6->ipi6_ifindex = dest_if;

            control_length += CMSG_SPACE(sizeof(struct in6_pktinfo));
        }
        else {
            DBG_PRINTF("Unexpected address family: %d\n", addr_from->sa_family);
        }
#ifdef IPV6_DONTFRAG
        if (addr_from->sa_family == AF_INET6) {
#ifdef CMSG_ALIGN
            struct cmsghdr* cmsg_2 = (struct cmsghdr*)((unsigned char*)cmsg + CMSG_ALIGN(cmsg->cmsg_len));
            {
#else
            struct cmsghdr* cmsg_2 = CMSG_NXTHDR((msg), cmsg);
            if (cmsg_2 == NULL) {
                DBG_PRINTF("Cannot obtain second CMSG (control_length: %d)\n", control_length);
            }
            else {
#endif
                int val = 1;
                cmsg_2->cmsg_level = IPPROTO_IPV6;
                cmsg_2->cmsg_type = IPV6_DONTFRAG;
                cmsg_2->cmsg_len = CMSG_LEN(sizeof(int));
                memcpy(CMSG_DATA(cmsg_2), &val, sizeof(int));
                control_length += CMSG_SPACE(sizeof(int));
            }
        }
#endif
#if defined(IP_DONTFRAG)
        if (addr_from->sa_family == AF_INET6 && length > PICOQUIC_INITIAL_MTU_IPV6) {
#ifdef CMSG_ALIGN
            struct cmsghdr* cmsg_2 = (struct cmsghdr*)((unsigned char*)cmsg + CMSG_ALIGN(cmsg->cmsg_len));
#else
            struct cmsghdr* cmsg_2 = CMSG_NXTHDR((msg), cmsg);
#endif
            if (cmsg_2 == NULL) {
                DBG_PRINTF("Cannot obtain second CMSG (control_length: %d)\n", control_length);
            }
            else
            {
                /* On BSD systems, just use IP_DONTFRAG */
                int val = 1;
                cmsg_2->cmsg_level = IPPROTO_IP;
                cmsg_2->cmsg_type = IP_DONTFRAG;
                cmsg_2->cmsg_len = CMSG_LEN(sizeof(int));
                memcpy(CMSG_DATA(cmsg_2), &val, sizeof(int));
                control_length += CMSG_SPACE(sizeof(int));
            }
        }
#endif
    }

    msg->msg_controllen = control_length;
    if (control_length == 0) {
        msg->msg_control = NULL;
    }
#endif
}


#ifdef _WINDOWS

void picoquic_delete_async_socket(picoquic_recvmsg_async_ctx_t * ctx)
{
    if (ctx->fd != INVALID_SOCKET) {
        SOCKET_CLOSE(ctx->fd);
        ctx->fd = INVALID_SOCKET;
    }

    if (ctx->overlap.hEvent != WSA_INVALID_EVENT) {
        WSACloseEvent(ctx->overlap.hEvent);
        ctx->overlap.hEvent = WSA_INVALID_EVENT;
    }

    free(ctx);
}

picoquic_recvmsg_async_ctx_t * picoquic_create_async_socket(int af, int recv_coalesced, int send_coalesced)
{
    int ret = 0;
    int last_error = 0;
    picoquic_recvmsg_async_ctx_t * ctx = (picoquic_recvmsg_async_ctx_t *)malloc(sizeof(picoquic_recvmsg_async_ctx_t));

    if (ctx == NULL) {
        DBG_PRINTF("Could not create async socket context, AF = %d!\n", af);
    }
    else {
        memset(ctx, 0, sizeof(picoquic_recvmsg_async_ctx_t));
        ctx->overlap.hEvent = WSA_INVALID_EVENT;

        ctx->fd = picoquic_open_client_socket(af);

        if (ctx->fd == INVALID_SOCKET) {
            last_error = WSAGetLastError();
            DBG_PRINTF("Could not initialize UDP socket, AF = %d, err=%d!\n",
                af, last_error);
            ret = -1;
        }
        else {
            GUID WSARecvMsg_GUID = WSAID_WSARECVMSG;
            DWORD NumberOfBytes;
            int nResult = WSAIoctl(ctx->fd, SIO_GET_EXTENSION_FUNCTION_POINTER,
                &WSARecvMsg_GUID, sizeof(WSARecvMsg_GUID),
                &ctx->WSARecvMsg, sizeof(ctx->WSARecvMsg),
                &NumberOfBytes, NULL, NULL);

            if (nResult == SOCKET_ERROR) {
                last_error = WSAGetLastError();
                DBG_PRINTF("Could not initialize WSARecvMsg on UDP socket %d= %d!\n",
                    (int)ctx->fd, last_error);
                ret = -1;
            }
            else {
                GUID WSASendMsg_GUID = WSAID_WSASENDMSG;
                nResult = WSAIoctl(ctx->fd, SIO_GET_EXTENSION_FUNCTION_POINTER,
                    &WSASendMsg_GUID, sizeof(WSASendMsg_GUID),
                    &ctx->WSASendMsg, sizeof(ctx->WSASendMsg),
                    &NumberOfBytes, NULL, NULL);

                if (nResult == SOCKET_ERROR) {
                    last_error = WSAGetLastError();
                    DBG_PRINTF("Could not initialize WSASendMsg on UDP socket %d= %d!\n",
                        (int)ctx->fd, last_error);
                    ret = -1;
                }
                else {
                    // ctx->overlap.hEvent = WSACreateEvent();
                    ctx->overlap.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
                    if (ctx->overlap.hEvent == WSA_INVALID_EVENT) {
                        last_error = WSAGetLastError();
                        DBG_PRINTF("Could not create WSA event for UDP socket %d= %d!\n",
                            (int)ctx->fd, last_error);
                        ret = -1;
                    }
                }
#ifdef UDP_RECV_MAX_COALESCED_SIZE
                if (ret == 0 && recv_coalesced) {
                    int n_messages = (recv_coalesced) ? 1 : 10;
                    DWORD coalesced_size = n_messages * PICOQUIC_MAX_PACKET_SIZE;
                    ctx->recv_buffer_size = coalesced_size;
                    ctx->recv_buffer = (uint8_t*)malloc(ctx->recv_buffer_size);
                    ctx->supports_udp_recv_coalesced = recv_coalesced;
                    ctx->supports_udp_send_coalesced = send_coalesced;
                    if (ctx->recv_buffer == NULL) {
                        DBG_PRINTF("Could allocate buffer size %zu for socket %d!\n",
                            ctx->recv_buffer_size, (int)ctx->fd);
                        ret = -1;
                    }
                    else if ((ret = setsockopt(ctx->fd, IPPROTO_UDP, UDP_RECV_MAX_COALESCED_SIZE, (char*)&coalesced_size,
                        (int)sizeof(coalesced_size))) != 0) {
                        last_error = GetLastError();
                        DBG_PRINTF("Cannot set UDP_RECV_MAX_COALESCED_SIZE %d, returns %d (%d)",
                            coalesced_size, ret, last_error);
                        ret = -1;
                    }
                }
#endif
            }
        }

        if (ret != 0) {
            picoquic_delete_async_socket(ctx);
            ctx = NULL;
        }
    }

    return ctx;
}

int picoquic_recvmsg_async_finish(
    picoquic_recvmsg_async_ctx_t * ctx)
{
    DWORD cbTransferred = 0;
    DWORD ret = 0;
    DWORD flags = 0;

    if (ctx == NULL) {
        return -1;
    }

    if (!WSAGetOverlappedResult(ctx->fd, &ctx->overlap, &cbTransferred, FALSE, &flags)) {
        ret = WSAGetLastError();
        if (ret == WSAECONNRESET) {
            ctx->bytes_recv = 0;
            ret = 0;
        }
        else {
            DBG_PRINTF("Could not complete async call (WSARecvMsg) on UDP socket %d = %d!\n",
                (int)ctx->fd, ret);
            ctx->bytes_recv = -1;
        }
    } 
    else {
        ctx->bytes_recv = cbTransferred;
        ctx->from_length = ctx->msg.namelen;

        picoquic_socks_cmsg_parse(&ctx->msg, &ctx->addr_dest, &ctx->dest_if, &ctx->received_ecn, &ctx->udp_coalesced_size);
    }

    return ret;
}

int picoquic_recvmsg_async_start(picoquic_recvmsg_async_ctx_t* ctx)
{
    int last_error;
    int ret = 0;
    DWORD numberOfBytesReceived = 0;
    int should_retry;

    do {
        should_retry = 0;
        ctx->from_length = 0;
        ctx->dest_length = 0;
        ctx->dest_if = 0;
        ctx->received_ecn = 0;
        ctx->bytes_recv = 0;
        ctx->udp_coalesced_size = 0;

        ctx->overlap.Internal = 0;
        ctx->overlap.InternalHigh = 0;
        ctx->overlap.Offset = 0;
        ctx->overlap.OffsetHigh = 0;

        ctx->dataBuf.buf = (char*)ctx->recv_buffer;
        ctx->dataBuf.len = (ULONG)ctx->recv_buffer_size;

        ctx->msg.name = (struct sockaddr*) & ctx->addr_from;
        ctx->msg.namelen = sizeof(ctx->addr_from);
        ctx->msg.lpBuffers = &ctx->dataBuf;
        ctx->msg.dwBufferCount = 1;
        ctx->msg.dwFlags = 0;
        ctx->msg.Control.buf = ctx->cmsg_buffer;
        ctx->msg.Control.len = sizeof(ctx->cmsg_buffer);

        /* Setting the &nbReceived parameter to NULL to force async behavior */
        ret = ctx->WSARecvMsg(ctx->fd, &ctx->msg, &numberOfBytesReceived, &ctx->overlap, NULL);

        if (ret != 0) {
            last_error = WSAGetLastError();
            if (last_error == WSA_IO_PENDING) {
                ret = 0;
            }
            else if (last_error == WSAECONNRESET) {
                /* Ignore the ICMP errors */
                should_retry = 1;
                ret = 0;
            }
            else {
                DBG_PRINTF("Could not start receive async (WSARecvMsg) on UDP socket %d = %d!\n",
                    (int)ctx->fd, last_error);
                ctx->bytes_recv = -1;
            }
        }
        else {
            DBG_PRINTF("Receive async immediate (WSARecvMsg) on UDP socket %d -- %d bytes !\n",
                (int)ctx->fd, numberOfBytesReceived);
            ctx->nb_immediate_receive++;
        }
    } while (should_retry);

    return ret;
}

#endif

int picoquic_recvmsg(SOCKET_TYPE fd,
    struct sockaddr_storage* addr_from,
    struct sockaddr_storage* addr_dest,
    int* dest_if,
    unsigned char* received_ecn,
    uint8_t* buffer, int buffer_max)
#ifdef _WINDOWS
{
    GUID WSARecvMsg_GUID = WSAID_WSARECVMSG;
    LPFN_WSARECVMSG WSARecvMsg;
    char cmsg_buffer[1024];
    DWORD NumberOfBytes;
    int nResult;
    WSAMSG msg;
    WSABUF dataBuf;
    int recv_ret = 0;
    int bytes_recv;
    int last_error;

    if (dest_if != NULL) {
        *dest_if = 0;
    }

    if (received_ecn != NULL) {
        *received_ecn = 0;
    }

    nResult = WSAIoctl(fd, SIO_GET_EXTENSION_FUNCTION_POINTER,
        &WSARecvMsg_GUID, sizeof WSARecvMsg_GUID,
        &WSARecvMsg, sizeof WSARecvMsg,
        &NumberOfBytes, NULL, NULL);

    if (nResult == SOCKET_ERROR) {
        last_error = WSAGetLastError();
        DBG_PRINTF("Could not initialize WSARecvMsg) on UDP socket %d= %d!\n",
            (int)fd, last_error);
        bytes_recv = -1;
    } else {
        dataBuf.buf = (char*)buffer;
        dataBuf.len = buffer_max;

        msg.name = (struct sockaddr*)addr_from;
        msg.namelen = sizeof(struct sockaddr_storage);
        msg.lpBuffers = &dataBuf;
        msg.dwBufferCount = 1;
        msg.dwFlags = 0;
        msg.Control.buf = cmsg_buffer;
        msg.Control.len = sizeof(cmsg_buffer);

        recv_ret = WSARecvMsg(fd, &msg, &NumberOfBytes, NULL, NULL);

        if (recv_ret != 0) {
            last_error = WSAGetLastError();
            DBG_PRINTF("Could not receive message (WSARecvMsg) on UDP socket %d = %d!\n",
                (int)fd, last_error);
            bytes_recv = -1;
        } else {
            bytes_recv = NumberOfBytes;
            picoquic_socks_cmsg_parse(&msg, addr_dest, dest_if, received_ecn, NULL);
        }
    }

    return bytes_recv;
}
#else
{
    int bytes_recv = 0;
    struct msghdr msg;
    struct iovec dataBuf;
    char cmsg_buffer[1024];

    if (dest_if != NULL) {
        *dest_if = 0;
    }

    dataBuf.iov_base = (char*)buffer;
    dataBuf.iov_len = buffer_max;

    msg.msg_name = (struct sockaddr*)addr_from;
    msg.msg_namelen = sizeof(struct sockaddr_storage);
    msg.msg_iov = &dataBuf;
    msg.msg_iovlen = 1;
    msg.msg_flags = 0;
    msg.msg_control = (void*)cmsg_buffer;
    msg.msg_controllen = sizeof(cmsg_buffer);

    bytes_recv = recvmsg(fd, &msg, 0);

    if (bytes_recv <= 0) {
        addr_from->ss_family = 0;
    } else {
        picoquic_socks_cmsg_parse(&msg, addr_dest, dest_if, received_ecn, NULL);
    }

    return bytes_recv;
}
#endif

int picoquic_sendmsg(SOCKET_TYPE fd,
    struct sockaddr* addr_dest,
    struct sockaddr* addr_from,
    int dest_if,
    const char* bytes, int length)
#ifdef _WINDOWS
{
    GUID WSASendMsg_GUID = WSAID_WSASENDMSG;
    LPFN_WSASENDMSG WSASendMsg;
    char cmsg_buffer[1024];
    DWORD NumberOfBytes;
    int ret = 0;
    DWORD dwBytesSent = 0;
    WSAMSG msg;
    WSABUF dataBuf;
    int bytes_sent;
    int last_error;

    ret = WSAIoctl(fd, SIO_GET_EXTENSION_FUNCTION_POINTER,
        &WSASendMsg_GUID, sizeof WSASendMsg_GUID,
        &WSASendMsg, sizeof WSASendMsg,
        &NumberOfBytes, NULL, NULL);

    if (ret == SOCKET_ERROR) {
        last_error = WSAGetLastError();
        DBG_PRINTF("Could not initialize WSASendMsg on UDP socket %d= %d!\n",
            (int)fd, last_error);
        bytes_sent = -1;
    }
    else {
        /* Format the control message header */
        memset(&msg, 0, sizeof(msg));
        msg.name = addr_dest;
        msg.namelen = picoquic_addr_length(addr_dest);
        dataBuf.buf = (char*)bytes;
        dataBuf.len = length;
        msg.lpBuffers = &dataBuf;
        msg.dwBufferCount = 1;
        msg.Control.buf = (char*)cmsg_buffer;
        msg.Control.len = sizeof(cmsg_buffer);

        /* Format the control message */
        picoquic_socks_cmsg_format(&msg, length, 0, addr_from, dest_if);

        /* Send the message */
        ret = WSASendMsg(fd, &msg, 0, &dwBytesSent, NULL, NULL);

        if (ret != 0) {
            bytes_sent = -1;
        } else {
            bytes_sent = (int)dwBytesSent;
        }
    }

    return bytes_sent;
}
#else
{
    struct msghdr msg;
    struct iovec dataBuf;
    char cmsg_buffer[1024];
    int bytes_sent;

    /* Format the message header */

    dataBuf.iov_base = (char*)bytes;
    dataBuf.iov_len = length;

    memset(&msg, 0, sizeof(msg));
    msg.msg_name = addr_dest;
    msg.msg_namelen = picoquic_addr_length(addr_dest);
    msg.msg_iov = &dataBuf;
    msg.msg_iovlen = 1;
    msg.msg_control = (void*)cmsg_buffer;
    msg.msg_controllen = sizeof(cmsg_buffer);

    /* Format the control message */
    picoquic_socks_cmsg_format(&msg, length, 0, addr_from, dest_if);

    bytes_sent = sendmsg(fd, &msg, 0);

    return bytes_sent;
}
#endif

int picoquic_select_ex(SOCKET_TYPE* sockets,
    int nb_sockets,
    struct sockaddr_storage* addr_from,
    struct sockaddr_storage* addr_dest,
    int* dest_if,
    unsigned char * received_ecn,
    uint8_t* buffer, int buffer_max,
    int64_t delta_t,
    int * socket_rank,
    uint64_t* current_time)
{
    fd_set readfds;
    struct timeval tv;
    int ret_select = 0;
    int bytes_recv = 0;
    int sockmax = 0;

    if (received_ecn != NULL) {
        *received_ecn = 0;
    }

    FD_ZERO(&readfds);

    for (int i = 0; i < nb_sockets; i++) {
        if (sockmax < (int)sockets[i]) {
            sockmax = (int)sockets[i];
        }
        FD_SET(sockets[i], &readfds);
    }

    if (delta_t <= 0) {
        tv.tv_sec = 0;
        tv.tv_usec = 0;
    } else {
        if (delta_t > 10000000) {
            tv.tv_sec = (long)10;
            tv.tv_usec = 0;
        } else {
            tv.tv_sec = (long)(delta_t / 1000000);
            tv.tv_usec = (long)(delta_t % 1000000);
        }
    }

    ret_select = select(sockmax + 1, &readfds, NULL, NULL, &tv);

    if (ret_select < 0) {
        bytes_recv = -1;
        DBG_PRINTF("Error: select returns %d\n", ret_select);
    } else if (ret_select > 0) {
        for (int i = 0; i < nb_sockets; i++) {
            if (FD_ISSET(sockets[i], &readfds)) {
                *socket_rank = i;
                bytes_recv = picoquic_recvmsg(sockets[i], addr_from,
                    addr_dest, dest_if, received_ecn,
                    buffer, buffer_max);

                if (bytes_recv <= 0) {
#ifdef _WINDOWS
                    int last_error = WSAGetLastError();

                    if (last_error == WSAECONNRESET || last_error == WSAEMSGSIZE) {
                        bytes_recv = 0;
                        continue;
                    }
#endif
                    DBG_PRINTF("Could not receive packet on UDP socket[%d]= %d!\n",
                        i, (int)sockets[i]);

                    break;
                } else {
                    break;
                }
            }
        }
    }

    *current_time = picoquic_current_time();

    return bytes_recv;
}

int picoquic_select(SOCKET_TYPE* sockets,
    int nb_sockets,
    struct sockaddr_storage* addr_from,
    struct sockaddr_storage* addr_dest,
    int* dest_if,
    unsigned char* received_ecn,
    uint8_t* buffer, int buffer_max,
    int64_t delta_t,
    uint64_t* current_time) {
    int socket_rank;
    return picoquic_select_ex(sockets, nb_sockets, addr_from, addr_dest, dest_if,
        received_ecn, buffer, buffer_max, delta_t, &socket_rank, current_time);
}

int picoquic_send_through_socket(
    SOCKET_TYPE fd,
    struct sockaddr* addr_dest,
    struct sockaddr* addr_from, int from_if,
    const char* bytes, int length, int* sock_err)
{
    int sent = picoquic_sendmsg(fd, addr_dest, addr_from, from_if, bytes, length);

#ifndef DISABLE_DEBUG_PRINTF
    if (sent <= 0) {
#ifdef _WINDOWS
        int last_error = WSAGetLastError();
#else
        int last_error = errno;
#endif
        DBG_PRINTF("Could not send packet on UDP socket[AF=%d]= %d!\n",
            addr_dest->sa_family, last_error);
        if (sock_err != NULL) {
            *sock_err = last_error;
        }
    }
#endif

    return sent;
}

int picoquic_send_through_server_sockets(
    picoquic_server_sockets_t* sockets,
    struct sockaddr* addr_dest,
    struct sockaddr* addr_from, int from_if,
    const char* bytes, int length, int* sock_err)
{
    /* Both Linux and Windows use separate sockets for V4 and V6 */
    int socket_index = (addr_dest->sa_family == AF_INET) ? 1 : 0;

    return picoquic_send_through_socket(sockets->s_socket[socket_index], addr_dest, addr_from, from_if, bytes, length, sock_err);
}

int picoquic_get_server_address(const char* ip_address_text, int server_port,
    struct sockaddr_storage* server_address, int* is_name)
{
    int ret = 0;
    struct sockaddr_in* ipv4_dest = (struct sockaddr_in*)server_address;
    struct sockaddr_in6* ipv6_dest = (struct sockaddr_in6*)server_address;

    /* get the IP address of the server */
    memset(server_address, 0, sizeof(struct sockaddr_storage));
    *is_name = 0;

    if (inet_pton(AF_INET, ip_address_text, &ipv4_dest->sin_addr) == 1) {
        /* Valid IPv4 address */
        ipv4_dest->sin_family = AF_INET;
        ipv4_dest->sin_port = htons((unsigned short)server_port);
    } else if (inet_pton(AF_INET6, ip_address_text, &ipv6_dest->sin6_addr) == 1) {
        /* Valid IPv6 address */
        ipv6_dest->sin6_family = AF_INET6;
        ipv6_dest->sin6_port = htons((unsigned short)server_port);
    } else {
        /* Server is described by name. Do a lookup for the IP address,
        * and then use the name as SNI parameter */
        struct addrinfo* result = NULL;
        struct addrinfo hints;

        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_DGRAM;
        hints.ai_protocol = IPPROTO_UDP;

        if ((ret = getaddrinfo(ip_address_text, NULL, &hints, &result)) != 0) {
#ifdef _WINDOWS
            int err = GetLastError();
#else
            int err = ret;
#endif
            fprintf(stderr, "Cannot get IP address for %s, err = %d (0x%x)\n", ip_address_text, err, err);
            ret = -1;
        } else {
            *is_name = 1;

            switch (result->ai_family) {
            case AF_INET:
                ipv4_dest->sin_family = AF_INET;
                ipv4_dest->sin_port = htons((unsigned short)server_port);
#ifdef _WINDOWS
                ipv4_dest->sin_addr.S_un.S_addr = ((struct sockaddr_in*)result->ai_addr)->sin_addr.S_un.S_addr;
#else
                ipv4_dest->sin_addr.s_addr = ((struct sockaddr_in*)result->ai_addr)->sin_addr.s_addr;
#endif
                break;
            case AF_INET6:
                ipv6_dest->sin6_family = AF_INET6;
                ipv6_dest->sin6_port = htons((unsigned short)server_port);
                memcpy(&ipv6_dest->sin6_addr,
                    &((struct sockaddr_in6*)result->ai_addr)->sin6_addr,
                    sizeof(ipv6_dest->sin6_addr));
                break;
            default:
                fprintf(stderr, "Error getting IPv6 address for %s, family = %d\n",
                    ip_address_text, result->ai_family);
                ret = -1;
                break;
            }

            freeaddrinfo(result);
        }
    }

    return ret;
}

/* Wireshark needs the session keys in order to decrypt and analyze packets.
 * In Unix and Windows, Wireshark reads these keys from a file. The name
 * of the file is passed in the environment variable SSLKEYLOGFILE,
 * which is accessed through system dependent API.
 */

void picoquic_set_key_log_file_from_env(picoquic_quic_t* quic)
{
    char* keylog_filename = NULL;

#ifdef _WINDOWS
    size_t len;
    
    if (_dupenv_s(&keylog_filename, &len, "SSLKEYLOGFILE") != 0 ||
        keylog_filename == NULL) {
        return;
    }
#else
    keylog_filename = getenv("SSLKEYLOGFILE");
    if (keylog_filename == NULL) {
        return;
    }
#endif

    picoquic_set_key_log_file(quic, keylog_filename);
}
