/*
 * @(#)PlainSocketImpl.c	1.29 99/04/22
 *
 * Copyright 1997-1999 by Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 * 
 * This software is the confidential and proprietary information
 * of Sun Microsystems, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Sun.
 */

#include "jvm_utils.h"
#include "java_net_SocketOptions.h"
#include "java_net_PlainSocketImpl.h"

/************************************************************************
 * PlainSocketImpl
 */

static jfieldID IO_fd_fdID;

jfieldID psi_fdID;
jfieldID psi_addressID;
jfieldID psi_portID;
jfieldID psi_localportID;
jfieldID psi_timeoutID;
jfieldID psi_flowLabel;
jfieldID psi_trafficClass;

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

/*
 * The initProto function is called whenever PlainSocketImpl is
 * loaded, to cache fieldIds for efficiency. This is called everytime
 * the Java class is loaded.
 *
 * Class:     java_net_PlainSocketImpl
 * Method:    initProto
 * Signature: ()V
 */
JNIEXPORT void JNICALL
    Java_java_net_PlainSocketImpl_initProto(JNIEnv *env, jclass cls) {
    
    struct protoent *proto = getprotobyname("TCP");
    tcp_level = (proto == NULL ? IPPROTO_TCP: proto->p_proto);
    
    psi_fdID = (*env)->GetFieldID(env, cls , "fd",
				  "Ljava/io/FileDescriptor;");
    psi_addressID = (*env)->GetFieldID(env, cls, "address",
				       "Ljava/net/InetAddress;");
    psi_portID = (*env)->GetFieldID(env, cls, "port", "I");
    psi_localportID = (*env)->GetFieldID(env, cls, "localport", "I");
    psi_timeoutID = (*env)->GetFieldID(env, cls, "timeout", "I");
    IO_fd_fdID = NET_GetFileDescriptorID(env);
    psi_flowLabel = (*env)->GetFieldID(env, cls, "flow_label", "I");
    psi_trafficClass = (*env)->GetFieldID(env, cls, "traffic_class", "I");
}

/* a global reference to the java.net.SocketException class. In
 * socketCreate, we ensure that this is initialized. This is to
 * prevent the problem where socketCreate runs out of file
 * descriptors, and is then unable to load the exception class.
 */
static jclass socketExceptionCls;

/*
 * Class:     java_net_PlainSocketImpl
 * Method:    socketCreate
 * Signature: (Z)V */
JNIEXPORT void JNICALL
    Java_java_net_PlainSocketImpl_socketCreate(JNIEnv *env, jobject this,
					       jboolean stream) {
    jobject fdObj;
    int fd;
    int arg = -1;
    
    if (socketExceptionCls == NULL) {
	socketExceptionCls =
	    (*env)->FindClass(env, "java/net/SocketException");
	socketExceptionCls =
	    (jclass)(*env)->NewGlobalRef(env, socketExceptionCls);
	if (socketExceptionCls == NULL) {
	    return;
	}
    }
    fdObj = (*env)->GetObjectField(env, this, psi_fdID);
    
    if (fdObj == NULL) {
	(*env)->ThrowNew(env, socketExceptionCls, "null fd object");
	return;
    }
    
    fd = JVM_Socket(AF_INET6, (stream ? SOCK_STREAM: SOCK_DGRAM), 0);
    
    if (fd == JVM_IO_ERR) {
	/* note: if you run out of fds, you may not be able to load
	 * the exception class, and get a NoClassDefFoundError
	 * instead.
	 */
	(*env)->ThrowNew(env, socketExceptionCls, strerror(errno));
	return;
    } else {
	(*env)->SetIntField(env, fdObj, IO_fd_fdID, fd);
    }
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg, 4);
}

/*
 * inetAddress is the address object passed to the socket connect
 * call.
 *
 * Class:     java_net_PlainSocketImpl
 * Method:    socketConnect
 * Signature: (Ljava/net/InetAddress;I)V
 */
