/*
 * @(#)PlainDatagramSocketImpl.c	1.37 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_PlainRawSocketImpl.h"

/*
 * PlainRawSocketImpl
 */

static jfieldID IO_fd_fdID;
static jfieldID prsi_flowLabel;
static jfieldID prsi_trafficClass;
static jfieldID prsi_fdID;
static jfieldID prsi_timeoutID;
static jfieldID prsi_protocol;

/*
 * Class:     java_net_PlainRawSocketImpl
 * Method:    init
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainRawSocketImpl_init(JNIEnv *env, jclass cls) {
  
  prsi_fdID = (*env)->GetFieldID(env, cls, "fd",
				 "Ljava/io/FileDescriptor;");
  prsi_timeoutID = (*env)->GetFieldID(env, cls, "timeout", "I");
  prsi_protocol = (*env)->GetFieldID(env, cls, "protocol", "I");
  IO_fd_fdID = NET_GetFileDescriptorID(env);
  prsi_flowLabel = (*env)->GetFieldID(env, cls, "flow_label", "I");
  prsi_trafficClass = (*env)->GetFieldID(env, cls, "traffic_class", "I");
}

/*
 * Class:     java_net_PlainRawSocketImpl
 * Method:    bind
 * Signature: (ILjava/net/InetAddress;)V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainRawSocketImpl_bind(JNIEnv *env, jobject this,
					   jobject laddr) {
  
  jobject fdObj = (*env)->GetObjectField(env, this, prsi_fdID);
  int fd, i = 0;
  jbyteArray jaddress;
  struct sockaddr_in6 lcladdr;
  int lcladdrlen= sizeof(lcladdr);
  jbyte *address;
  
  if (IS_NULL(laddr)) { /* shouldn't happen, but be safe */
    i = -1;
  } else {
    jaddress = (*env)->GetObjectField(env, laddr, 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;
    }
  }
  if (IS_NULL(fdObj)) {
    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
		    "Socket closed");
    return;
  } else {
    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
  }

  memset((char *) &lcladdr, 0, lcladdrlen);
  lcladdr.sin6_family = AF_INET6;
#ifdef SIN6_LEN
  lcladdr.sin6_len = lcladdrlen;
#endif    
  /* Check "the evaluation order of ||. Should be left THEN right" */
  if ((i != -1) || !IN6_IS_ADDR_UNSPECIFIED((struct in6_addr *)address)) {
    for (i = 0; i < V6_SIZE; i++) 
      lcladdr.sin6_addr.s6_addr[i] = address[i];  
  } else {
    lcladdr.sin6_addr = in6addr_any;
  }
  
  if (JVM_Bind(fd, (struct sockaddr *)&lcladdr, sizeof(lcladdr)) == -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;
  }

}

/*
 * Class:     java_net_PlainRawSocketImpl
 * Method:    send
 * Signature: (Ljava/net/RawPacket;)V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainRawSocketImpl_send(JNIEnv *env, jobject this,
					   jobject packet) {

  char BUF[MAX_BUFFER_LEN];
  char *fullPacket = NULL;
  int mallocedPacket = JNI_FALSE, i;
  jint flow_label = (*env)->GetIntField(env, this, prsi_flowLabel);
  jint traffic_class = (*env)->GetIntField(env, this, prsi_trafficClass);
  /* The object's field */
  jobject fdObj = (*env)->GetObjectField(env, this, prsi_fdID);
  
  jbyteArray packetBuffer;
  jobject packetAddress;
  jint packetBufferOffset, packetBufferLen;
  
  /* The packetAddress address, family and port */
  jint family;
  jbyte *address;
  jbyteArray jaddress;
  
  /* The fdObj'fd */
  jint fd;
  
  struct sockaddr_in6 rmtaddr;
   int lcladdrlen= sizeof(rmtaddr);
 
  if (IS_NULL(fdObj)) {
    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
		    "Socket closed");
    return;
  }
  fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
  
  if (IS_NULL(packet)) {
    JNU_ThrowNullPointerException(env, "packet");
    return;
  }
  
  packetBuffer = (*env)->GetObjectField(env, packet, rp_bufID);
  packetAddress = (*env)->GetObjectField(env, packet, rp_addressID);
  if (IS_NULL(packetBuffer) || IS_NULL(packetAddress)) {
    JNU_ThrowNullPointerException(env, "null buffer || null address");
    return;
  }
  
  packetBufferOffset = (*env)->GetIntField(env, packet, rp_offsetID);
  packetBufferLen = (*env)->GetIntField(env, packet, rp_lengthID);
  
  jaddress = (*env)->GetObjectField(env, packetAddress, 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, packetAddress, ia_familyID);
  
  memset((char *) &rmtaddr, 0, sizeof(rmtaddr));
  rmtaddr.sin6_family = family;
