/*
 * SPDX-License-Identifier: MIT
 *
 * Copyright (C) 2015-2017 Michael Jeanson <mjeanson@efficios.com>
 * Copyright (C) 2015 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
 */

#ifndef BABELTRACE_COMPAT_SOCKET_HPP
#define BABELTRACE_COMPAT_SOCKET_HPP

#include <stdbool.h>

#include "cpp-common/bt2c/logging.hpp"

#ifdef __MINGW32__

#    include <winsock2.h>

#    define BT_INVALID_SOCKET INVALID_SOCKET
#    define BT_SOCKET_ERROR   SOCKET_ERROR
#    define BT_SOCKET         SOCKET

static inline int bt_socket_init(const bt2c::Logger& logger)
{
    WORD verreq;
    WSADATA wsa;
    int ret;

    /* Request winsock 2.2 support */
    verreq = MAKEWORD(2, 2);

    ret = WSAStartup(verreq, &wsa);
    if (ret != 0) {
        BT_CPPLOGE_SPEC(logger, "Winsock init failed with error: {}", ret);
        goto end;
    }

    if (LOBYTE(wsa.wVersion) != 2 || HIBYTE(wsa.wVersion) != 2) {
        BT_CPPLOGE_SPEC(logger, "Could not init winsock 2.2 support");
        WSACleanup();
        ret = -1;
    }

end:
    return ret;
}

static inline int bt_socket_fini(void)
{
    return WSACleanup();
}

static inline int bt_socket_send(int sockfd, const void *buf, size_t len, int flags)
{
    return send(sockfd, (const char *) buf, len, flags);
}

static inline int bt_socket_recv(int sockfd, void *buf, size_t len, int flags)
{
    return recv(sockfd, (char *) buf, len, flags);
}

static inline int bt_socket_close(int fd)
{
    return closesocket(fd);
}

static inline bool bt_socket_interrupted(void)
{
    /* There is no equivalent to EINTR in winsock 2.2 */
    return false;
}

static inline const char *bt_socket_errormsg(void)
{
    const char *errstr;
    int error = WSAGetLastError();

    switch (error) {
    case WSAEINTR:
        errstr = "Call interrupted";
        break;
    case WSAEBADF:
        errstr = "Bad file";
        break;
    case WSAEACCES:
        errstr = "Bad access";
        break;
    case WSAEFAULT:
        errstr = "Bad argument";
        break;
    case WSAEINVAL:
        errstr = "Invalid arguments";
        break;
    case WSAEMFILE:
        errstr = "Out of file descriptors";
        break;
    case WSAEWOULDBLOCK:
        errstr = "Call would block";
        break;
    case WSAEINPROGRESS:
    case WSAEALREADY:
        errstr = "Blocking call in progress";
        break;
    case WSAENOTSOCK:
        errstr = "Descriptor is not a socket";
        break;
    case WSAEDESTADDRREQ:
        errstr = "Need destination address";
        break;
    case WSAEMSGSIZE:
        errstr = "Bad message size";
        break;
    case WSAEPROTOTYPE:
        errstr = "Bad protocol";
        break;
    case WSAENOPROTOOPT:
        errstr = "Protocol option is unsupported";
        break;
    case WSAEPROTONOSUPPORT:
        errstr = "Protocol is unsupported";
        break;
    case WSAESOCKTNOSUPPORT:
        errstr = "Socket is unsupported";
        break;
    case WSAEOPNOTSUPP:
        errstr = "Operation not supported";
        break;
    case WSAEAFNOSUPPORT:
        errstr = "Address family not supported";
        break;
    case WSAEPFNOSUPPORT:
        errstr = "Protocol family not supported";
        break;
    case WSAEADDRINUSE:
        errstr = "Address already in use";
        break;
    case WSAEADDRNOTAVAIL:
        errstr = "Address not available";
        break;
    case WSAENETDOWN:
        errstr = "Network down";
        break;
    case WSAENETUNREACH:
        errstr = "Network unreachable";
        break;
    case WSAENETRESET:
        errstr = "Network has been reset";
        break;
    case WSAECONNABORTED:
        errstr = "Connection was aborted";
        break;
    case WSAECONNRESET:
        errstr = "Connection was reset";
        break;
    case WSAENOBUFS:
        errstr = "No buffer space";
        break;
    case WSAEISCONN:
        errstr = "Socket is already connected";
        break;
    case WSAENOTCONN:
        errstr = "Socket is not connected";
        break;
    case WSAESHUTDOWN:
        errstr = "Socket has been shut down";
        break;
    case WSAETOOMANYREFS:
        errstr = "Too many references";
        break;
    case WSAETIMEDOUT:
        errstr = "Timed out";
        break;
    case WSAECONNREFUSED:
        errstr = "Connection refused";
        break;
    case WSAELOOP:
        errstr = "Loop??";
        break;
    case WSAENAMETOOLONG:
        errstr = "Name too long";
        break;
    case WSAEHOSTDOWN:
        errstr = "Host down";
        break;
    case WSAEHOSTUNREACH:
        errstr = "Host unreachable";
        break;
    case WSAENOTEMPTY:
        errstr = "Not empty";
        break;
    case WSAEPROCLIM:
        errstr = "Process limit reached";
        break;
    case WSAEUSERS:
        errstr = "Too many users";
        break;
    case WSAEDQUOT:
        errstr = "Bad quota";
        break;
    case WSAESTALE:
        errstr = "Something is stale";
        break;
    case WSAEREMOTE:
        errstr = "Remote error";
        break;
    case WSAEDISCON:
        errstr = "Disconnected";
        break;

    /* Extended Winsock errors */
    case WSASYSNOTREADY:
        errstr = "Winsock library is not ready";
        break;
    case WSANOTINITIALISED:
        errstr = "Winsock library not initialised";
        break;
    case WSAVERNOTSUPPORTED:
        errstr = "Winsock version not supported";
        break;

    /* getXbyY() errors (already handled in herrmsg):
	 * Authoritative Answer: Host not found */
    case WSAHOST_NOT_FOUND:
        errstr = "Host not found";
        break;

    /* Non-Authoritative: Host not found, or SERVERFAIL */
    case WSATRY_AGAIN:
        errstr = "Host not found, try again";
        break;

    /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */
    case WSANO_RECOVERY:
        errstr = "Unrecoverable error in call to nameserver";
        break;

    /* Valid name, no data record of requested type */
    case WSANO_DATA:
        errstr = "No data record of requested type";
        break;

    default:
        errstr = "Unknown error";
    }

    return errstr;
}