JNIEXPORT void JNICALL
    Java_java_net_PlainSocketImpl_socketConnect(JNIEnv *env, jobject this,
						jobject iaObj, jint port)
{
    jint localport = (*env)->GetIntField(env, this, psi_localportID);
    jint flow_label = (*env)->GetIntField(env, this, psi_flowLabel);
    jint traffic_class = (*env)->GetIntField(env, this, psi_trafficClass);    
    /* address, family and localport are int fields of iaObj */
    int family, i;
    jbyteArray jaddress;
    jbyte *address;
    
    /* fdObj is the FileDescriptor field on this */
    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
    /* fd is an int field on iaObj */
    jint fd;
  
    struct sockaddr_in6 him;
    /* The result of the connection */
    int connect = -1;
  
    if (IS_NULL(fdObj)) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
	return;
    } else {
	fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }
    if (IS_NULL(iaObj)) {
	JNU_ThrowNullPointerException(env, "inet address argument null.");
	return;
    } else {
	jaddress = (*env)->GetObjectField(env, iaObj, ia_addressID); 
	if ((address = (*env)->GetByteArrayElements(env, jaddress, NULL)) == NULL) {
	    jclass newExcCls = (*env)->FindClass(env, "java/lang/RuntimeException");
	    if (newExcCls == 0)  
		return;
	    (*env)->ThrowNew(env, newExcCls, "");
	    return;
	}
	family = (*env)->GetIntField(env, iaObj, ia_familyID);
    }
  
    /* connect */
    memset((char *) &him, 0, sizeof(him));
    him.sin6_port = htons((short) port);
#ifdef SIN6_LEN
    him.sin6_len = sizeof(him);
#endif    
    him.sin6_family = family;
    him.sin6_flowinfo = htonl((traffic_class << 24) | flow_label);
    if (!IN6_IS_ADDR_UNSPECIFIED((struct in6_addr *)address)) {
	for (i = 0; i < V6_SIZE; i++) 
	    him.sin6_addr.s6_addr[i] = address[i];  
    } else {
	him.sin6_addr = in6addr_any;
    }
  
    connect = JVM_Connect(fd, (struct sockaddr *) &him, sizeof(him));
    if (connect < 0) {
	if (connect == JVM_IO_INTR) {
	    JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
			    "operation interrupted");
	} else if (errno == EPROTO) {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "ProtocolException",
			    strerror(errno));
	} else if (errno == ECONNREFUSED) {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "ConnectException",
			    strerror(errno));
	} else if (errno == ETIMEDOUT || errno == EHOSTUNREACH) {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "NoRouteToHostException",
			    strerror(errno));
	} else {
	    char buf[128];
#if JDK_VERSION >= 2
	    jio_snprintf(buf, sizeof(buf), "errno: %d, error: %s for fd: %d", 
			 errno, strerror(errno), fd);
#endif
	    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", buf);
	}
	return;
    }
    (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd);
  
    /* set the remote peer address and port */
    (*env)->SetObjectField(env, this, psi_addressID, iaObj);
    (*env)->SetIntField(env, this, psi_portID, 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.
	 */
	int len = sizeof(him);
	if (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			    strerror(errno));
	    return;
	}
	(*env)->SetIntField(env, this, psi_localportID, ntohs(him.sin6_port));
    }
}

/*
 * Class:     java_net_PlainSocketImpl
 * Method:    socketBind
 * Signature: (Ljava/net/InetAddress;I)V
 */
JNIEXPORT void JNICALL
    Java_java_net_PlainSocketImpl_socketBind(JNIEnv *env, jobject this,
					     jobject iaObj, jint localport) {
  
    /* fdObj is the FileDescriptor field on this */
    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
    /* fd is an int field on fdObj */
    int fd;
  
    /* address and family are int fields on iaObj */
    int family, i;
    jbyteArray jaddress;
    jbyte *address;
  
    struct sockaddr_in6 him;
    if (IS_NULL(fdObj)) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return;
    } else {
	fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }
    if (IS_NULL(iaObj)) {
	JNU_ThrowNullPointerException(env, "iaObj is null.");
	return;
    } else {
	jaddress = (*env)->GetObjectField(env, iaObj, ia_addressID); 
	if ((address = (*env)->GetByteArrayElements(env, jaddress, NULL)) == NULL) {
	    jclass newExcCls = (*env)->FindClass(env, "java/lang/RuntimeException");
	    if (newExcCls == 0)  
		return;
	    (*env)->ThrowNew(env, newExcCls, "");
	    return;
	}
	family = (*env)->GetIntField(env, iaObj, ia_familyID);
    }
    /* bind */
    memset((char *) &him, 0, sizeof(him));
    him.sin6_port = htons((short)localport);
    him.sin6_family = family;