#ifdef SIN6_LEN
  rmtaddr.sin6_len = lcladdrlen;
#endif    
  rmtaddr.sin6_flowinfo = htonl((traffic_class << 24) | flow_label);
  if (!IN6_IS_ADDR_UNSPECIFIED((struct in6_addr *)address)) {
    for (i = 0; i < V6_SIZE; i++) 
      rmtaddr.sin6_addr.s6_addr[i] = address[i];  
  } else {
    rmtaddr.sin6_addr = in6addr_any;
  }

  if (packetBufferLen > MAX_BUFFER_LEN) {
    /* When JNI-ifying the JDK's IO routines, we turned
     * read's and write's of byte arrays of size greater
     * than 2048 bytes into several operations of size 2048.
     * This saves a malloc()/memcpy()/free() for big
     * buffers.  This is OK for file IO and TCP, but that
     * strategy violates the semantics of a raw protocol.
     * (one big send) != (several smaller sends).  So here
     * we *must* alloc the buffer.  Note it may be bigger
     * than 65,536 (0xFFFF) with the use of jumbograms
     *
     * We may want to use a smarter allocation scheme at some
     * point.
     */
    fullPacket = (char *)malloc(packetBufferLen);
    
    if (!fullPacket) {
      JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
      return;
    } else {
      mallocedPacket = JNI_TRUE;
    }
  } else {
    fullPacket = &(BUF[0]);
  }

  (*env)->GetByteArrayRegion(env, packetBuffer, packetBufferOffset, packetBufferLen,
			     (jbyte *)fullPacket);
  
  switch (JVM_SendTo(fd, fullPacket, packetBufferLen, 0,
			       (struct sockaddr *)&rmtaddr,
		     sizeof(rmtaddr))) {
  case JVM_IO_ERR:
    JNU_ThrowIOException(env, strerror(errno));
    break;
  case JVM_IO_INTR:
    JNU_ThrowByName(env, "java/io/InterruptedIOException",
		    "operation interrupted");
  }
  if (mallocedPacket) {
    free(fullPacket);
  }
  return;
}

/*
 * Class:     java_net_PlainRawSocketImpl
 * Method:    peek
 * Signature: (Ljava/net/InetAddress;)I
 */
JNIEXPORT void JNICALL
Java_java_net_PlainRawSocketImpl_peek(JNIEnv *env, jobject this,
					   jobject addressObj) {

  jobject fdObj = (*env)->GetObjectField(env, this, prsi_fdID);
  jint timeout = (*env)->GetIntField(env, this, prsi_timeoutID);
  jint fd;
  
  /* The address and family fields of addressObj */
  jbyteArray jaddress;
  jint family;
  
  ssize_t n;
  struct sockaddr_in6 remote_addr;
  int remote_addrsize = sizeof (remote_addr);
  char buf[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(addressObj)) {
	JNU_ThrowNullPointerException(env, "Null address in peek()");
  return;
  } else {
    jaddress = (*env)->GetObjectField(env, addressObj, ia_addressID); 
    family = (*env)->GetIntField(env, addressObj, ia_familyID);
  }
  if (timeout) {
    int ret = JVM_Timeout(fd, timeout);
    if (ret == 0) {
      JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
			    "Peek 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;	/* WARNING: SHOULD WE REALLY RETURN -2??? */
    }
  }
  n = JVM_RecvFrom(fd, buf, 1, MSG_PEEK,
		   (struct sockaddr *)&remote_addr, &remote_addrsize);
  
  if (n == JVM_IO_ERR) {
    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
		    (errno == EBADF) ? "Socket closed" : strerror(errno));
    return;
  } else if (n == JVM_IO_INTR) {
    JNU_ThrowByName(env, "java/io/InterruptedIOException", 0);
    return;
  }

  (*env)->SetByteArrayRegion(env, jaddress, 0, V6_SIZE, remote_addr.sin6_addr.s6_addr);
  if ((*env)->ExceptionOccurred(env) != NULL)
    return;
  (*env)->SetIntField(env, addressObj, ia_familyID,
		      remote_addr.sin6_family);
  
  return;
}

