/*
 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.net;

import java.io.FileDescriptor;
import static ikvm.internal.JNI.*;
import static ikvm.internal.Winsock.*;
import static java.net.net_util_md.*;
import static java.net.InetAddress.IPv4;
import static java.net.InetAddress.IPv6;

final class TwoStacksPlainSocketImpl_c
{
static final int JVM_IO_ERR = -1;
static final int JVM_IO_INTR = -2;

static final int java_net_SocketOptions_SO_TIMEOUT = SocketOptions.SO_TIMEOUT;
static final int java_net_SocketOptions_SO_BINDADDR = SocketOptions.SO_BINDADDR;
static final int java_net_SocketOptions_SO_SNDBUF = SocketOptions.SO_SNDBUF;
static final int java_net_SocketOptions_SO_RCVBUF = SocketOptions.SO_RCVBUF;
static final int java_net_SocketOptions_IP_TOS = SocketOptions.IP_TOS;
static final int java_net_SocketOptions_SO_REUSEADDR = SocketOptions.SO_REUSEADDR;
static final int java_net_SocketOptions_TCP_NODELAY = SocketOptions.TCP_NODELAY;
static final int java_net_SocketOptions_SO_OOBINLINE = SocketOptions.SO_OOBINLINE;
static final int java_net_SocketOptions_SO_KEEPALIVE = SocketOptions.SO_KEEPALIVE;
static final int java_net_SocketOptions_SO_LINGER = SocketOptions.SO_LINGER;

/*
#include <windows.h>
#include <winsock2.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys/types.h>

#include "java_net_SocketOptions.h"
#include "java_net_TwoStacksPlainSocketImpl.h"
#include "java_net_SocketImpl.h"
#include "java_net_InetAddress.h"
#include "java_io_FileDescriptor.h"
#include "java_lang_Integer.h"

#include "jvm.h"
#include "net_util.h"
#include "jni_util.h"
*/

/************************************************************************
 * TwoStacksPlainSocketImpl
 */

/*
static jfieldID IO_fd_fdID;

jfieldID psi_fdID;
jfieldID psi_fd1ID;
jfieldID psi_addressID;
jfieldID psi_portID;
jfieldID psi_localportID;
jfieldID psi_timeoutID;
jfieldID psi_trafficClassID;
jfieldID psi_serverSocketID;
jfieldID psi_lastfdID;
*/

/*
 * the level of the TCP protocol for setsockopt and getsockopt
 * we only want to look this up once, from the static initializer
 * of TwoStacksPlainSocketImpl
 */
static int tcp_level = -1;

static cli.System.Net.Sockets.Socket getFD(JNIEnv env, TwoStacksPlainSocketImpl _this) {
    FileDescriptor fdObj = _this.fd;

    if (fdObj == NULL) {
        return null;
    }
    return fdObj.getSocket();
}

static cli.System.Net.Sockets.Socket getFD1(JNIEnv env, TwoStacksPlainSocketImpl _this) {
    FileDescriptor fdObj = _this.fd1;

    if (fdObj == NULL) {
        return null;
    }
    return fdObj.getSocket();
}


/*
 * The initProto function is called whenever TwoStacksPlainSocketImpl is
 * loaded, to cache fieldIds for efficiency. This is called everytime
 * the Java class is loaded.
 *
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    initProto

 * Signature: ()V
 */
/*
JNIEXPORT void JNICALL
Java_java_net_TwoStacksPlainSocketImpl_initProto(JNIEnv *env, jclass cls) {

    struct protoent *proto = getprotobyname("TCP");
    tcp_level = (proto == 0 ? IPPROTO_TCP: proto->p_proto);

    psi_fdID = (*env)->GetFieldID(env, cls , "fd", "Ljava/io/FileDescriptor;");
    CHECK_NULL(psi_fdID);
    psi_fd1ID =(*env)->GetFieldID(env, cls , "fd1", "Ljava/io/FileDescriptor;");
    CHECK_NULL(psi_fd1ID);
    psi_addressID = (*env)->GetFieldID(env, cls, "address",
                                          "Ljava/net/InetAddress;");
    CHECK_NULL(psi_addressID);
    psi_portID = (*env)->GetFieldID(env, cls, "port", "I");
    CHECK_NULL(psi_portID);
    psi_lastfdID = (*env)->GetFieldID(env, cls, "lastfd", "I");
    CHECK_NULL(psi_portID);
    psi_localportID = (*env)->GetFieldID(env, cls, "localport", "I");
    CHECK_NULL(psi_localportID);
    psi_timeoutID = (*env)->GetFieldID(env, cls, "timeout", "I");
    CHECK_NULL(psi_timeoutID);
    psi_trafficClassID = (*env)->GetFieldID(env, cls, "trafficClass", "I");
    CHECK_NULL(psi_trafficClassID);
    psi_serverSocketID = (*env)->GetFieldID(env, cls, "serverSocket",
                                            "Ljava/net/ServerSocket;");
    CHECK_NULL(psi_serverSocketID);
    IO_fd_fdID = NET_GetFileDescriptorID(env);
    CHECK_NULL(IO_fd_fdID);
}
*/