#ifdef SIN6_LEN
    him.sin6_len = sizeof(him);
#endif    
    if (!IN6_IS_ADDR_UNSPECIFIED((struct in6_addr *)address)) {
	for (i = 0; i < V6_SIZE; i++) 
	    him.sin6_addr.s6_addr[i] = address[i];  
    } else {
	him.sin6_addr = in6addr_any;
    }
 
    if (JVM_Bind(fd, (struct sockaddr *)&him, sizeof(him)) == -1) {
	if (errno == EADDRINUSE || errno == EADDRNOTAVAIL ||
	    errno == EPERM || errno == EACCES) {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "BindException",
			    strerror(errno));
	} else {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			    strerror(errno));
	}
	return;
    }
  
    /* set the address */
    (*env)->SetObjectField(env, this, psi_addressID, iaObj);
  
    /* intialize the local port */
    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.
	 */
	int len = sizeof(him);
	if (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			    strerror(errno));
	    return;
	}
	(*env)->SetIntField(env, this, psi_localportID,
			    ntohs(him.sin6_port));
    } else {
	(*env)->SetIntField(env, this, psi_localportID, localport);
    }
}

/*
 * Class:     java_net_PlainSocketImpl
 * Method:    socketListen
 * Signature: (I)V
 */
JNIEXPORT void JNICALL
    Java_java_net_PlainSocketImpl_socketListen (JNIEnv *env, jobject this,
						jint count)
{
    /* this FileDescriptor fd field */
    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
    /* fdObj's int fd field */
    int fd;
  
    if (IS_NULL(fdObj)) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return;
    } else {
	fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }
  
    /*
   * Workaround for bugid 4101691 in Solaris 2.6. See 4106600.
   * If listen backlog is Integer.MAX_VALUE then subtract 1.
   * Check "safe for other platforms"
   */
    if (count == 0x7fffffff)
	count -= 1;
  
    if (JVM_Listen(fd, count) == JVM_IO_ERR) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			strerror(errno));
    }
}

/*
 * Class:     java_net_PlainSocketImpl
 * Method:    socketAccept
 * Signature: (Ljava/net/SocketImpl;)V
 */