/*
 * Class:     java_net_PlainRawSocketImpl
 * Method:    receive
 * Signature: (Ljava/net/RawPacket;)V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainRawSocketImpl_receive(JNIEnv *env, jobject this,
					      jobject packet) {

  char BUF[MAX_BUFFER_LEN];
  char *fullPacket = NULL;
  int mallocedPacket = JNI_FALSE;
  jobject fdObj = (*env)->GetObjectField(env, this, prsi_fdID);
  jint timeout = (*env)->GetIntField(env, this, prsi_timeoutID);
  
  jbyteArray packetBuffer;
  jint packetBufferOffset, packetBufferLen;
  
  int fd;
  jbyteArray jaddress;
  
  int n;
  struct sockaddr_in6 remote_addr;
  int remote_addrsize = sizeof (remote_addr);
  
  if (IS_NULL(fdObj)) {
    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
		    "Socket closed");
    return;
  }

  fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
  
  if (IS_NULL(packet)) {
    JNU_ThrowNullPointerException(env, "packet");
    return;
  }
  
  packetBuffer = (*env)->GetObjectField(env, packet, rp_bufID);
  if (IS_NULL(packetBuffer)) {
    JNU_ThrowNullPointerException(env, "packet buffer");
    return;
  }
  packetBufferOffset = (*env)->GetIntField(env, packet, rp_offsetID);
  packetBufferLen = (*env)->GetIntField(env, packet, rp_lengthID);
  if (timeout) {
    int ret = JVM_Timeout(fd, timeout);
    if (ret == 0) {
      JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
		      "Receive timed out");
      return;
    } else if (ret == JVM_IO_ERR) {
      JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
		      "Socket closed");
      return;
    } else if (ret == JVM_IO_INTR) {
      JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
		      "operation interrupted");
      return;
    }
  }
  
  if (packetBufferLen > MAX_BUFFER_LEN) {
    
    /* When JNI-ifying the JDK's IO routines, we turned
     * read's and write's of byte arrays of size greater
     * than 2048 bytes into several operations of size 2048.
     * This saves a malloc()/memcpy()/free() for big
     * buffers.  This is OK for file IO and TCP, but that
     * strategy violates the semantics of a raw protocol.
     * (one big send) != (several smaller sends).  So here
     * we *must* alloc the buffer.    Note it may be bigger
     * than 65,536 (0xFFFF) with the use of jumbograms
     *
     * We may want to use a smarter allocation scheme at some
     * point.
     */
     fullPacket = (char *)malloc(packetBufferLen);
    
    if (!fullPacket) {
      JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
      return;
    } else {
      mallocedPacket = JNI_TRUE;
    }
  } else {
    fullPacket = &(BUF[0]);
  }
  n = JVM_RecvFrom(fd, fullPacket, packetBufferLen, 0,
		   (struct sockaddr *)&remote_addr, &remote_addrsize);
  /* truncate the data if the packet's length is too small */
  if (n > packetBufferLen) {
    n = packetBufferLen;
  }
  if (n == JVM_IO_ERR) {
    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
		    "socket closed");
    (*env)->SetIntField(env, packet, rp_offsetID, 0);
    (*env)->SetIntField(env, packet, rp_lengthID, 0);
  } else if (n == JVM_IO_INTR) {
    JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
		    "operation interrupted");
    (*env)->SetIntField(env, packet, rp_offsetID, 0);
    (*env)->SetIntField(env, packet, rp_lengthID, 0);
  } else {
    /*
     * success - fill in received address...
     *
     * REMIND: Fill in an int on the packet, and create inetadd
     * object in Java, as a performance improvement. Also
     * construct the inetadd object lazily.
     */
    jclass ia_clazz = (*env)->FindClass(env, "java/net/InetAddress");
    jmethodID ia_ctor = (*env)->GetMethodID(env, ia_clazz,
					    "<init>", "()V");
    jobject packetAddress = (*env)->NewObject(env, ia_clazz, ia_ctor);
    
    jaddress = (*env)->GetObjectField(env, packetAddress, ia_addressID);
    (*env)->SetByteArrayRegion(env, jaddress, 0, V6_SIZE, remote_addr.sin6_addr.s6_addr);
    if ((*env)->ExceptionOccurred(env) != NULL)
      return;
    (*env)->SetIntField(env, packetAddress, ia_familyID,
			remote_addr.sin6_family);
    /* stuff the new Inetaddress in the packet */
    (*env)->SetObjectField(env, packet, rp_addressID, packetAddress);
    /* and fill in the data, remote address and such */
    (*env)->SetByteArrayRegion(env, packetBuffer, packetBufferOffset, n,
			       (jbyte *)fullPacket);
    (*env)->SetIntField(env, packet, rp_lengthID, n);
    
    (*env)->SetByteArrayRegion(env, jaddress, 0, V6_SIZE, remote_addr.sin6_addr.s6_addr);
    if ((*env)->ExceptionOccurred(env) != NULL)
      return;
    (*env)->SetIntField(env, packetAddress, ia_familyID,
			remote_addr.sin6_family);
  }
  
  if (mallocedPacket) {
    free(fullPacket);
  }
}

