/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This file defines _PR_MapOptionName().  The purpose of putting
 * _PR_MapOptionName() in a separate file is to work around a Winsock
 * header file problem on Windows NT.
 *
 * On Windows NT, if we define _WIN32_WINNT to be 0x0400 (in order
 * to use Service Pack 3 extensions), windows.h includes winsock2.h
 * (instead of winsock.h), which doesn't define many socket options
 * defined in winsock.h.
 *
 * We need the socket options defined in winsock.h.  So this file
 * includes winsock.h, with _WIN32_WINNT undefined.
 */

#if defined(WINNT) || defined(__MINGW32__)
#include <winsock.h>
#endif

/* MinGW doesn't define these in its winsock.h. */
#ifdef __MINGW32__
#ifndef IP_TTL
#define IP_TTL 7
#endif
#ifndef IP_TOS
#define IP_TOS 8
#endif
#endif

#include "primpl.h"

#if defined(LINUX) || defined(ANDROID)
#include <netinet/in.h>
#endif

#ifdef DARWIN
#include <netinet/in.h>
#include <netinet/ip.h>
#endif

#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>  /* TCP_NODELAY, TCP_MAXSEG */
#endif

#ifndef _PR_PTHREADS

PRStatus PR_CALLBACK _PR_SocketGetSocketOption(PRFileDesc *fd, PRSocketOptionData *data)
{
    PRStatus rv;
    PRInt32 length;
    PRInt32 level, name;

    /*
     * PR_SockOpt_Nonblocking is a special case that does not
     * translate to a getsockopt() call
     */
    if (PR_SockOpt_Nonblocking == data->option)
    {
        data->value.non_blocking = fd->secret->nonblocking;
        return PR_SUCCESS;
    }

    rv = _PR_MapOptionName(data->option, &level, &name);
    if (PR_SUCCESS == rv)
    {
        switch (data->option)
        {
            case PR_SockOpt_Linger:
            {
                struct linger linger;
                length = sizeof(linger);
                rv = _PR_MD_GETSOCKOPT(
                         fd, level, name, (char *) &linger, &length);
                if (PR_SUCCESS == rv)
                {
                    PR_ASSERT(sizeof(linger) == length);
                    data->value.linger.polarity =
                        (linger.l_onoff) ? PR_TRUE : PR_FALSE;
                    data->value.linger.linger =
                        PR_SecondsToInterval(linger.l_linger);
                }
                break;
            }
            case PR_SockOpt_Reuseaddr:
            case PR_SockOpt_Keepalive:
            case PR_SockOpt_NoDelay:
            case PR_SockOpt_Broadcast:
            case PR_SockOpt_Reuseport:
            {
#ifdef WIN32 /* Winsock */
                BOOL value;
#else
                PRIntn value;
#endif
                length = sizeof(value);
                rv = _PR_MD_GETSOCKOPT(
                         fd, level, name, (char*)&value, &length);
                if (PR_SUCCESS == rv) {
                    data->value.reuse_addr = (0 == value) ? PR_FALSE : PR_TRUE;
                }
                break;
            }
            case PR_SockOpt_McastLoopback:
            {
#ifdef WIN32 /* Winsock */
                BOOL bool;
#else
                PRUint8 bool;
#endif
                length = sizeof(bool);
                rv = _PR_MD_GETSOCKOPT(
                         fd, level, name, (char*)&bool, &length);
                if (PR_SUCCESS == rv) {
                    data->value.mcast_loopback = (0 == bool) ? PR_FALSE : PR_TRUE;
                }
                break;
            }
            case PR_SockOpt_RecvBufferSize:
            case PR_SockOpt_SendBufferSize:
            case PR_SockOpt_MaxSegment:
            {
                PRIntn value;
                length = sizeof(value);
                rv = _PR_MD_GETSOCKOPT(
                         fd, level, name, (char*)&value, &length);
                if (PR_SUCCESS == rv) {
                    data->value.recv_buffer_size = value;
                }
                break;
            }
            case PR_SockOpt_IpTimeToLive:
            case PR_SockOpt_IpTypeOfService:
            {
                /* These options should really be an int (or PRIntn). */
                length = sizeof(PRUintn);
                rv = _PR_MD_GETSOCKOPT(
                         fd, level, name, (char*)&data->value.ip_ttl, &length);
                break;
            }
            case PR_SockOpt_McastTimeToLive:
            {
#ifdef WIN32 /* Winsock */
                int ttl;
#else
                PRUint8 ttl;
#endif
                length = sizeof(ttl);
                rv = _PR_MD_GETSOCKOPT(
                         fd, level, name, (char*)&ttl, &length);
                if (PR_SUCCESS == rv) {
                    data->value.mcast_ttl = ttl;
                }
                break;
            }
#ifdef IP_ADD_MEMBERSHIP
            case PR_SockOpt_AddMember:
            case PR_SockOpt_DropMember:
            {
                struct ip_mreq mreq;
                length = sizeof(mreq);
                rv = _PR_MD_GETSOCKOPT(
                         fd, level, name, (char*)&mreq, &length);
                if (PR_SUCCESS == rv)
                {
                    data->value.add_member.mcaddr.inet.ip =
                        mreq.imr_multiaddr.s_addr;
                    data->value.add_member.ifaddr.inet.ip =
                        mreq.imr_interface.s_addr;
                }
                break;
            }
#endif /* IP_ADD_MEMBERSHIP */
            case PR_SockOpt_McastInterface:
            {
                /* This option is a struct in_addr. */
                length = sizeof(data->value.mcast_if.inet.ip);
                rv = _PR_MD_GETSOCKOPT(
                         fd, level, name,
                         (char*)&data->value.mcast_if.inet.ip, &length);
                break;
            }
            case PR_SockOpt_DontFrag:
            {
#if !defined(WIN32) && !defined(DARWIN) && !defined(LINUX) && !defined(ANDROID)
                PR_SetError(PR_OPERATION_NOT_SUPPORTED_ERROR, 0);
                rv = PR_FAILURE;
#else
#ifdef WIN32 /* Winsock */
                DWORD value;
#else
                PRIntn value;
#endif
                length = sizeof(value);
                rv = _PR_MD_GETSOCKOPT(
                         fd, level, name, (char*)&value, &length);
#if defined(WIN32) || defined(DARWIN)
                data->value.dont_fragment = value;
#else
                data->value.dont_fragment = (value == IP_PMTUDISC_DO) ? 1 : 0;
#endif
#endif /* !(!defined(WIN32) && !defined(DARWIN) && !defined(LINUX) && !defined(ANDROID)) */
                break;
            }
            default:
                PR_NOT_REACHED("Unknown socket option");
                break;
        }
    }
    return rv;
}  /* _PR_SocketGetSocketOption */