/*
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    socketCreate
 * Signature: (Z)V
 */
static void socketCreate(JNIEnv env, TwoStacksPlainSocketImpl _this, boolean stream) {
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;

    if (IS_NULL(fdObj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "null fd object");
        return;
    }
    fd = socket(AF_INET, (stream ? SOCK_STREAM: SOCK_DGRAM), 0);
    if (fd == INVALID_SOCKET) {
        NET_ThrowCurrent(env, "create");
        return;
    } else {
        /* Set socket attribute so it is not passed to any child process */
        //SetHandleInformation((HANDLE)(UINT_PTR)fd, HANDLE_FLAG_INHERIT, FALSE);
        fdObj.setSocket(fd);
    }
    if (ipv6_available()) {

        if (IS_NULL(fd1Obj)) {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                            "null fd1 object");
            fdObj.setSocket(null);
            NET_SocketClose(fd);
            return;
        }
        fd1 = socket(AF_INET6, (stream ? SOCK_STREAM: SOCK_DGRAM), 0);
        if (fd1 == INVALID_SOCKET) {
            NET_ThrowCurrent(env, "create");
            fdObj.setSocket(null);
            NET_SocketClose(fd);
            return;
        } else {
            fd1Obj.setSocket(fd1);
        }
    } else {
        _this.fd1 = null;
    }
}

/*
 * inetAddress is the address object passed to the socket connect
 * call.
 *
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    socketConnect
 * Signature: (Ljava/net/InetAddress;I)V
 */