JNIEXPORT void JNICALL
    Java_java_net_PlainSocketImpl_socketAccept(JNIEnv *env, jobject this,
					       jobject socket)
{
    /* fields on this */
    jint port;
    jint timeout = (*env)->GetIntField(env, this, psi_timeoutID);
    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
  
  /* the FileDescriptor field on socket */
    jobject socketFdObj;
    /* the InetAddress field on socket */
    jobject socketAddressObj;
    jbyteArray jaddress;

  /* the fd int field on fdObj */
    jint fd;
  
    jthrowable error;
  
    struct sockaddr_in6 him;
    jint len = sizeof(him);
  
    if (IS_NULL(fdObj)) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return;
    } else {
	fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }
    if (IS_NULL(socket)) {
	JNU_ThrowNullPointerException(env, "socket is null");
	return;
    } else {
	socketFdObj = (*env)->GetObjectField(env, socket, psi_fdID);
	socketAddressObj = (*env)->GetObjectField(env, socket, psi_addressID);
    }
    if ((IS_NULL(socketAddressObj)) || (IS_NULL(socketFdObj))) {
	JNU_ThrowNullPointerException(env, "socket address or fd obj");
	return;
    }
    if (timeout) {
	int ret = JVM_Timeout(fd, timeout);
	if (ret == 0) {
	    JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
			    "Accept timed out");
	    return;
	} else if (ret == JVM_IO_ERR) {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			    (errno == EBADF) ? "Socket closed" :
			    strerror(errno));
	    return;
	} else if (ret == JVM_IO_INTR) {
	    JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
			    "operation interrupted");
	    return;
	}
    }
    fd = JVM_Accept(fd, (struct sockaddr *)&him, &len);
    if (fd < 0) {
	if (fd == -2) {
	    JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
			    "operation interrupted");
	} else {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			    (errno == EBADF) ? "Socket closed" : strerror(errno));
	}
	return;
    }
  
    /*
   * This is a gross temporary workaround for a bug in libsocket
   * of solaris2.3.  They exit a mutex twice without entering it
   * and our monitor stuff throws an exception.  The workaround is
   * to simply ignore IllegalMonitorStateException from accept.
   * If we get an exception, and it has an exception object, and
   * we can find the class InternalError in the java pakage, and
   * the exception that was thrown was an instance of
   * IllegalMonitorStateException, then ignore it.
   *
   * Check "safe for other platforms"
   */
    error = (*env)->ExceptionOccurred(env);
    if (!IS_NULL(error)) {
	jclass ilmeCls =
	    (*env)->FindClass(env, "java/lang/IllegalMonitorStateException");
	jclass errorCls = (*env)->GetObjectClass(env, error);
	if (!IS_NULL(ilmeCls) &&
	    (*env)->IsSameObject(env, ilmeCls, errorCls)) {
	    (*env)->ExceptionClear(env);
	}
    }
  
    (*env)->SetIntField(env, socketFdObj, IO_fd_fdID, fd);
  
  /*
   * fill up the remote peer port and address in the new socket structure
   */
    (*env)->SetIntField(env, socket, psi_portID, ntohs(him.sin6_port));
    (*env)->SetIntField(env, socketAddressObj, ia_familyID, him.sin6_family);
    jaddress = (*env)->GetObjectField(env, socketAddressObj, ia_addressID);
    (*env)->SetByteArrayRegion(env, jaddress, 0, V6_SIZE, him.sin6_addr.s6_addr);
    if ((*env)->ExceptionOccurred(env) != NULL)
	return;

  /* also fill up the local port information */
    port = (*env)->GetIntField(env, this, psi_localportID);
    (*env)->SetIntField(env, socket, psi_localportID, port);
}

/*
 * Class:     java_net_PlainSocketImpl
 * Method:    socketAvailable
 * Signature: ()I
 */
JNIEXPORT jint JNICALL
    Java_java_net_PlainSocketImpl_socketAvailable(JNIEnv *env, jobject this) {

    jint ret = -1;
    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
    jint fd;
  
    if (IS_NULL(fdObj)) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return -1;
    } else {
	fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }
    /* JVM_SocketAvailable returns 0 for failure, 1 for success */
    if (!JVM_SocketAvailable(fd, &ret)){
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			strerror(errno));
    }
    return ret;
}

/*
 * Class:     java_net_PlainSocketImpl
 * Method:    socketClose
 * Signature: ()V
 */
JNIEXPORT void JNICALL
    Java_java_net_PlainSocketImpl_socketClose(JNIEnv *env, jobject this) {
    
    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
    jint 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 = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }
    (*env)->SetIntField(env, fdObj, IO_fd_fdID, -1);
    JVM_SocketClose(fd);
}

/*
 * Class:     java_net_PlainSocketImpl
 * Method:    socketSetOption
 * Signature: (IZLjava/lang/Object;)V
 */