PRStatus PR_CALLBACK _PR_SocketSetSocketOption(PRFileDesc *fd, const PRSocketOptionData *data)
{
    PRStatus rv;
    PRInt32 level, name;

    /*
     * PR_SockOpt_Nonblocking is a special case that does not
     * translate to a setsockopt call.
     */
    if (PR_SockOpt_Nonblocking == data->option)
    {
#ifdef WINNT
        PR_ASSERT((fd->secret->md.io_model_committed == PR_FALSE)
                  || (fd->secret->nonblocking == data->value.non_blocking));
        if (fd->secret->md.io_model_committed
            && (fd->secret->nonblocking != data->value.non_blocking))
        {
            /*
             * On NT, once we have associated a socket with the io
             * completion port, we can't disassociate it.  So we
             * can't change the nonblocking option of the socket
             * afterwards.
             */
            PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
            return PR_FAILURE;
        }
#endif
        fd->secret->nonblocking = data->value.non_blocking;
        return PR_SUCCESS;
    }

    rv = _PR_MapOptionName(data->option, &level, &name);
    if (PR_SUCCESS == rv)
    {
        switch (data->option)
        {
            case PR_SockOpt_Linger:
            {
                struct linger linger;
                linger.l_onoff = data->value.linger.polarity;
                linger.l_linger = PR_IntervalToSeconds(data->value.linger.linger);
                rv = _PR_MD_SETSOCKOPT(
                         fd, level, name, (char*)&linger, sizeof(linger));
                break;
            }
            case PR_SockOpt_Reuseaddr:
            case PR_SockOpt_Keepalive:
            case PR_SockOpt_NoDelay:
            case PR_SockOpt_Broadcast:
            case PR_SockOpt_Reuseport:
            {
#ifdef WIN32 /* Winsock */
                BOOL value;
#else
                PRIntn value;
#endif
                value = (data->value.reuse_addr) ? 1 : 0;
                rv = _PR_MD_SETSOCKOPT(
                         fd, level, name, (char*)&value, sizeof(value));
                break;
            }
            case PR_SockOpt_McastLoopback:
            {
#ifdef WIN32 /* Winsock */
                BOOL bool;
#else
                PRUint8 bool;
#endif
                bool = data->value.mcast_loopback ? 1 : 0;
                rv = _PR_MD_SETSOCKOPT(
                         fd, level, name, (char*)&bool, sizeof(bool));
                break;
            }
            case PR_SockOpt_RecvBufferSize:
            case PR_SockOpt_SendBufferSize:
            case PR_SockOpt_MaxSegment:
            {
                PRIntn value = data->value.recv_buffer_size;
                rv = _PR_MD_SETSOCKOPT(
                         fd, level, name, (char*)&value, sizeof(value));
                break;
            }
            case PR_SockOpt_IpTimeToLive:
            case PR_SockOpt_IpTypeOfService:
            {
                /* These options should really be an int (or PRIntn). */
                rv = _PR_MD_SETSOCKOPT(
                         fd, level, name, (char*)&data->value.ip_ttl, sizeof(PRUintn));
                break;
            }
            case PR_SockOpt_McastTimeToLive:
            {
#ifdef WIN32 /* Winsock */
                int ttl;
#else
                PRUint8 ttl;
#endif
                ttl = data->value.mcast_ttl;
                rv = _PR_MD_SETSOCKOPT(
                         fd, level, name, (char*)&ttl, sizeof(ttl));
                break;
            }
#ifdef IP_ADD_MEMBERSHIP
            case PR_SockOpt_AddMember:
            case PR_SockOpt_DropMember:
            {
                struct ip_mreq mreq;
                mreq.imr_multiaddr.s_addr =
                    data->value.add_member.mcaddr.inet.ip;
                mreq.imr_interface.s_addr =
                    data->value.add_member.ifaddr.inet.ip;
                rv = _PR_MD_SETSOCKOPT(
                         fd, level, name, (char*)&mreq, sizeof(mreq));
                break;
            }
#endif /* IP_ADD_MEMBERSHIP */
            case PR_SockOpt_McastInterface:
            {
                /* This option is a struct in_addr. */
                rv = _PR_MD_SETSOCKOPT(
                         fd, level, name, (char*)&data->value.mcast_if.inet.ip,
                         sizeof(data->value.mcast_if.inet.ip));
                break;
            }
            case PR_SockOpt_DontFrag:
            {
#if !defined(WIN32) && !defined(DARWIN) && !defined(LINUX) && !defined(ANDROID)
                PR_SetError(PR_OPERATION_NOT_SUPPORTED_ERROR, 0);
                rv = PR_FAILURE;
#else
#if defined(WIN32) /* Winsock */
                DWORD value;
                value = (data->value.dont_fragment) ? 1 : 0;
#elif defined(LINUX) || defined(ANDROID)
                PRIntn value;
                value = (data->value.dont_fragment) ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT;
#elif defined(DARWIN)
                PRIntn value;
                value = data->value.dont_fragment;
#endif
                rv = _PR_MD_SETSOCKOPT(
                         fd, level, name, (char*)&value, sizeof(value));
#endif /* !(!defined(WIN32) && !defined(DARWIN) && !defined(LINUX) && !defined(ANDROID)) */
                break;
            }
            default:
                PR_NOT_REACHED("Unknown socket option");
                break;
        }
    }
    return rv;
}  /* _PR_SocketSetSocketOption */

