/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 * 
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 * 
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

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

#ifdef WINNT
#include <winsock.h>
#endif

#include "primpl.h"

#if defined(XP_UNIX) || defined(OS2)
#include <netinet/tcp.h>  /* TCP_NODELAY, TCP_MAXSEG */
#endif

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

#ifndef TCP_MAXSEG                      /* maxumum segment size for tcp     */
#define TCP_MAXSEG          _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
    };
    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
    };

    if ((optname < PR_SockOpt_Linger)
    && (optname > PR_SockOpt_MaxSegment))
    {
        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 */

#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:
            {
#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;
            }
            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;
            }
            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;
            }
            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:
            {
#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;
            }
            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;
            }
            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;
            }
            default:
                PR_NOT_REACHED("Unknown socket option");
                break;
        }  
    }
    return rv;
}  /* _PR_SocketSetSocketOption */

#endif /* ! _PR_PTHREADS */