static void socketConnect(JNIEnv env, TwoStacksPlainSocketImpl _this, InetAddress iaObj, int port, int timeout)
{
    int localport = _this.localport;

    /* family and localport are int fields of iaObj */
    int family;
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;
    boolean ipv6_supported = ipv6_available();

    /* fd initially points to the IPv4 socket and fd1 to the IPv6 socket
     * If we want to connect to IPv6 then we swap the two sockets/objects
     * This way, fd is always the connected socket, and fd1 always gets closed.
     */
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;

    SOCKETADDRESS him;
    him = new SOCKETADDRESS();

    /* The result of the connection */
    int connect_res;

    if (!IS_NULL(fdObj)) {
        fd = fdObj.getSocket();
    }

    if (ipv6_supported && !IS_NULL(fd1Obj)) {
        fd1 = fd1Obj.getSocket();
    }

    if (IS_NULL(iaObj)) {
        JNU_ThrowNullPointerException(env, "inet address argument is null.");
        return;
    }

    if (NET_InetAddressToSockaddr(env, iaObj, port, him, JNI_FALSE) != 0) {
      return;
    }

    family = him.him.sa_family;
    if (family == AF_INET6) {
        if (!ipv6_supported) {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                            "Protocol family not supported");
            return;
        } else {
            if (fd1 == null) {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                                "Destination unreachable");
                return;
            }
            /* close the v4 socket, and set fd to be the v6 socket */
            _this.fd = fd1Obj;
            _this.fd1 = null;
            NET_SocketClose(fd);
            fd = fd1; fdObj = fd1Obj;
        }
    } else {
        if (fd1 != null) {
            fd1Obj.setSocket(null);
            NET_SocketClose(fd1);
        }
        if (fd == null) {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                            "Destination unreachable");
            return;
        }
    }
    _this.fd1 = null;

    if (timeout <= 0) {
        connect_res = connect(fd, him);
        if (connect_res == SOCKET_ERROR) {
            connect_res = WSAGetLastError();
        }
    } else {
        int optval;

        /* make socket non-blocking */
        optval = 1;
        ioctlsocket( fd, FIONBIO, optval );

        /* initiate the connect */
        connect_res = connect(fd, him);
        if (connect_res == SOCKET_ERROR) {
            if (WSAGetLastError() != WSAEWOULDBLOCK) {
                connect_res = WSAGetLastError();
            } else {
                fd_set wr, ex;
                wr = new fd_set(); ex = new fd_set();
                timeval t = new timeval();

                FD_ZERO(wr);
                FD_ZERO(ex);
                FD_SET(fd, wr);
                FD_SET(fd, ex);
                t.tv_sec = timeout / 1000;
                t.tv_usec = (timeout % 1000) * 1000;

                /*
                 * Wait for timout, connection established or
                 * connection failed.
                 */
                connect_res = select(null, wr, ex, t);

                /*
                 * Timeout before connection is established/failed so
                 * we throw exception and shutdown input/output to prevent
                 * socket from being used.
                 * The socket should be closed immediately by the caller.
                 */
                if (connect_res == 0) {
                    JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketTimeoutException",
                                    "connect timed out");
                    shutdown( fd, SD_BOTH );

                     /* make socket blocking again - just in case */
                    optval = 0;
                    ioctlsocket( fd, FIONBIO, optval );
                    return;
                }

                /*
                 * We must now determine if the connection has been established
                 * or if it has failed. The logic here is designed to work around
                 * bug on Windows NT whereby using getsockopt to obtain the
                 * last error (SO_ERROR) indicates there is no error. The workaround
                 * on NT is to allow winsock to be scheduled and this is done by
                 * yielding and retrying. As yielding is problematic in heavy
                 * load conditions we attempt up to 3 times to get the error reason.
                 */
                if (!FD_ISSET(fd, ex)) {
                    connect_res = 0;
                } else {
                    int retry;
                    for (retry=0; retry<3; retry++) {
                        int[] tmp = { 0 };
                        NET_GetSockOpt(fd, SOL_SOCKET, SO_ERROR,
                                       tmp);
                        connect_res = tmp[0];
                        if (connect_res != 0) {
                            break;
                        }
                        Sleep(0);
                    }

                    if (connect_res == 0) {
                        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                                        "Unable to establish connection");
                        return;
                    }
                }
            }
        }

        /* make socket blocking again */
        optval = 0;
        ioctlsocket(fd, FIONBIO, optval);
    }

    if (connect_res != 0) {
        if (connect_res == WSAEADDRNOTAVAIL) {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"ConnectException",
                "connect: Address is invalid on local machine, or port is not valid on remote machine");
        } else {
            NET_ThrowNew(env, connect_res, "connect");
        }
        return;
    }

    fdObj.setSocket(fd);

    /* set the remote peer address and port */
    _this.address = iaObj;
    _this.port = port;

    /*
     * we need to initialize the local port field if bind was called
     * previously to the connect (by the client) then localport field
     * will already be initialized
     */
    if (localport == 0) {
        /* Now that we're a connected socket, let's extract the port number
         * that the system chose for us and store it in the Socket object.
         */
        if (getsockname(fd, him) == -1) {

            if (WSAGetLastError() == WSAENOTSOCK) {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Socket closed");
            } else {
                NET_ThrowCurrent(env, "getsockname failed");
            }
            return;
        }
        port = ntohs (GET_PORT(him));
        _this.localport = port;
    }
}

/*
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    socketBind
 * Signature: (Ljava/net/InetAddress;I)V
 */