/*
 * Class:     java_net_PlainRawSocketImpl
 * Method:    rawSocketCreate
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainRawSocketImpl_rawSocketCreate(JNIEnv *env,
							   jobject this, jint proto) {
    jobject fdObj = (*env)->GetObjectField(env, this, prsi_fdID);
    int fd;


    if (IS_NULL(fdObj)) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return;
    } else {
	fd =  JVM_Socket(AF_INET6, SOCK_RAW, proto);
    }
    if (fd == JVM_IO_ERR) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			strerror(errno));
	return;
    }
    (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd);
}

/*
 * Class:     java_net_PlainRawSocketImpl
 * Method:    rawSocketClose
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainRawSocketImpl_rawSocketClose(JNIEnv *env,
							  jobject this) {
    /*
     * REMIND: PUT A LOCK AROUND THIS CODE
     */
    jobject fdObj = (*env)->GetObjectField(env, this, prsi_fdID);
    int fd;

    if (IS_NULL(fdObj)) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return;
    } else {
        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }
    JVM_Close(fd);
    (*env)->SetIntField(env, fdObj, IO_fd_fdID, -1);
}

/*
 * Class:     java_net_PlainRawSocketImpl
 * Method:    socketSetOption
 * Signature: (ILjava/lang/Object;)V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainRawSocketImpl_socketSetOption(JNIEnv *env,
						      jobject this,
						      jint opt,
						      jobject value) {

    jobject fdObj = (*env)->GetObjectField(env, this, prsi_fdID);
    int fd;

    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(value)) {
	JNU_ThrowNullPointerException(env, "value argument");
	return;
    }
    if (fd < 0) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"socket closed");
	return;
    } else if (opt == java_net_SocketOptions_SO_SNDBUF) {
	int arg;
	jclass iCls = (*env)->FindClass(env, "java/lang/Integer");
	jfieldID 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 (opt == java_net_SocketOptions_SO_RCVBUF) {
	int arg;
	jclass iCls = (*env)->FindClass(env, "java/lang/Integer");
	jfieldID 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 (opt == java_net_SocketOptions_IPV6_AUTH_TRANS_LEVEL) {
	int arg;
	jclass iCls = (*env)->FindClass(env, "java/lang/Integer");
	jfieldID i_valueID = (*env)->GetFieldID(env, iCls, "value", "I");
	arg = (*env)->GetIntField(env, value, i_valueID);
	if (JVM_SetSockOpt(fd, SOL_SOCKET, IPV6_AUTH_TRANS_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    NET_ThrowCurrent(env, "setsockopt() IPV6_AUTH_TRANS_LEVEL");
	    return;
	}
    } else if (opt == java_net_SocketOptions_IPV6_AUTH_NETWORK_LEVEL) {
	int arg;
	jclass iCls = (*env)->FindClass(env, "java/lang/Integer");
	jfieldID i_valueID = (*env)->GetFieldID(env, iCls, "value", "I");
	arg = (*env)->GetIntField(env, value, i_valueID);
	if (JVM_SetSockOpt(fd, SOL_SOCKET, IPV6_AUTH_NETWORK_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    NET_ThrowCurrent(env, "setsockopt() IPV6_AUTH_NETWORK_LEVEL");
	    return;
	}
    } else if (opt == java_net_SocketOptions_IPV6_ESP_TRANS_LEVEL) {
	int arg;
	jclass iCls = (*env)->FindClass(env, "java/lang/Integer");
	jfieldID i_valueID = (*env)->GetFieldID(env, iCls, "value", "I");
	arg = (*env)->GetIntField(env, value, i_valueID);
	if (JVM_SetSockOpt(fd, SOL_SOCKET, IPV6_ESP_TRANS_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    NET_ThrowCurrent(env, "setsockopt() IPV6_ESP_TRANS_LEVEL");
	    return;
	}
    } else if (opt == java_net_SocketOptions_IPV6_ESP_NETWORK_LEVEL) {
	int arg;
	jclass iCls = (*env)->FindClass(env, "java/lang/Integer");
	jfieldID i_valueID = (*env)->GetFieldID(env, iCls, "value", "I");
	arg = (*env)->GetIntField(env, value, i_valueID);
	if (JVM_SetSockOpt(fd, SOL_SOCKET, IPV6_ESP_NETWORK_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    NET_ThrowCurrent(env, "setsockopt() IPV6_ESP_NETWORK_LEVEL");
	    return;
	}
    } else {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"invalid RawSocket option");
    }
}

/*
 * Returns relevant info as a jbyteArray.
 *
 * Class:     java_net_PlainRawSocketImpl
 * Method:    socketGetOption
 * Signature: (I)I
 */