#endif /* ! _PR_PTHREADS */

/*
 *********************************************************************
 *********************************************************************
 **
 ** Make sure that the following is at the end of this file,
 ** because we will be playing with macro redefines.
 **
 *********************************************************************
 *********************************************************************
 */

/*
 * Not every platform has all the socket options we want to
 * support.  Some older operating systems such as SunOS 4.1.3
 * don't have the IP multicast socket options.  Win32 doesn't
 * have TCP_MAXSEG.
 *
 * To deal with this problem, we define the missing socket
 * options as _PR_NO_SUCH_SOCKOPT.  _PR_MapOptionName() fails with
 * PR_OPERATION_NOT_SUPPORTED_ERROR if a socket option not
 * available on the platform is requested.
 */

/*
 * Sanity check.  SO_LINGER and TCP_NODELAY should be available
 * on all platforms.  Just to make sure we have included the
 * appropriate header files.  Then any undefined socket options
 * are really missing.
 */

#if !defined(SO_LINGER)
#error "SO_LINGER is not defined"
#endif

#if !defined(TCP_NODELAY)
#error "TCP_NODELAY is not defined"
#endif

/*
 * Make sure the value of _PR_NO_SUCH_SOCKOPT is not
 * a valid socket option.
 */
#define _PR_NO_SUCH_SOCKOPT -1

#ifndef SO_KEEPALIVE
#define SO_KEEPALIVE        _PR_NO_SUCH_SOCKOPT
#endif

#ifndef SO_SNDBUF
#define SO_SNDBUF           _PR_NO_SUCH_SOCKOPT
#endif