static void socketBind(JNIEnv env, TwoStacksPlainSocketImpl _this,
                                         InetAddress iaObj, int localport,
                                         boolean exclBind) {
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;
    boolean ipv6_supported = ipv6_available();

    /* family is an int field of iaObj */
    int family;
    int rv;

    SOCKETADDRESS him;
    him = new SOCKETADDRESS();

    family = getInetAddress_family(env, iaObj);

    if (family == IPv6 && !ipv6_supported) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Protocol family not supported");
        return;
    }

    if (IS_NULL(fdObj) || (ipv6_supported && IS_NULL(fd1Obj))) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Socket closed");
        return;
    } else {
        fd = fdObj.getSocket();
        if (ipv6_supported) {
            fd1 = fd1Obj.getSocket();
        }
    }
    if (IS_NULL(iaObj)) {
        JNU_ThrowNullPointerException(env, "inet address argument");
        return;
    }

    if (NET_InetAddressToSockaddr(env, iaObj, localport,
                          him, JNI_FALSE) != 0) {
      return;
    }
    if (ipv6_supported) {
        ipv6bind v6bind = new ipv6bind();
        v6bind.addr = him;
        v6bind.ipv4_fd = fd;
        v6bind.ipv6_fd = fd1;
        rv = NET_BindV6(v6bind, exclBind);
        if (rv != -1) {
            /* check if the fds have changed */
            if (v6bind.ipv4_fd != fd) {
                fd = v6bind.ipv4_fd;
                if (fd == null) {
                    /* socket is closed. */
                    _this.fd = null;
                } else {
                    /* socket was re-created */
                    fdObj.setSocket(fd);
                }
            }
            if (v6bind.ipv6_fd != fd1) {
                fd1 = v6bind.ipv6_fd;
                if (fd1 == null) {
                    /* socket is closed. */
                    _this.fd1 = null;
                } else {
                    /* socket was re-created */
                    fd1Obj.setSocket(fd1);
                }
            }
        }
    } else {
        rv = NET_WinBind(fd, him, exclBind);
    }

    if (rv == -1) {
        NET_ThrowCurrent(env, "JVM_Bind");
        return;
    }

    /* set the address */
    _this.address = iaObj;

    /* intialize the local port */
    if (localport == 0) {
        /* Now that we're a bound socket, let's extract the port number
         * that the system chose for us and store it in the Socket object.
         */
        int port;
        fd = him.him.sa_family == AF_INET? fd: fd1;

        if (getsockname(fd, him) == -1) {
            NET_ThrowCurrent(env, "getsockname in plain socketBind");
            return;
        }
        port = ntohs (GET_PORT (him));

        _this.localport = port;
    } else {
        _this.localport = localport;
    }
}

/*
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    socketListen
 * Signature: (I)V
 */
static void socketListen (JNIEnv env, TwoStacksPlainSocketImpl _this, int count)
{
    /* this FileDescriptor fd field */
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    InetAddress address;
    /* fdObj's int fd field */
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;
    SOCKETADDRESS addr = new SOCKETADDRESS();

    if (IS_NULL(fdObj) && IS_NULL(fd1Obj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "socket closed");
        return;
    }

    if (!IS_NULL(fdObj)) {
        fd = fdObj.getSocket();
    }
    /* Listen on V4 if address type is v4 or if v6 and address is ::0.
     * Listen on V6 if address type is v6 or if v4 and address is 0.0.0.0.
     * In cases, where we listen on one space only, we close the other socket.
     */
    address = _this.address;
    if (IS_NULL(address)) {
        JNU_ThrowNullPointerException(env, "socket address");
        return;
    }
    if (NET_InetAddressToSockaddr(env, address, 0, addr,
                                  JNI_FALSE) != 0) {
      return;
    }

    if (addr.him.sa_family == AF_INET || IN6ADDR_ISANY(addr.him6)) {
        /* listen on v4 */
        if (listen(fd, count) == -1) {
            NET_ThrowCurrent(env, "listen failed");
        }
    } else {
        NET_SocketClose (fd);
        _this.fd = null;
    }
    if (ipv6_available() && !IS_NULL(fd1Obj)) {
        fd1 = fd1Obj.getSocket();
        if (addr.him.sa_family == AF_INET6 || addr.him4.sin_addr.s_addr == INADDR_ANY) {
            /* listen on v6 */
            if (listen(fd1, count) == -1) {
                NET_ThrowCurrent(env, "listen failed");
            }
        } else {
            NET_SocketClose (fd1);
            _this.fd1 = null;
        }
    }
}

/*
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    socketAccept
 * Signature: (Ljava/net/SocketImpl;)V
 */