JNIEXPORT void JNICALL
    Java_java_net_PlainSocketImpl_socketSetOption(JNIEnv *env, jobject this,
						  jint cmd, jboolean on,
						  jobject value) {
    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
    jint fd;

    /* i is java.lang.Integer */
    jclass iCls;
    jfieldID i_valueID;

    if (IS_NULL(fdObj)) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return;
    } else {
	fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }
    if (fd < 0) {
	/* the fd is closed */
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return;
    }
    if (cmd == java_net_SocketOptions_TCP_NODELAY) {
	long onl = (long)on;
	/* enable or disable TCP_NODELAY */
	if (JVM_SetSockOpt(fd, tcp_level, TCP_NODELAY,
		       (char *)&onl, sizeof(long)) < 0) {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			    strerror(errno));
	}
	return;
    } else if(cmd == java_net_SocketOptions_SO_LINGER) {
	struct linger arg;
	arg.l_onoff = on;

	if(on) {
	    if (IS_NULL(value)) {
		/* trying to enable w/ no timeout */
		JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
				"invalid parameter setting SO_LINGER");
		return;
	    }
	    iCls = (*env)->FindClass(env, "java/lang/Integer");
	    i_valueID = (*env)->GetFieldID(env, iCls, "value", "I");
	    arg.l_linger = (*env)->GetIntField(env, value, i_valueID);
	    if(JVM_SetSockOpt(fd, SOL_SOCKET, SO_LINGER,
			  (char*)&arg, sizeof(arg)) < 0) {
		JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
				strerror(errno));
		return;
	    }
	} else {
	    /* we want to *disable* SO_LINGER.  This means (I think)
	     * that we want to *enable* SO_DONTLINGER.  But,
	     * SO_DONTLINGER doesn't seem to be recognized so
	     * just use SO_LINGER w/ arg.l_onoff off (zero).
	     */
	    if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_LINGER,
			   (char*)&arg, sizeof(arg)) < 0) {
		JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
				strerror(errno));
		return;
	    }
	}
    } else if (cmd == java_net_SocketOptions_SO_SNDBUF) {
	int arg;
	iCls = (*env)->FindClass(env, "java/lang/Integer");
	i_valueID = (*env)->GetFieldID(env, iCls, "value", "I");
	arg = (*env)->GetIntField(env, value, i_valueID);
	if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_SNDBUF,
		       (char *)&arg, sizeof(arg)) < 0) {
	    NET_ThrowCurrent(env, "setsockopt() SO_SNDBUF");
	    return;

	}
    } else if (cmd == java_net_SocketOptions_SO_RCVBUF) {
	int arg;
	iCls = (*env)->FindClass(env, "java/lang/Integer");
	i_valueID = (*env)->GetFieldID(env, iCls, "value", "I");
	arg = (*env)->GetIntField(env, value, i_valueID);
	if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_RCVBUF,
		       (char *)&arg, sizeof(arg)) < 0) {
	    NET_ThrowCurrent(env, "setsockopt() SO_RCVBUF");
	    return;

	}
    } else if (cmd == java_net_SocketOptions_IPV6_AUTH_TRANS_LEVEL) {
	int arg;
	iCls = (*env)->FindClass(env, "java/lang/Integer");
	i_valueID = (*env)->GetFieldID(env, iCls, "value", "I");
	arg = (*env)->GetIntField(env, value, i_valueID);
	if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_AUTH_TRANS_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    NET_ThrowCurrent(env, "setsockopt() IPV6_AUTH_TRANS_LEVEL");
	    return;

	}
    } else if (cmd == java_net_SocketOptions_IPV6_AUTH_NETWORK_LEVEL) {
	int arg;
	iCls = (*env)->FindClass(env, "java/lang/Integer");
	i_valueID = (*env)->GetFieldID(env, iCls, "value", "I");
	arg = (*env)->GetIntField(env, value, i_valueID);
	if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_AUTH_NETWORK_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    NET_ThrowCurrent(env, "setsockopt() IPV6_AUTH_NETWORK_LEVEL");
	    return;

	}
    } else if (cmd == java_net_SocketOptions_IPV6_ESP_TRANS_LEVEL) {
	int arg;
	iCls = (*env)->FindClass(env, "java/lang/Integer");
	i_valueID = (*env)->GetFieldID(env, iCls, "value", "I");
	arg = (*env)->GetIntField(env, value, i_valueID);
	if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_ESP_TRANS_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    NET_ThrowCurrent(env, "setsockopt() IPV6_ESP_TRANS_LEVEL");
	    return;

	}
    } else if (cmd == java_net_SocketOptions_IPV6_ESP_NETWORK_LEVEL) {
	int arg;
	iCls = (*env)->FindClass(env, "java/lang/Integer");
	i_valueID = (*env)->GetFieldID(env, iCls, "value", "I");
	arg = (*env)->GetIntField(env, value, i_valueID);
	if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_ESP_NETWORK_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    NET_ThrowCurrent(env, "setsockopt() IPV6_NETWORK_ESP_LEVEL");
	    return;

	}
    } else {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket option unsupported");
	return;
    }
    return;
}