#else /* __MINGW32__ */

#    include <errno.h>
#    include <glib.h>
#    include <netdb.h>
#    include <netinet/in.h>
#    include <sys/socket.h>
#    include <unistd.h>

#    define BT_INVALID_SOCKET -1
#    define BT_SOCKET_ERROR   -1
#    define BT_SOCKET         int

static inline int bt_socket_init(const bt2c::Logger&)
{
    return 0;
}

static inline int bt_socket_fini(void)
{
    return 0;
}

static inline int bt_socket_send(int sockfd, const void *buf, size_t len, int flags)
{
    return send(sockfd, buf, len, flags);
}

static inline int bt_socket_recv(int sockfd, void *buf, size_t len, int flags)
{
    return recv(sockfd, buf, len, flags);
}

static inline int bt_socket_close(int fd)
{
    return close(fd);
}

static inline bool bt_socket_interrupted(void)
{
    return (errno == EINTR);
}

static inline const char *bt_socket_errormsg(void)
{
    return g_strerror(errno);
}
#endif

/*
 * This wrapper is used on platforms that have no way of ignoring SIGPIPE
 * during a send().
 */

#ifndef MSG_NOSIGNAL
#    ifdef SO_NOSIGPIPE
#        define MSG_NOSIGNAL SO_NOSIGPIPE
#    elif defined(__MINGW32__)
#        define MSG_NOSIGNAL 0
#    endif
#endif

#if defined(MSG_NOSIGNAL)
static inline ssize_t bt_socket_send_nosigpipe(int fd, const void *buffer, size_t size)
{
    return bt_socket_send(fd, buffer, size, MSG_NOSIGNAL);
}
#else

#    include <signal.h>

static inline ssize_t bt_socket_send_nosigpipe(int fd, const void *buffer, size_t size)
{
    ssize_t sent;
    int saved_err;
    sigset_t sigpipe_set, pending_set, old_set;
    int sigpipe_was_pending;

    /*
	 * Discard the SIGPIPE from send(), not disturbing any SIGPIPE
	 * that might be already pending. If a bogus SIGPIPE is sent to
	 * the entire process concurrently by a malicious user, it may
	 * be simply discarded.
	 */
    if (sigemptyset(&pending_set)) {
        return -1;
    }
    /*
	 * sigpending returns the mask of signals that are _both_
	 * blocked for the thread _and_ pending for either the thread or
	 * the entire process.
	 */
    if (sigpending(&pending_set)) {
        return -1;
    }
    sigpipe_was_pending = sigismember(&pending_set, SIGPIPE);
    /*
	 * If sigpipe was pending, it means it was already blocked, so
	 * no need to block it.
	 */
    if (!sigpipe_was_pending) {
        if (sigemptyset(&sigpipe_set)) {
            return -1;
        }
        if (sigaddset(&sigpipe_set, SIGPIPE)) {
            return -1;
        }
        if (pthread_sigmask(SIG_BLOCK, &sigpipe_set, &old_set)) {
            return -1;
        }
    }

    /* Send and save errno. */
    sent = bt_socket_send(fd, buffer, size, 0);
    saved_err = errno;

    if (sent == -1 && errno == EPIPE && !sigpipe_was_pending) {
        struct timespec timeout = {0, 0};
        int ret;

        do {
            ret = sigtimedwait(&sigpipe_set, NULL, &timeout);
        } while (ret == -1 && errno == EINTR);
    }
    if (!sigpipe_was_pending) {
        if (pthread_sigmask(SIG_SETMASK, &old_set, NULL)) {
            return -1;
        }
    }
    /* Restore send() errno */
    errno = saved_err;

    return sent;
}
#endif

#endif /* BABELTRACE_COMPAT_SOCKET_HPP */