static void socketAccept(JNIEnv env, TwoStacksPlainSocketImpl _this, SocketImpl socket)
{
    /* fields on this */
    int port;
    int timeout = _this.timeout;
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;

    /* the FileDescriptor field on socket */
    FileDescriptor socketFdObj;

    /* the InetAddress field on socket */
    InetAddress socketAddressObj;

    /* the fd int field on fdObj */
    cli.System.Net.Sockets.Socket fd=null, fd1=null;

    SOCKETADDRESS him;
    him = new SOCKETADDRESS();

    if (IS_NULL(fdObj) && IS_NULL(fd1Obj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Socket closed");
        return;
    }
    if (!IS_NULL(fdObj)) {
        fd = fdObj.getSocket();
    }
    if (!IS_NULL(fd1Obj)) {
        fd1 = fd1Obj.getSocket();
    }
    if (IS_NULL(socket)) {
        JNU_ThrowNullPointerException(env, "socket is null");
        return;
    } else {
        socketFdObj = socket.fd;
        socketAddressObj = socket.address;
    }
    if ((IS_NULL(socketAddressObj)) || (IS_NULL(socketFdObj))) {
        JNU_ThrowNullPointerException(env, "socket address or fd obj");
        return;
    }
    if (fd != null && fd1 != null) {
        fd_set rfds = new fd_set();
        timeval t = new timeval();
        cli.System.Net.Sockets.Socket lastfd, fd2;
        FD_ZERO(rfds);
        FD_SET(fd,rfds);
        FD_SET(fd1,rfds);
        if (timeout != 0) {
            t.tv_sec = timeout/1000;
            t.tv_usec = (timeout%1000)*1000;
        } else {
            t = null;
        }
        int res = select (rfds, null, null, t);
        if (res == 0) {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketTimeoutException",
                            "Accept timed out");
            return;
        } else if (res == 1) {
            fd2 = FD_ISSET(fd, rfds)? fd: fd1;
        } else if (res == 2) {
            /* avoid starvation */
            lastfd = _this.lastfd;
            if (lastfd != null) {
                fd2 = lastfd==fd? fd1: fd;
            } else {
                fd2 = fd;
            }
            _this.lastfd = fd2;
        } else {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                            "select failed");
            return;
        }
        fd = fd2;
    } else {
        int ret;
        if (fd1 != null) {
            fd = fd1;
        }
        if (timeout != 0) {
            ret = NET_Timeout(fd, timeout);
            if (ret == 0) {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketTimeoutException",
                                "Accept timed out");
                return;
            } else if (ret == -1) {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "socket closed");
            /* REMIND: SOCKET CLOSED PROBLEM */
    /*        NET_ThrowCurrent(env, "Accept failed"); */
                return;
            } else if (ret == -2) {
                JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException",
                                "operation interrupted");
                return;
            }
        }
    }
    fd = accept(fd, him);
    if (fd == null) {
        /* REMIND: SOCKET CLOSED PROBLEM */
        if (false) {
            JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException",
                            "operation interrupted");
        } else {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                            "socket closed");
        }
        return;
    }
    socketFdObj.setSocket(fd);

    if (him.him.sa_family == AF_INET) {

        /*
         * fill up the remote peer port and address in the new socket structure
         */
        socketAddressObj = new Inet4Address(null, ntohl(him.him4.sin_addr.s_addr));
        socket.address = socketAddressObj;
    } else {
        /* AF_INET6 -> Inet6Address */

        // [IKVM] We need to convert scope_id 0 to -1 here, because for sin6_scope_id 0 means unspecified, whereas Java uses -1
        int scopeId = him.him6.sin6_scope_id;
        socketAddressObj = new Inet6Address(null, him.him6.sin6_addr, scopeId == 0 ? -1 : scopeId);
    }
    /* fields common to AF_INET and AF_INET6 */

    port = ntohs (GET_PORT (him));
    socket.port = port;
    port = _this.localport;
    socket.localport = port;
    socket.address = socketAddressObj;
}

/*
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    socketAvailable
 * Signature: ()I
 */