/*
 * Class:     java_net_PlainSocketImpl
 * Method:    socketGetOption
 * Signature: (I)I
 */
JNIEXPORT jbyteArray JNICALL
    Java_java_net_PlainSocketImpl_socketGetOption(JNIEnv *env, jobject this,
						  jint opt) {
    /*
   * returning -1 from this method means the option is DISABLED.
   * Any other return value (including 0) is considered an option
   * parameter to be interpreted.
   */
    
    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
    int fd;
    jbyteArray jaddress;
    
    if (IS_NULL(fdObj)) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return NULL;
    } else {
	fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }
    if(fd < 0) {
	/* the fd is closed */
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return NULL;
    }
    if (opt == java_net_SocketOptions_TCP_NODELAY) {
	/* query for TCP_NODELAY */
	int result, len, net;
	len = sizeof(int);
	if (JVM_GetSockOpt(fd, tcp_level, TCP_NODELAY, (char*)&result, &len) < 0) {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			    strerror(errno));
	    return NULL;
	}
	if (result == 0 ) {
	    char net = -1;
	    len = sizeof(net);
	    jaddress = (*env)->NewByteArray(env, len);
	    if (jaddress == NULL) {
		JNU_ThrowOutOfMemoryError(env, "Result allocation failed");
		return NULL;
	    }
	    (*env)->SetByteArrayRegion(env, jaddress, 0, len, (char *)&net); 
	    return jaddress;
	} else {
	    jaddress = (*env)->NewByteArray(env, len);
	    if (jaddress == NULL) {
		JNU_ThrowOutOfMemoryError(env, "Result allocation failed");
		return NULL;
	    }
	    net = htonl(result);
	    (*env)->SetByteArrayRegion(env, jaddress, 0, len, (char *)&net);
	    return jaddress;     
	}; 
    } else if (opt == java_net_SocketOptions_SO_BINDADDR) {
	/* find out local IP address, return as 128 bit value */
	struct sockaddr_in6 him;
	int len = sizeof(struct sockaddr_in6);
	memset(&him, 0, len);
	if (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			    strerror(errno));
	    return NULL;
	}
	jaddress = (*env)->NewByteArray(env, V6_SIZE);
	if (jaddress == NULL) {
	    JNU_ThrowOutOfMemoryError(env, "Address allocation failed");
	    return NULL;
	}
	(*env)->SetByteArrayRegion(env, jaddress, 0, V6_SIZE, him.sin6_addr.s6_addr);
	return jaddress; 
    } else if (opt == java_net_SocketOptions_SO_LINGER) {
	struct linger arg;
	int len = sizeof(struct linger);
	memset(&arg, 0, len);
    
	if(JVM_GetSockOpt(fd, SOL_SOCKET, SO_LINGER, (char*)&arg, &len) < 0) {
	    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			    strerror(errno));
	    return NULL;
	}
	/* if disabled, return -1, else return the linger time */
	if (arg.l_onoff) {
	    int net = htonl(arg.l_linger);
	    len = sizeof(net);
	    jaddress = (*env)->NewByteArray(env, len);
	    if (jaddress == NULL) {
		JNU_ThrowOutOfMemoryError(env, "Result allocation failed");
		return NULL;
	    }
	    (*env)->SetByteArrayRegion(env, jaddress, 0, len, (char *)&net);
	    return jaddress;
	} else {
	    char net = -1;
	    len = sizeof(net);
	    jaddress = (*env)->NewByteArray(env, len);
	    if (jaddress == NULL) {
		JNU_ThrowOutOfMemoryError(env, "Result allocation failed");
		return NULL;
	    }
	    (*env)->SetByteArrayRegion(env, jaddress, 0, 1, (char *)&net); 
	    return jaddress;
	}
    } else if (opt == java_net_SocketOptions_SO_SNDBUF) {
	int result = -1, net;
	int len = sizeof(result);
	if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_SNDBUF,
		       (char *)&result, &len) < 0) {
	    NET_ThrowCurrent(env, "getsockopt() SO_SNDBUF");
	    return NULL;
	}
	jaddress = (*env)->NewByteArray(env, len);
	if (jaddress == NULL) {
	    JNU_ThrowOutOfMemoryError(env, "Result allocation failed");
	    return NULL;
	}
	net = htonl(result);
	(*env)->SetByteArrayRegion(env, jaddress, 0, len, (char *)&net);
	return jaddress;
    } else if (opt == java_net_SocketOptions_SO_RCVBUF) {
	int result = -1, net;
	int len = sizeof(result);
	if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_RCVBUF,
		       (char *)&result, &len) < 0) {
	    NET_ThrowCurrent(env, "getsockopt() SO_RCVBUF");
	    return NULL;
	}
	jaddress = (*env)->NewByteArray(env, len);
	if (jaddress == NULL) {
	    JNU_ThrowOutOfMemoryError(env, "Result allocation failed");
	    return NULL;
	}
	net = htonl(result);
	(*env)->SetByteArrayRegion(env, jaddress, 0, len, (char *)&net);
	return jaddress;
    } else if (opt == java_net_SocketOptions_IPV6_AUTH_TRANS_LEVEL) {
	int result = -1, net;
	int len = sizeof(result);
	if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_AUTH_TRANS_LEVEL,
		       (char *)&result, &len) < 0) {
	    NET_ThrowCurrent(env, "getsockopt() IPV6_AUTH_TRANS_LEVEL");
	    return NULL;
	}
	jaddress = (*env)->NewByteArray(env, len);
	if (jaddress == NULL) {
	    JNU_ThrowOutOfMemoryError(env, "Result allocation failed");
	    return NULL;
	}
	net = htonl(result);
	(*env)->SetByteArrayRegion(env, jaddress, 0, len, (char *)&net);
	return jaddress;
    } else if (opt == java_net_SocketOptions_IPV6_AUTH_NETWORK_LEVEL) {
	int result = -1, net;
	int len = sizeof(result);
	if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_AUTH_NETWORK_LEVEL,
		       (char *)&result, &len) < 0) {
	    NET_ThrowCurrent(env, "getsockopt() IPV6_AUTH_NETWORK_LEVEL");
	    return NULL;
	}
	jaddress = (*env)->NewByteArray(env, len);
	if (jaddress == NULL) {
	    JNU_ThrowOutOfMemoryError(env, "Result allocation failed");
	    return NULL;
	}
	net = htonl(result);
	(*env)->SetByteArrayRegion(env, jaddress, 0, len, (char *)&net);
	return jaddress;
    } else if (opt == java_net_SocketOptions_IPV6_ESP_TRANS_LEVEL) {
	int result = -1, net;
	int len = sizeof(result);
	if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_ESP_TRANS_LEVEL,
		       (char *)&result, &len) < 0) {
	    NET_ThrowCurrent(env, "getsockopt() IPV6_ESP_TRANS_LEVEL");
	    return NULL;
	}
	jaddress = (*env)->NewByteArray(env, len);
	if (jaddress == NULL) {
	    JNU_ThrowOutOfMemoryError(env, "Result allocation failed");
	    return NULL;
	}
	net = htonl(result);
	(*env)->SetByteArrayRegion(env, jaddress, 0, len, (char *)&net);
	return jaddress;
    } else if (opt == java_net_SocketOptions_IPV6_ESP_NETWORK_LEVEL) {
	int result = -1, net;
	int len = sizeof(result);
	if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_ESP_NETWORK_LEVEL,
		       (char *)&result, &len) < 0) {
	    NET_ThrowCurrent(env, "getsockopt() IPV6_ESP_NETWORK_LEVEL");
	    return NULL;
	}
	jaddress = (*env)->NewByteArray(env, len);
	if (jaddress == NULL) {
	    JNU_ThrowOutOfMemoryError(env, "Result allocation failed");
	    return NULL;
	}
	net = htonl(result);
	(*env)->SetByteArrayRegion(env, jaddress, 0, len, (char *)&net);
	return jaddress;
    }    
  
    /* we don't understand the specified option */
    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
		    "invalid option or option not supported on this stack");
    return NULL;
}