#ifndef SO_RCVBUF
#define SO_RCVBUF           _PR_NO_SUCH_SOCKOPT
#endif

#ifndef IP_MULTICAST_IF                 /* set/get IP multicast interface   */
#define IP_MULTICAST_IF     _PR_NO_SUCH_SOCKOPT
#endif

#ifndef IP_MULTICAST_TTL                /* set/get IP multicast timetolive  */
#define IP_MULTICAST_TTL    _PR_NO_SUCH_SOCKOPT
#endif

#ifndef IP_MULTICAST_LOOP               /* set/get IP multicast loopback    */
#define IP_MULTICAST_LOOP   _PR_NO_SUCH_SOCKOPT
#endif

#ifndef IP_ADD_MEMBERSHIP               /* add  an IP group membership      */
#define IP_ADD_MEMBERSHIP   _PR_NO_SUCH_SOCKOPT
#endif

#ifndef IP_DROP_MEMBERSHIP              /* drop an IP group membership      */
#define IP_DROP_MEMBERSHIP  _PR_NO_SUCH_SOCKOPT
#endif

#ifndef IP_TTL                          /* set/get IP Time To Live          */
#define IP_TTL              _PR_NO_SUCH_SOCKOPT
#endif

#ifndef IP_TOS                          /* set/get IP Type Of Service       */
#define IP_TOS              _PR_NO_SUCH_SOCKOPT
#endif

/* set/get IP do not fragment */
#if defined(WIN32)
#ifndef IP_DONTFRAGMENT
#define IP_DONTFRAGMENT     _PR_NO_SUCH_SOCKOPT
#endif

#elif defined(LINUX) || defined(ANDROID)
#ifndef IP_MTU_DISCOVER
#define IP_MTU_DISCOVER     _PR_NO_SUCH_SOCKOPT
#endif

#elif defined(DARWIN)
#ifndef IP_DONTFRAG
#define IP_DONTFRAG         _PR_NO_SUCH_SOCKOPT
#endif
#endif

#ifndef TCP_NODELAY                     /* don't delay to coalesce data     */
#define TCP_NODELAY         _PR_NO_SUCH_SOCKOPT
#endif

#ifndef TCP_MAXSEG                      /* maxumum segment size for tcp     */
#define TCP_MAXSEG          _PR_NO_SUCH_SOCKOPT
#endif

#ifndef SO_BROADCAST                    /* enable broadcast on UDP sockets  */
#define SO_BROADCAST        _PR_NO_SUCH_SOCKOPT
#endif

#ifndef SO_REUSEPORT                    /* allow local address & port reuse */
#define SO_REUSEPORT        _PR_NO_SUCH_SOCKOPT
#endif

PRStatus _PR_MapOptionName(
    PRSockOption optname, PRInt32 *level, PRInt32 *name)
{
    static PRInt32 socketOptions[PR_SockOpt_Last] =
    {
        0, SO_LINGER, SO_REUSEADDR, SO_KEEPALIVE, SO_RCVBUF, SO_SNDBUF,
        IP_TTL, IP_TOS, IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP,
        IP_MULTICAST_IF, IP_MULTICAST_TTL, IP_MULTICAST_LOOP,
        TCP_NODELAY, TCP_MAXSEG, SO_BROADCAST, SO_REUSEPORT,
#if defined(WIN32)
        IP_DONTFRAGMENT,
#elif defined(LINUX) || defined(ANDROID)
        IP_MTU_DISCOVER,
#elif defined(DARWIN)
        IP_DONTFRAG,
#else
        _PR_NO_SUCH_SOCKOPT,
#endif
    };
    static PRInt32 socketLevels[PR_SockOpt_Last] =
    {
        0, SOL_SOCKET, SOL_SOCKET, SOL_SOCKET, SOL_SOCKET, SOL_SOCKET,
        IPPROTO_IP, IPPROTO_IP, IPPROTO_IP, IPPROTO_IP,
        IPPROTO_IP, IPPROTO_IP, IPPROTO_IP,
        IPPROTO_TCP, IPPROTO_TCP, SOL_SOCKET, SOL_SOCKET, IPPROTO_IP
    };

    if ((optname < PR_SockOpt_Linger)
        || (optname >= PR_SockOpt_Last))
    {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return PR_FAILURE;
    }

    if (socketOptions[optname] == _PR_NO_SUCH_SOCKOPT)
    {
        PR_SetError(PR_OPERATION_NOT_SUPPORTED_ERROR, 0);
        return PR_FAILURE;
    }
    *name = socketOptions[optname];
    *level = socketLevels[optname];
    return PR_SUCCESS;
}  /* _PR_MapOptionName */