static int socketAvailable(JNIEnv env, TwoStacksPlainSocketImpl _this) {

    int[] available = { -1 };
    int res;
    FileDescriptor fdObj = _this.fd;
    cli.System.Net.Sockets.Socket fd;

    if (IS_NULL(fdObj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Socket closed");
        return -1;
    } else {
        fd = fdObj.getSocket();
    }
    res = ioctlsocket(fd, FIONREAD, available);
    /* if result isn't 0, it means an error */
    if (res != 0) {
        NET_ThrowNew(env, res, "socket available");
    }
    return available[0];
}

/*
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    socketClose
 * Signature: ()V
 */
static void socketClose0(JNIEnv env, TwoStacksPlainSocketImpl _this, boolean useDeferredClose) {

    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;

    if (IS_NULL(fdObj) && IS_NULL(fd1Obj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "socket already closed");
        return;
    }
    if (!IS_NULL(fdObj)) {
        fd = fdObj.getSocket();
    }
    if (!IS_NULL(fd1Obj)) {
        fd1 = fd1Obj.getSocket();
    }
    if (fd != null) {
        fdObj.setSocket(null);
        NET_SocketClose(fd);
    }
    if (fd1 != null) {
        fd1Obj.setSocket(null);
        NET_SocketClose(fd1);
    }
}

/*
 * Socket options for plainsocketImpl
 *
 *
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    socketNativeSetOption
 * Signature: (IZLjava/lang/Object;)V
 */
static void socketNativeSetOption(JNIEnv env, TwoStacksPlainSocketImpl _this, int cmd, boolean on, Object value) {
    cli.System.Net.Sockets.Socket fd, fd1;
    int[] level = new int[1];
    int[] optname = new int[1];
    Object optval;

    /*
     * Get SOCKET and check that it hasn't been closed
     */
    fd = getFD(env, _this);
    fd1 = getFD1(env, _this);
    if (fd == null && fd1 == null) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Socket closed");
        return;
    }

    /*
     * SO_TIMEOUT is the socket option used to specify the timeout
     * for ServerSocket.accept and Socket.getInputStream().read.
     * It does not typically map to a native level socket option.
     * For Windows we special-case this and use the SOL_SOCKET/SO_RCVTIMEO
     * socket option to specify a receive timeout on the socket. This
     * receive timeout is applicable to Socket only and the socket
     * option should not be set on ServerSocket.
     */
    if (cmd == java_net_SocketOptions_SO_TIMEOUT) {

        /*
         * Don't enable the socket option on ServerSocket as it's
         * meaningless (we don't receive on a ServerSocket).
         */
        Object ssObj = _this.serverSocket;
        if (ssObj != NULL) {
            return;
        }

        /*
         * SO_RCVTIMEO is only supported on Microsoft's implementation
         * of Windows Sockets so if WSAENOPROTOOPT returned then
         * reset flag and timeout will be implemented using
         * select() -- see SocketInputStream.socketRead.
         */
        if (isRcvTimeoutSupported) {
            int timeout = ((Integer)value).intValue();

            /*
             * Disable SO_RCVTIMEO if timeout is <= 5 second.
             */
            if (timeout <= 5000) {
                timeout = 0;
            }

            if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, timeout) < 0) {
                if (WSAGetLastError() == WSAENOPROTOOPT) {
                    isRcvTimeoutSupported = JNI_FALSE;
                } else {
                    NET_ThrowCurrent(env, "setsockopt SO_RCVTIMEO");
                }
            }
            if (fd1 != null) {
                if (setsockopt(fd1, SOL_SOCKET, SO_RCVTIMEO, timeout) < 0) {
                    NET_ThrowCurrent(env, "setsockopt SO_RCVTIMEO");
                }
            }
        }
        return;
    }

    /*
     * Map the Java level socket option to the platform specific
     * level
     */
    if (NET_MapSocketOption(cmd, level, optname) != 0) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Invalid option");
        return;
    }

    switch (cmd) {

        case java_net_SocketOptions_TCP_NODELAY :
        case java_net_SocketOptions_SO_OOBINLINE :
        case java_net_SocketOptions_SO_KEEPALIVE :
        case java_net_SocketOptions_SO_REUSEADDR :
            optval = on;
            break;

        case java_net_SocketOptions_SO_SNDBUF :
        case java_net_SocketOptions_SO_RCVBUF :
        case java_net_SocketOptions_IP_TOS :
            optval = ((Integer)value).intValue();
            break;

        case java_net_SocketOptions_SO_LINGER :
            {
                linger ling = new linger();
                if (on) {
                    ling.l_onoff = 1;
                    ling.l_linger = ((Integer)value).intValue();
                } else {
                    ling.l_onoff = 0;
                    ling.l_linger = 0;
                }
                optval = ling;
            }
            break;

        default: /* shouldn't get here */
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                "Option not supported by TwoStacksPlainSocketImpl");
            return;
    }

    if (fd != null) {
        if (NET_SetSockOpt(fd, level[0], optname[0], optval) < 0) {
            NET_ThrowCurrent(env, "setsockopt");
        }
    }

    if (fd1 != null) {
        if (NET_SetSockOpt(fd1, level[0], optname[0], optval) < 0) {
            NET_ThrowCurrent(env, "setsockopt");
        }
    }
}


/*
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    socketGetOption
 * Signature: (I)I
 */