JNIEXPORT jbyteArray JNICALL
Java_java_net_PlainRawSocketImpl_socketGetOption(JNIEnv *env, jobject this,
						      jint opt) {
  jobject fdObj = (*env)->GetObjectField(env, this, prsi_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) {
    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
		    "socket closed");
    return NULL;
  } 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_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;
  } else {
    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
		    "invalid RawSocket option or option not supported on this stack");
    return NULL;
  }
}

/*
 * Raw IP-related calls
 */

/*
 * Class:     java_net_PlainRawSocketImpl
 * Method:    setChecksum
 * Signature: (B)V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainRawSocketImpl_setChecksum(JNIEnv *env, jobject this,
						    jint offset) {

    jobject fdObj = (*env)->GetObjectField(env, this, prsi_fdID);
    int fd;
    /* it is important to cast this to a char, otherwise setsockopt gets confused */
    int value = offset;

    if (IS_NULL(fdObj)) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return;
    } else {
	fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }
    /* setsockopt to be correct checksum */
    if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, (char*)&value,
		   sizeof(value)) < 0) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			strerror(errno));
    }
}


/*
 * Class:     java_net_PlainRawSocketImpl
 * Method:    getChecksum
 * Signature: ()B
 */
JNIEXPORT jint JNICALL
Java_java_net_PlainRawSocketImpl_getChecksum(JNIEnv *env, jobject this) {

    jobject fdObj = (*env)->GetObjectField(env, this, prsi_fdID);
    jint fd = -1;

    int  offset = 0;
    int len = sizeof(offset);
    if (IS_NULL(fdObj)) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			"Socket closed");
	return -1;
    } else {
	fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }
    /* getsockopt of checksum */
    if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_CHECKSUM,
		   (char*)&offset, &len) < 0) {
	JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
			strerror(errno));
	return -1;
    }
    return (jint)offset;
}