static int socketGetOption(JNIEnv env, TwoStacksPlainSocketImpl _this, int opt, Object iaContainerObj) {

    cli.System.Net.Sockets.Socket fd, fd1;
    int[] level = new int[1];
    int[] optname = new int[1];
    Object optval;

    /*
     * Get SOCKET and check it hasn't been closed
     */
    fd = getFD(env, _this);
    fd1 = getFD1(env, _this);

    if (fd == null && fd1 == null) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Socket closed");
        return -1;
    }
    if (fd == null) {
        fd = fd1;
    }

    /* For IPv6, we assume both sockets have the same setting always */

    /*
     * SO_BINDADDR isn't a socket option
     */
    if (opt == java_net_SocketOptions_SO_BINDADDR) {
        SOCKETADDRESS him;
        him = new SOCKETADDRESS();
        int[] port = { 0 };
        InetAddress iaObj;

        if (fd == null) {
            /* must be an IPV6 only socket. Case where both sockets are != -1
             * is handled in java
             */
            fd = getFD1 (env, _this);
        }

        if (getsockname(fd, him) < 0) {
            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG+"SocketException",
                             "Error getting socket name");
            return -1;
        }
        iaObj = NET_SockaddrToInetAddress(him, port);
        ((InetAddressContainer)iaContainerObj).addr = iaObj;
        return 0; /* notice change from before */
    }

    /*
     * Map the Java level socket option to the platform specific
     * level and option name.
     */
    if (NET_MapSocketOption(opt, level, optname) != 0) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Invalid option");
        return -1;
    }

    /*
     * Args are int except for SO_LINGER
     */
    if (opt == java_net_SocketOptions_SO_LINGER) {
        optval = new linger();
    } else {
        optval = new int[1];
    }

    if (NET_GetSockOpt(fd, level[0], optname[0], optval) < 0) {
        NET_ThrowCurrent(env, "getsockopt");
        return -1;
    }

    switch (opt) {
        case java_net_SocketOptions_SO_LINGER:
            return (((linger)optval).l_onoff != 0 ? ((linger)optval).l_linger: -1);

        case java_net_SocketOptions_SO_SNDBUF:
        case java_net_SocketOptions_SO_RCVBUF:
        case java_net_SocketOptions_IP_TOS:
            return ((int[])optval)[0];

        case java_net_SocketOptions_TCP_NODELAY :
        case java_net_SocketOptions_SO_OOBINLINE :
        case java_net_SocketOptions_SO_KEEPALIVE :
        case java_net_SocketOptions_SO_REUSEADDR :
            return (((int[])optval)[0] == 0) ? -1 : 1;

        default: /* shouldn't get here */
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                "Option not supported by TwoStacksPlainSocketImpl");
            return -1;
    }
}

/*
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    socketShutdown
 * Signature: (I)V
 */
static void socketShutdown(JNIEnv env, TwoStacksPlainSocketImpl _this, int howto)
{

    FileDescriptor fdObj = _this.fd;
    cli.System.Net.Sockets.Socket fd;

    /*
     * WARNING: THIS NEEDS LOCKING. ALSO: SHOULD WE CHECK for fd being
     * -1 already?
     */
    if (IS_NULL(fdObj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "socket already closed");
        return;
    } else {
        fd = fdObj.getSocket();
    }
    shutdown(fd, howto);
}

/*
 * Class:     java_net_TwoStacksPlainSocketImpl
 * Method:    socketSendUrgentData
 * Signature: (B)V
 */
static void socketSendUrgentData(JNIEnv env, TwoStacksPlainSocketImpl _this, int data) {
    /* The fd field */
    FileDescriptor fdObj = _this.fd;
    int n;
    cli.System.Net.Sockets.Socket fd;

    if (IS_NULL(fdObj)) {
        JNU_ThrowByName(env, "java/net/SocketException", "Socket closed");
        return;
    } else {
        fd = fdObj.getSocket();
        /* Bug 4086704 - If the Socket associated with this file descriptor
         * was closed (sysCloseFD), the the file descriptor is set to -1.
         */
        if (fd == null) {
            JNU_ThrowByName(env, "java/net/SocketException", "Socket closed");
            return;
        }

    }
    n = send(fd, new byte[] { (byte)data }, 1, MSG_OOB);
    if (n == JVM_IO_ERR) {
        NET_ThrowCurrent(env, "send");
        return;
    }
    if (n == JVM_IO_INTR) {
        JNU_ThrowByName(env, "java/io/InterruptedIOException", null);
        return;
    }
}
}
