/* $Id: PlainANEPSocketImpl.c,v 1.14 2001/01/08 13:53:31 sdalu Exp $ */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <errno.h>
#include <string.h>
#include <stdlib.h>

#include "anep.h"

#include <jni.h>
#include "jvm_utils.h"
#include "java_net_SocketOptions.h"
#include "java_net_PlainANEPSocketImpl.h"
#include "java_net_ANEPOption.h"
#include "java_net_ANEPPacket.h"

#include "config.h"

#define HI(r...) printf(r)

#define HERE								\
	do {								\
		HI("You are in %s/%d\n", __FUNCTION__, __LINE__);	\
	} while(0)

#ifndef MIN
#  define MIN(x,y) (x < y ? x : y)
#endif

/* java.lang.Integer */
static jfieldID i_valueID;

/* java.io.FileDescriptor */
static jfieldID fd_fdID;

/* java.net.PlainANEPSocketImpl */
static jfieldID pasi_flowLabelID;
static jfieldID pasi_trafficClassID;
static jfieldID pasi_fdID;
static jfieldID pasi_discardFlagID;
static jfieldID pasi_timeoutID;

/* java.net.ANEPPacket */
static jfieldID ap_offsetID;
static jfieldID ap_lengthID;
static jfieldID ap_flagsID;
static jfieldID ap_typeID;
static jfieldID ap_payloadID;
static jfieldID ap_optionsID;
static jfieldID ap_addressID;

/* java.net.InetAddress */
static jfieldID ia_familyID;
static jfieldID ia_addressID;

/* java.net.ANEPOption */
static jfieldID ao_typeID;
static jfieldID ao_flagsID;
static jfieldID ao_offsetID;
static jfieldID ao_lengthID;
static jfieldID ao_payloadID;
static jfieldID ao_receivedID;

/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    init
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainANEPSocketImpl_init(JNIEnv *env, jclass cls) {
    jclass fdCls   = (*env)->FindClass(env, "java/io/FileDescriptor");
    jclass iCls    = (*env)->FindClass(env, "java/lang/Integer");
    jclass apCls   = (*env)->FindClass(env, "java/net/ANEPPacket");
    jclass pasiCls = (*env)->FindClass(env, "java/net/PlainANEPSocketImpl");
    jclass aoCls   = (*env)->FindClass(env, "java/net/ANEPOption");
    jclass iaCls   = (*env)->FindClass(env, "java/net/InetAddress");

#define CacheFieldID(pre, field, class) \
    pre##_##field##ID = (*env)->GetFieldID(env, pre##Cls, #field, class)

    /* java.lang.Integer */
    CacheFieldID(i, value, "I");

    /* java.io.FileDescriptor */
    CacheFieldID(fd, fd, "I");

    /* java.net.PlainANEPSocketImpl */
    CacheFieldID(pasi, fd,           "Ljava/io/FileDescriptor;");
    CacheFieldID(pasi, discardFlag,  "I");
    CacheFieldID(pasi, timeout,      "I");
    CacheFieldID(pasi, flowLabel,    "I");
    CacheFieldID(pasi, trafficClass, "I");

    /* java.net.ANEPPacket */
    CacheFieldID(ap, offset,  "I");
    CacheFieldID(ap, length,  "I");
    CacheFieldID(ap, flags,   "I");
    CacheFieldID(ap, type,    "I");
    CacheFieldID(ap, payload, "[B");
    CacheFieldID(ap, options, "[Ljava/net/ANEPOption;");
    CacheFieldID(ap, address, "Ljava/net/InetAddress;");

    /* java.net.InetAddress */
    CacheFieldID(ia, family,  "I");
    CacheFieldID(ia, address, "[B");

    /* java.net.ANEPOption */
    CacheFieldID(ao, type,	"I");
    CacheFieldID(ao, flags,	"I");
    CacheFieldID(ao, payload,	"[B");
    CacheFieldID(ao, offset,	"I");
    CacheFieldID(ao, length,	"I");
    CacheFieldID(ao, received,  "Z");

#undef CacheFieldID
}

/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    bind
 * Signature: (ILjava/net/InetAddress;)V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainANEPSocketImpl_bind(JNIEnv *env, jobject this,
				       jint type, jobject laddr) {
    jobject		fdObj   = (*env)->GetObjectField(env, this, pasi_fdID);
    jbyteArray		jaddress;
    jbyte		*address;
    socklen_t		addrlen = sizeof(struct sockaddr_anep);
    struct sockaddr_anep addr;
    struct in6_addr	ip_addr = in6addr_any;
    int			fd;
    
    /* Sanity check */
    if (!fdObj)
	THROW(env, SocketException, "Socket closed");
    if (type < 0 || type > java_net_ANEPPacket_MAX_TYPE)
	THROW(env, IllegalArgumentException, "type out of range");

    /* chose the address? */
    if (!laddr)
	goto addr_set;

    /* Extract address information */
    jaddress = (*env)->GetObjectField(env, laddr, ia_addressID); 
    address  = (*env)->GetByteArrayElements(env, jaddress, NULL);

    if (!address)
	THROW(env, RuntimeException, "unable to fetch address data");

    /* save IP address */
    memcpy(&ip_addr, address, sizeof(struct in6_addr));
    // ::TODO:: flowinfo?

    /* Set IPv6 socket address */
 addr_set:
    memset(&addr, 0, addrlen);
    addr.sanep_family		= AF_INET6;
    addr.sanep_type		= htons((short)type);
    addr.sanep_ipv6_flowinfo	= 0;
    addr.sanep_ipv6_addr	= ip_addr;

    /* Retrieve file descriptor */
    fd = (*env)->GetIntField(env, fdObj, fd_fdID);

    /* Bind */
    if (JVM_Bind(fd, (struct sockaddr *)&addr, addrlen) < 0) {
	switch(errno) {
	case EADDRINUSE:
	case EADDRNOTAVAIL:
	case EPERM:
	case EACCES:
	    THROW(env, BindException, strerror(errno));
	default:
	    THROW(env, IOException, strerror(errno));
	}
    }
}

/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    _send
 * Signature: (Ljava/net/ANEPPacket;)V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainANEPSocketImpl__1send(JNIEnv *env, jobject this,
					 jobject packet) {
    char			 buff[MAX_BUFFER_LEN];
    char			*fullPacket	= buff;
    struct in6_addr		 ip_addr	= in6addr_any;
    struct sockaddr_anep	 rmtaddr;
    struct anep_optbuffer	 clroptbuff	= ANEP_OPTBUFFER_STRUCT_SEND;
    struct anep_optbuffer	*optbuff	= &clroptbuff;
    struct anep_optbuffer	*optinfo;
    int				 option_count;
    int				 i;
    int				 optsize	= 0;
    int				 buffsize;
    char			*exc		= NULL;
    char			*msg		= NULL;

    jobject			 fdObj;
    jint			 fd;
    jint			 socketDiscardFlag;

    jbyteArray			 packetBuffer;
    jobject			 packetAddress;
    jint			 packetBufferOffset;
    jint			 packetBufferLen;
    jint			 packetType;
    jint			 packetFlags;
    jobjectArray		 packetOptions;

    jobject			 option;
    jint			 optionType;
    jint			 optionBufferOffset;
    jint			 optionBufferLen;
    jint			 optionFlags;
    jbyteArray			 optionBuffer;

    jint			 family;
    jbyte			*address;
    jbyteArray			 a_address;
    
    jint			 flow_label;
    jint			 traffic_class;

    /* Sanity check */
    fdObj = (*env)->GetObjectField(env, this, pasi_fdID);
    if (!fdObj) 
	THROW(env, SocketException, "Socket closed");
    if (!packet)
	THROW(env, NullPointerException, "packet");

    /* Retrieve socket info (ie: hack) */
    socketDiscardFlag = !!(*env)->GetIntField(env, this, pasi_discardFlagID);

    /* Retrieve FlowLabel and TrafficClass */
    flow_label    = (*env)->GetIntField(env, this, pasi_flowLabelID);
    traffic_class = (*env)->GetIntField(env, this, pasi_trafficClassID);

    /* Retrieve: buffer and address */
    packetBuffer  = (*env)->GetObjectField(env, packet, ap_payloadID);
    packetAddress = (*env)->GetObjectField(env, packet, ap_addressID);

    /* Sanity check */
    if (!packetBuffer || !packetAddress)
	THROW(env, NullPointerException, "null buffer or null address");
  
    /* Retrieve: File descriptor, type, flags, bufferOffset and bufferLen */
    fd		       = (*env)->GetIntField(env, fdObj,  fd_fdID);
    packetType         = (*env)->GetIntField(env, packet, ap_typeID);
    packetFlags	       = (*env)->GetIntField(env, packet, ap_flagsID);
    packetBufferOffset = (*env)->GetIntField(env, packet, ap_offsetID);
    packetBufferLen    = (*env)->GetIntField(env, packet, ap_lengthID);

    /* Extract address and family */
    a_address          = (*env)->GetObjectField(env, packetAddress, 
						ia_addressID); 
    address            = (*env)->GetByteArrayElements(env, a_address, NULL);
    family	       = (*env)->GetIntField(env, packetAddress, ia_familyID);

    /* Sanity check */
    if (!address)
	THROW(env, RuntimeException, "unable to fetch address data");
    if (family != AF_INET6)
	THROW(env, RuntimeException, "BUG [libnet6/anep/send] family != AF_INET6");

    /* Build IPv6 address */
    memcpy(ip_addr.s6_addr, address, V6_SIZE);
    
    memset(&rmtaddr, 0, sizeof(struct sockaddr_anep));
    rmtaddr.sanep_type          = htons((short)packetType);
    rmtaddr.sanep_family        = family;
    rmtaddr.sanep_ipv6_flowinfo = htonl((traffic_class << 24) | flow_label);;
    rmtaddr.sanep_ipv6_addr	= ip_addr;

    /* Deal with options */
    packetOptions = (*env)->GetObjectField(env, packet, ap_optionsID);
    if (!packetOptions)
	goto send_packet;

    /* Allocate the optbuff array */
    option_count = (*env)->GetArrayLength(env, packetOptions);
    optinfo = optbuff = (struct anep_optbuffer *) 
	malloc((option_count + 1) * sizeof(struct anep_optbuffer));
    if (!optbuff)
	THROW(env, OutOfMemoryError, "heap allocation failed");

    /* This is for sending */
    ANEP_OPTBUFFER_SET_SIDE(optinfo, ANEP_OPTBUFFER_SEND);
    optinfo++;

    /* Look at all the options */
    for (i = 0 ; i < option_count ; i++) {
	struct anep_optionhdr *opthdr;

	/* Retrive the ith option */
	option = (*env)->GetObjectArrayElement(env, packetOptions, i);
	
	/* Sanity check */
	if (!option) {
	    exc = NullPointerException;
	    msg = "null option";
	    goto free;
	}

	/* Retrieve buffer */
	optionBuffer = (*env)->GetObjectField(env, option, ao_payloadID);

	/* Skip it ? */
	/* We friendly handle the case were there is no payload in an option
	 * as being: "don't send this one".
	 */
	if (!optionBuffer)
	    continue;

	/* Retrieve offset, lenght, type and flag (verified in java) */
	optionBufferOffset = (*env)->GetIntField(env, option, ao_offsetID);
	optionBufferLen    = (*env)->GetIntField(env, option, ao_lengthID);
	optionType         = (*env)->GetIntField(env, option, ao_typeID);
	optionFlags        = (*env)->GetIntField(env, option, ao_flagsID);

	/* fill option buffer information and allocate buffer */
	buffsize = ANEP_OPTION_SIZE(optionBufferLen);
	opthdr = (struct anep_optionhdr *)malloc(buffsize);
	memset(opthdr, 0, buffsize);
	if (!opthdr) {
	    exc = OutOfMemoryError;
	    msg = "heap allocation failed";
	    goto free;
	}

	optinfo->type	= optionType;
	optinfo->length	= buffsize;
	optinfo->buff	= opthdr;
	
	/* fill buffer */
	opthdr->type	= ntohs((short)(optionType | optionFlags));
	opthdr->length	= ntohs((short)(buffsize >> 2));
	(*env)->GetByteArrayRegion(env, optionBuffer, 
				   optionBufferOffset, optionBufferLen, 
				   (jbyte *)(opthdr+1));

	/* next */
	optinfo++;
	optsize++;
    }

 send_packet:
    // ::TODO:: deal here with ANEPPacket.Flags
    if (packetFlags != java_net_ANEPPacket_USE_DEFAULT_FLAGS) {
	exc = RuntimeException;
	msg = "Tried to use a receive constructor, to remove the USE_DEFAULT_FLAGS limitation...";
	goto free;
    }

    /* register the option buffers into the kernel */
    if (JVM_SetSockOpt(fd, SOL_ANEP, ANEP_OPTBUFFER,
		       (char *) optbuff, 
		       (optsize+1) * sizeof(struct anep_optbuffer)) < 0) {
	exc = SocketException;
	msg = strerror(errno);
	goto free;
    }

    /* Allocate memory for the packet payload */
    if (packetBufferLen > MAX_BUFFER_LEN) {
#ifdef DISALLOW_JUMBOGRAM
	if (packetBufferLen > MAX_PACKET_LEN)
	    packetBufferLen = MAX_PACKET_LEN;
#endif
	fullPacket = (char *)malloc(packetBufferLen);
	if (!fullPacket) {
	    exc = OutOfMemoryError;
	    msg = "heap allocation failed";
	    goto free;
	}
    } else {
	fullPacket = buff;
    }
    
    /* fill buffer */
    (*env)->GetByteArrayRegion(env, packetBuffer, packetBufferOffset, 
			       packetBufferLen, (jbyte *)fullPacket);

    /* Send it */
    switch (JVM_SendTo(fd, fullPacket, packetBufferLen, 0,
		       (struct sockaddr *)&rmtaddr, sizeof(rmtaddr))) {
    case JVM_IO_ERR:
	exc = IOException;
	msg = strerror(errno);
	goto free;
    case JVM_IO_INTR:
	exc = InterruptedIOException;
	msg = "operation interrupted";
	goto free;
    }

 free:
    /* Free memmory */
    if (fullPacket != buff)
	free(fullPacket);

    if (optbuff != &clroptbuff) {
	while (optsize)
	    free(optbuff[optsize--].buff); /* the first is not a buufer */
	free(optbuff);
    }

    /* Exception? */
    if (exc)
	THROW(env, exc, msg);

    return;
}

/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    peek
 * Signature: (Ljava/net/InetAddress;)I
 */
JNIEXPORT jint JNICALL
Java_java_net_PlainANEPSocketImpl_peek(JNIEnv *env, jobject this,
					   jobject addressObj) {
    jobject	fdObj   = (*env)->GetObjectField(env, this, pasi_fdID);
    jint	timeout = (*env)->GetIntField(env, this, pasi_timeoutID);
    jint			fd;
    jbyteArray			a_address;
    jint			family;
    ssize_t			n;
    char			buf[1];
    struct sockaddr_anep	remote_addr;
    int				remote_addrsize = sizeof(struct sockaddr_anep);
    struct anep_optbuffer	clroptbuff	= ANEP_OPTBUFFER_STRUCT_RECV;
    
    /* Sanity check */
    if (!fdObj)
	THROW(env, SocketException, "Socket closed", -1);
    if (!addressObj)
	THROW(env, NullPointerException, "Null address in peek()", -1);

    /* Extract file descriptor and address information */
    fd		= (*env)->GetIntField(env, fdObj, fd_fdID);
    a_address	= (*env)->GetObjectField(env, addressObj, ia_addressID); 
    family	= (*env)->GetIntField(env, addressObj, ia_familyID);
    
    /* Wait with timeout (if timeout) */
    if (timeout) {
	int ret = JVM_Timeout(fd, timeout);
	switch(ret) {
	case 0:
	    THROW(env, InterruptedIOException, "Peek timed out", ret);
	case JVM_IO_ERR:
	    THROW(env, SocketException, 
		  (errno == EBADF) ? "Socket closed" : strerror(errno), ret);
	case JVM_IO_INTR:
	    /* WARNING: SHOULD WE REALLY RETURN -2??? */
	    THROW(env, InterruptedIOException, "operation interrupted", ret);
	}
    }

    /* Ensure there is no option buffers */
    if (JVM_SetSockOpt(fd, SOL_ANEP, ANEP_OPTBUFFER,
		       (char *)&clroptbuff, sizeof(struct anep_optbuffer)) < 0)
	THROW(env, SocketException, strerror(errno), 0);

    /* Peek at the ANEP packet */
    n = JVM_RecvFrom(fd, buf, 1, MSG_PEEK,
		     (struct sockaddr *)&remote_addr, &remote_addrsize);
    switch(n) {
    case JVM_IO_ERR:
	THROW(env, SocketException,
	      (errno == EBADF) ? "Socket closed" : strerror(errno),
	      0);
    case JVM_IO_INTR:
	THROW(env, InterruptedIOException, "", 0);
    }
    
    /* Store remote address */
    (*env)->SetByteArrayRegion(env, a_address, 0, V6_SIZE, 
			       remote_addr.sanep_ipv6_addr.s6_addr);
    if ((*env)->ExceptionOccurred(env))
	return -1;
    (*env)->SetIntField(env, addressObj, ia_familyID,
			remote_addr.sanep_family);

    /* return type */
    /*  there is not much choices left, it is necessary the one you were
     *  listening for ...
     */
    return ntohs(remote_addr.sanep_type);
}

/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    receive
 * Signature: (Ljava/net/ANEPPacket;)V
 */
#define __FUNC__ "PlainANEPSocketImpl_receive"
JNIEXPORT void JNICALL
Java_java_net_PlainANEPSocketImpl_receive(JNIEnv *env, jobject this,
					  jobject packet) {
    char			 buff[MAX_BUFFER_LEN];
    char			*fullPacket	= buff;
    struct anep_optbuffer	 clroptbuff	= ANEP_OPTBUFFER_STRUCT_RECV;
    struct sockaddr_anep	 remote_addr;
    int				 remote_addrsize= sizeof(struct sockaddr_anep);
    struct anep_optbuffer	*optbuff	= &clroptbuff;
    struct anep_optbuffer	*optinfo;
    int				 option_count	= 0;
    int				 i;
    int				 optsize	= 0;
    int				 buffsize;
    char			*exc		= NULL;
    char			*msg		= NULL;
    int fd;
    int n;

    jobject	fdObj     = (*env)->GetObjectField(env, this, pasi_fdID);
    jint	timeout   = (*env)->GetIntField(env, this, pasi_timeoutID);

    jclass	iaCls     = (*env)->FindClass(env, "java/net/InetAddress");
    jmethodID	iaCtorID  = (*env)->GetMethodID(env, iaCls, "<init>", "()V");

    jbyteArray			 a_address;

    jbyteArray			 packetBuffer;
    jobject			 packetAddress;
    jint			 packetBufferOffset;
    jint			 packetBufferLen;
    jobjectArray		 packetOptions;

    jobject			 option;
    jint			 optionType;
    jint			 optionBufferOffset;
    jint			 optionBufferLen;
    jbyteArray			 optionBuffer;

    /* Sanity check */
    if (!fdObj)
	THROW(env, SocketException, "Socket closed");
    if (!packet)
	THROW(env, NullPointerException, "packet");
    
    /* Retrieve */
    packetBuffer	= (*env)->GetObjectField(env, packet, ap_payloadID);

    /* Sanity check */
    if (!packetBuffer)
	THROW(env, NullPointerException, "packet buffer");

    /* Retrieve */
    fd			= (*env)->GetIntField(env, fdObj, fd_fdID);
    packetBufferOffset	= (*env)->GetIntField(env, packet, ap_offsetID);
    packetBufferLen	= (*env)->GetIntField(env, packet, ap_lengthID);

    /* Wait with timeout (if timeout) */
    if (timeout) {
	int ret = JVM_Timeout(fd, timeout);
	switch(ret) {
	case 0:
	    THROW(env, InterruptedIOException, "Receive timed out");
	case JVM_IO_ERR:
	    THROW(env, SocketException, "Socket closed");
	case JVM_IO_INTR:
	    THROW(env, InterruptedIOException, "operation interrupted");
	}
    }
    
    /* Deal with options */
    packetOptions = (*env)->GetObjectField(env, packet, ap_optionsID);
    if (!packetOptions)
	goto recv_packet;

    /* Allocate the optbuff array */
    option_count = (*env)->GetArrayLength(env, packetOptions);
    optinfo = optbuff = (struct anep_optbuffer *) 
	malloc((option_count + 1) * sizeof(struct anep_optbuffer));
    if (!optbuff)
	THROW(env, OutOfMemoryError, "heap allocation failed");

    /* This is for receiving */
    ANEP_OPTBUFFER_SET_SIDE(optinfo, ANEP_OPTBUFFER_RECV);
    optinfo++;

    /* Look at all the options */
    for (i = 0 ; i < option_count ; i++) {
	struct anep_optionhdr *opthdr;

	/* Retrive the ith option */
	option = (*env)->GetObjectArrayElement(env, packetOptions, i);
	
	/* Sanity check */
	if (!option) {
	    exc = NullPointerException;
	    msg = "null option";
	    goto free;
	}

	/* Retrieve buffer */
	optionBuffer = (*env)->GetObjectField(env, option, ao_payloadID);

	/* Retrieve length and type  (verified in java) */
	optionBufferLen    = (*env)->GetIntField(env, option, ao_lengthID);
	optionType         = (*env)->GetIntField(env, option, ao_typeID);
	
	/* If no buffer present, assume one still want to know if an option
	 * is present in the paquet. (this is more a sanity check)
	 */
	if (!optionBuffer)
	    optionBufferLen = 0;

	/* fill option buffer information and allocate buffer */
	buffsize = ANEP_OPTION_SIZE(optionBufferLen);
	opthdr = (struct anep_optionhdr *)malloc(buffsize);
	if (!opthdr) {
	    exc = OutOfMemoryError;
	    msg = "heap allocation failed";
	    goto free;
	}

	optinfo->type	= optionType;
	optinfo->length	= buffsize;
	optinfo->buff	= opthdr;

	
	/* fill buffer */
	opthdr->length	= htons((short)(buffsize >> 2));
	opthdr->type	= optionType == 0 ? ~0 : 0;

	/* next */
	optinfo++;
	optsize++;
    }


 recv_packet:
    /* register the option buffers into the kernel */
    if (JVM_SetSockOpt(fd, SOL_ANEP, ANEP_OPTBUFFER,
		       (char *) optbuff, 
		       (optsize+1) * sizeof(struct anep_optbuffer)) < 0) {
	exc = SocketException;
	msg = strerror(errno);
	goto free;
    }

    /* Allocate memory for receiving the packet payload */
    if (packetBufferLen > MAX_BUFFER_LEN) {
	// ::TODO:: doesn't work correctly with jumbogram
	if (packetBufferLen > MAX_PACKET_LEN) 
	    packetBufferLen = MAX_PACKET_LEN;

	fullPacket = (char *)malloc(packetBufferLen);
	if (!fullPacket) {
	    exc = OutOfMemoryError;
	    msg = "heap allocation failed";
	    goto free;
	}
    }

    /* Receive */
    n = JVM_RecvFrom(fd, fullPacket, packetBufferLen, 0,
		     (struct sockaddr *)&remote_addr, &remote_addrsize);
    if (n > packetBufferLen) /* OS security check ?!? */
	n = packetBufferLen;

    switch (n) {
    case JVM_IO_ERR:
	exc = SocketException;
	msg = "socket closed";
	goto free;
    case JVM_IO_INTR:
	exc = InterruptedIOException;
	msg = "operation interrupted";
	goto free;
    }

    /* Option again... */
    if (!packetOptions)
	goto fill_packet;

    /* Reset pointer to option buffers */
    optinfo = optbuff + 1;

    /* Look at all the options */
    for (i = 0 ; i < option_count ; i++) {
	struct anep_optionhdr	*opthdr	= optinfo->buff;
	int			 length	= ntohs(opthdr->length) << 2;
	int			 type	= ntohs(opthdr->type);
	int			 alen;

	/* Retrive the ith option */
	option = (*env)->GetObjectArrayElement(env, packetOptions, i);
	
	/* Sanity check */
	if (!option) {
	    exc = NullPointerException;
	    msg = "null option";
	    goto free;
	}

	/* Retrieve buffer */
	optionBuffer = (*env)->GetObjectField(env, option, ao_payloadID);

	/* Retrieve offset and length (verified in java) */
	optionBufferOffset = (*env)->GetIntField(env, option, ao_offsetID);
	optionBufferLen    = (*env)->GetIntField(env, option, ao_lengthID);
	optionType         = (*env)->GetIntField(env, option, ao_typeID);

	/* If no buffer present, assume one still want to know if an option
	 * is present in the paquet. (this is more a sanity check)
	 */
	if (!optionBuffer)
	    optionBufferLen = 0;

	/* Store option data in java */
	/* first we need to adjust the length, because the length
	 *  asked has been eventually roundup, so we need to send
	 *  no more that what has been asked.
	 */
	alen = MIN(optionBufferLen, length - sizeof(opthdr));

	/* If no buffer present, assume one still want to know if an option
	 * is present in the paquet.
	 */
	if (optionBuffer)
	    (*env)->SetByteArrayRegion(env, optionBuffer, 
				       optionBufferOffset, alen,
				       (jbyte *)(opthdr+1));

	(*env)->SetIntField(env, option, ao_lengthID, 
			    alen);
	(*env)->SetIntField(env, option, ao_flagsID, 
			    type & ANEP_OPT_FLAG_MASK);
	(*env)->SetBooleanField(env, option, ao_receivedID,
				(type & ANEP_OPT_TYPE_MASK) == optionType);

	/* delayed sanity check */
	if ((*env)->ExceptionOccurred(env))
	    goto free;

	/* next */
	optinfo++;
    }


 fill_packet:
    /* Create and fill and InetAddr */
    packetAddress = (*env)->NewObject(env, iaCls, iaCtorID);
    a_address	  = (*env)->GetObjectField(env, packetAddress, ia_addressID);
    (*env)->SetByteArrayRegion(env, a_address, 0, V6_SIZE, 
			       remote_addr.sanep_ipv6_addr.s6_addr);
    (*env)->SetIntField(env, packetAddress, ia_familyID,
			remote_addr.sanep_family);

    if ((*env)->ExceptionOccurred(env))
	return;

    /* stuff the new Inetaddress in the packet */
    (*env)->SetObjectField(env, packet, ap_addressID, packetAddress);

    /* and fill in the data, remote address/port and such */
    (*env)->SetByteArrayRegion(env, packetBuffer, packetBufferOffset, n,
			       (jbyte *)fullPacket);
    (*env)->SetIntField(env, packet, ap_typeID,
			ntohs(remote_addr.sanep_type));
    (*env)->SetIntField(env, packet, ap_lengthID, n);
    
    if ((*env)->ExceptionOccurred(env))
	return;
    

 free:
    /* Free memmory */
    if (fullPacket != buff)
	free(fullPacket);

    if (optbuff != &clroptbuff) {
	while (optsize) 
	    free(optbuff[optsize--].buff); /* the first is not a buufer */
	free(optbuff);
    }

    /* Exception? */
    if (exc)
	THROW(env, exc, msg);

    return;
}

/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    anepSocketCreate
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainANEPSocketImpl_anepSocketCreate(JNIEnv *env,
						   jobject this) {
    jobject	fdObj = (*env)->GetObjectField(env, this, pasi_fdID);
    int		fd;

    /* Sanity check */
    if (!fdObj)
	THROW(env, SocketException, "Socket closed");

    /* Create socket */
    fd =  JVM_Socket(AF_INET6, SOCK_DGRAM, SOL_ANEP);
    if (fd == JVM_IO_ERR)
	THROW(env, SocketException, strerror(errno));

    /* Store file descriptor */
    (*env)->SetIntField(env, fdObj, fd_fdID, fd);
}

/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    anepSocketClose
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainANEPSocketImpl_anepSocketClose(JNIEnv *env,
						  jobject this) {
    // ::TODO:: REMIND: PUT A LOCK AROUND THIS CODE
    jobject fdObj = (*env)->GetObjectField(env, this, pasi_fdID);
    int fd;

    /* Sanity check */
    if (!fdObj)
	THROW(env, SocketException, "Socket closed");

    /* Retrieve file descriptor */
    fd = (*env)->GetIntField(env, fdObj, fd_fdID);

    /* Close socket */
    JVM_Close(fd);

    /* Store no file descriptor */
    (*env)->SetIntField(env, fdObj, fd_fdID, -1);
}

/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    allowOptionTypes
 * Signature: ([I)V
 */ 
JNIEXPORT void JNICALL
Java_java_net_PlainANEPSocketImpl_allowOptionTypes(JNIEnv *env,
						   jobject this,
						   jintArray as_optype) {
    jobject fdObj = (*env)->GetObjectField(env, this, pasi_fdID);
    struct anep_optype		*optype	= NULL;
    struct anep_optype		*opt;
    jint			*types;
    char			*exc	= NULL;
    char			*msg	= NULL;
    int				 count	= 0;
    int				 fd;
    int				 i;

    /* Sanity check */
    if (!fdObj)
	THROW(env, SocketException, "Socket closed");

    /* Retrieve file descriptor */
    fd = (*env)->GetIntField(env, fdObj, fd_fdID);
    if (fd < 0)
	THROW(env, SocketException, "socket closed");

    /* Clear? */
    if (as_optype == NULL)
	goto set_optype;

    /* Fetch count / types array */
    types = (*env)->GetIntArrayElements(env, as_optype, NULL);
    if (!types)
	THROW(env, RuntimeException, "unable to fetch types data");
    count = (*env)->GetArrayLength(env, as_optype);

    /* Clear? */
    if (count == 0)
	goto set_optype;

    /* Allocate memory */
    opt = optype = (struct anep_optype *) malloc(count 
						 * sizeof(struct anep_optype));
    if (!optype)
	THROW(env, OutOfMemoryError, "heap allocation failed");

    /* Fill optype */
    for (i = 0 ; i < count ; i++) {
	opt[i].action                      = ANEP_OPTYPE_KNOWN;
	opt[i].beg_type = opt[i].end_type = types[i];
    }

 set_optype:
    if (JVM_SetSockOpt(fd, SOL_ANEP, ANEP_OPTYPE, 
		       (char *) optype, 
		       count * sizeof(struct anep_optype)) < 0) {
	exc = SocketException;
	msg = "Bad option type";
	goto free;
    }


 free:
    /* Free memory */
    if (optype)
	free(optype);

    /* Exception ? */
    if (exc)
	THROW(env, exc, msg);
}

/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    socketSetOption
 * Signature: (ILjava/lang/Object;)V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainANEPSocketImpl_socketSetOption(JNIEnv *env,
						  jobject this,
						  jint opt,
						  jobject value) {
    jobject fdObj = (*env)->GetObjectField(env, this, pasi_fdID);
    int fd;
    char        fullMsg[512];
    char *msg = NULL;
    int arg = (*env)->GetIntField(env, value, i_valueID); // assert

    /* Sanity check */
    if (!fdObj)
	THROW(env, SocketException, "Socket closed");
    if (!value)
	THROW(env, NullPointerException, "value argument");

    /* Retrieve file descriptor */
    fd = (*env)->GetIntField(env, fdObj, fd_fdID);
    if (fd < 0)
	THROW(env, SocketException, "socket closed");

    switch(opt) {
    case java_net_SocketOptions_ANEP_DISCARD:
	if (JVM_SetSockOpt(fd, SOL_ANEP, ANEP_DISCARD,
		       (char *)&arg, sizeof(int)) < 0)
	    THROW(env, SocketException, strerror(errno));
	(*env)->SetIntField(env, this, pasi_discardFlagID, arg);
	if ((*env)->ExceptionOccurred(env))
	    THROW(env, RuntimeException, "ANEP_DISCARD coherence lost");
	break;
	
    case java_net_SocketOptions_IPV6_MULTICAST_IF: 
	if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
		       (const char*)&arg, sizeof(arg)) < 0) 
	    THROW(env, SocketException, strerror(errno));
	break;

    case java_net_SocketOptions_SO_REUSEADDR: {
	arg = arg ? -1 : 0;
	if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&arg, sizeof(arg)) < 0) 
	    THROW(env, SocketException, strerror(errno));
	break;

    case java_net_SocketOptions_SO_SNDBUF:
	if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_SNDBUF,
		       (char *)&arg, sizeof(arg)) < 0) {
	    msg = "setsockopt() SO_SNDBUF";
	    goto throw;
	}
	break;

    case java_net_SocketOptions_SO_RCVBUF:
	if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_RCVBUF,
		       (char *)&arg, sizeof(arg)) < 0) {
	    msg = "setsockopt() SO_RCVBUF";
	    goto throw;
	}
	break;

    case java_net_SocketOptions_IPV6_AUTH_TRANS_LEVEL:
	if (JVM_SetSockOpt(fd, SOL_SOCKET, IPV6_AUTH_TRANS_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    msg = "setsockopt() IPV6_AUTH_TRANS_LEVEL";
	    goto throw;
	}
	break;

    case java_net_SocketOptions_IPV6_AUTH_NETWORK_LEVEL:
	if (JVM_SetSockOpt(fd, SOL_SOCKET, IPV6_AUTH_NETWORK_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    msg = "setsockopt() IPV6_AUTH_NETWORK_LEVEL";
	    goto throw;
	}
	break;

    case java_net_SocketOptions_IPV6_ESP_TRANS_LEVEL:
	if (JVM_SetSockOpt(fd, SOL_SOCKET, IPV6_ESP_TRANS_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    msg = "setsockopt() IPV6_ESP_TRANS_LEVEL";
	    goto throw;
	}
	break;

    case java_net_SocketOptions_IPV6_ESP_NETWORK_LEVEL:
	if (JVM_SetSockOpt(fd, SOL_SOCKET, IPV6_ESP_NETWORK_LEVEL,
		       (char *)&arg, sizeof(arg)) < 0) {
	    msg = "setsockopt() IPV6_ESP_NETWORK_LEVEL";
	    goto throw;
	}
	break;

    default:
	THROW(env, SocketException, "invalid ANEPSocket option");
    }

    return;

 throw:
    if (!msg)
        msg = "no further information";
 
    switch(errno) {
    case EBADF: 
        jio_snprintf(fullMsg, sizeof(fullMsg), "socket closed: %s", msg);
        THROW(env, SocketException, fullMsg);
        break;
    case EINTR:
        THROW(env, InterruptedIOException, msg);
    default:
        jio_snprintf(fullMsg, sizeof(fullMsg), "%s: %s", msg, strerror(errno));
        THROW(env, SocketException, fullMsg);
    }
    }
}

/*
 * Returns relevant info as a jint.
 *
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    socketGetOption
 * Signature: (I)I
 */
JNIEXPORT jbyteArray JNICALL
Java_java_net_PlainANEPSocketImpl_socketGetOption(JNIEnv *env, jobject this,
						  jint opt) {
    jbyteArray  jaddress;
    jobject     fdObj   = (*env)->GetObjectField(env, this, pasi_fdID);
    int         len     = sizeof(int);
    int         integer = -1;
    char       *msg     = NULL;
    int         fd;
    void       *src	= NULL;
    char        fullMsg[512];
    struct sockaddr_anep skaddr;
    
    if (!fdObj)
	THROW(env, SocketException, "Socket closed", NULL);

    fd = (*env)->GetIntField(env, fdObj, fd_fdID);
    
    if (fd < 0)
	THROW(env, SocketException, "socket closed", NULL);
    
    switch(opt) {
    case java_net_SocketOptions_ANEP_DISCARD:
	if (JVM_GetSockOpt(fd, SOL_ANEP, ANEP_DISCARD,
		       (char *)&integer, &len) < 0)
	    THROW(env, SocketException, strerror(errno), NULL);
	integer = htonl(integer);
	break;
	
    case java_net_SocketOptions_SO_BINDADDR: 
	len = sizeof(struct sockaddr_anep);
	memset(&skaddr, 0, len);
	if (JVM_GetSockName(fd, (struct sockaddr *)&skaddr, &len) < 0)
	    THROW(env, SocketException, strerror(errno), NULL);

	src = skaddr.sanep_ipv6_addr.s6_addr;
	len = V6_SIZE;
	break;

    case java_net_SocketOptions_IPV6_MULTICAST_IF: 
	if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
		       (char *)&integer, &len) < 0)
	    THROW(env, SocketException, strerror(errno), NULL);
	integer = htonl(integer);
	break;

    case java_net_SocketOptions_SO_REUSEADDR:
	if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&integer, &len) < 0) 
	    THROW(env, SocketException, strerror(errno), NULL);
	integer = htonl(integer);
	break;

    case java_net_SocketOptions_SO_SNDBUF: 
	if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_SNDBUF, (char *)&integer, &len) < 0) {
	    msg = "getsockopt() SO_SNDBUF";
	    goto throw;
	}
	integer = htonl(integer);
	break;

    case java_net_SocketOptions_SO_RCVBUF: 
	if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_RCVBUF, (char *)&integer, &len) < 0){
	    msg = "getsockopt() SO_RCVBUF";
	    goto throw;
	}
	integer = htonl(integer); 
	break;

    case java_net_SocketOptions_IPV6_AUTH_TRANS_LEVEL: 
	if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_AUTH_TRANS_LEVEL,
		       (char *)&integer, &len) < 0) {
	    msg = "getsockopt() IPV6_AUTH_TRANS_LEVEL";
	    goto throw;
	}
	integer = htonl(integer);    
	break;

    case java_net_SocketOptions_IPV6_AUTH_NETWORK_LEVEL: 
	if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_AUTH_NETWORK_LEVEL,
		       (char *)&integer, &len) < 0) {
	    msg = "getsockopt() IPV6_AUTH_NETWORK_LEVEL";
	    goto throw;
	}
	integer = htonl(integer);    
	break;

    case java_net_SocketOptions_IPV6_ESP_TRANS_LEVEL: 
	if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_ESP_TRANS_LEVEL,
		       (char *)&integer, &len) < 0) {
	    msg = "getsockopt() IPV6_ESP_TRANS_LEVEL";
	    goto throw;
	}
	integer = htonl(integer);    
	break;

    case java_net_SocketOptions_IPV6_ESP_NETWORK_LEVEL:
	if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_ESP_NETWORK_LEVEL,
		       (char *)&integer, &len) < 0) {
	    msg = "getsockopt() IPV6_ESP_NETWORK_LEVEL";
	    goto throw;
	}
	integer = htonl(integer);    
	break;

    default:
	THROW(env, SocketException,
	      "invalid or unsupported ANEPSocket option on this stack", NULL);
    }
    
    if (!src) {
	src = &integer;
    }

    jaddress = (*env)->NewByteArray(env, len);
    if (!jaddress)
	THROW(env, OutOfMemoryError, "Result allocation failed", NULL);
    (*env)->SetByteArrayRegion(env, jaddress, 0, len, (char *)src);
    return jaddress;

 throw:
    if (!msg)
        msg = "no further information";
 
    switch(errno) {
    case EBADF: 
        jio_snprintf(fullMsg, sizeof(fullMsg), "socket closed: %s", msg);
        THROW(env, SocketException, fullMsg, NULL);
        break;
    case EINTR:
        THROW(env, InterruptedIOException, msg, NULL);
    default:
        jio_snprintf(fullMsg, sizeof(fullMsg), "%s: %s", msg, strerror(errno));
        THROW(env, SocketException, fullMsg, NULL);
    }
}

/*
 * Multicast-related calls
 */

/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    setTTL
 * Signature: (B)V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainANEPSocketImpl_setHOPS(JNIEnv *env, jobject this,
						    jint ttl) {
    jobject fdObj = (*env)->GetObjectField(env, this, pasi_fdID);
    char    ittl  = (char)ttl;
    int     fd;
    
    if (!fdObj)
	THROW(env, SocketException, "Socket closed");

    fd = (*env)->GetIntField(env, fdObj, fd_fdID);

    /* setsockopt to be correct ttl */
    if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 
		   (char*)&ittl, sizeof(ittl)) < 0)
	THROW(env, SocketException, strerror(errno));
}


/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    getTTL
 * Signature: ()B
 */
JNIEXPORT jint JNICALL
Java_java_net_PlainANEPSocketImpl_getHOPS(JNIEnv *env, jobject this) {
    jobject fdObj = (*env)->GetObjectField(env, this, pasi_fdID);
    jint    fd    = -1;
    u_char  ttl   = 0;
    int     len   = sizeof(ttl);

    if (!fdObj)
	THROW(env, SocketException, "Socket closed", -1);

    fd = (*env)->GetIntField(env, fdObj, fd_fdID);

    /* getsockopt of ttl */
    if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 
		   (char*)&ttl, &len) < 0)
	THROW(env, "SocketException", strerror(errno), -1);
    
    return (jint)ttl;
}


/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    join
 * Signature: (Ljava/net/InetAddress;)V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainANEPSocketImpl_join(JNIEnv *env, jobject this,
					   jobject address) {
    jobject           fdObj = (*env)->GetObjectField(env, this, pasi_fdID);
    jbyteArray        jaddress;
    jbyte            *addressAddress;
    jint              fd;
    struct ipv6_mreq  mname;
    int               len = sizeof(int);
    int               in;
    
    if (!fdObj)
	THROW(env, SocketException, "Socket closed");

    if (!address)
	THROW(env, NullPointerException, "address");
    
    fd             = (*env)->GetIntField(env, fdObj, fd_fdID);
    jaddress       = (*env)->GetObjectField(env, address, ia_addressID); 
    addressAddress = (*env)->GetByteArrayElements(env, jaddress, NULL);
    
    if (!addressAddress)
	THROW(env, RuntimeException, "");
    
    /*
     * Set the multicast group address in the ip_mreq field
     * eventually this check should be done by the security manager
     */
    memset(&mname, 0, sizeof(struct ipv6_mreq));
    memcpy(&mname.ipv6mr_multiaddr.s6_addr[0], addressAddress, V6_SIZE);
    
    /* Set the interface */
    if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *)&in, &len) < 0)
	THROW(env, SocketException, strerror(errno));
    mname.ipv6mr_interface = in;
    
    /* Join the multicast group */
    if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, 
		   (char *)&mname, sizeof(struct ipv6_mreq)) < 0)
	THROW(env, SocketException, "error setting options");
}


/*
 * Class:     java_net_PlainANEPSocketImpl
 * Method:    leave
 * Signature: (Ljava/net/InetAddress;)V
 */
JNIEXPORT void JNICALL
Java_java_net_PlainANEPSocketImpl_leave(JNIEnv *env, jobject this,
					jobject address) {
    jobject           fdObj = (*env)->GetObjectField(env, this, pasi_fdID);
    jbyteArray        jaddress;
    jbyte            *addressAddress;
    jint              fd;
    struct ipv6_mreq  mname;
    int               len = sizeof(int);
    int               in;
    
    if (!fdObj)
	THROW(env, SocketException, "Socket closed");
    
    if (!address)
	THROW(env, NullPointerException, "address argument");
    
    fd             = (*env)->GetIntField(env, fdObj, fd_fdID);
    jaddress       = (*env)->GetObjectField(env, address, ia_addressID); 
    addressAddress = (*env)->GetByteArrayElements(env, jaddress, NULL);
    
    if (!addressAddress)
	THROW(env, RuntimeException, "");
    
    /*
     * Set the multicast group address in the ipv6_mreq field
     * eventually this check should be done by the security manager
     */
    memset(&mname, 0, sizeof(struct ipv6_mreq));
    memcpy(&mname.ipv6mr_multiaddr.s6_addr[0], addressAddress, V6_SIZE);
    
    /* Set the interface */
    if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *)&in, &len) < 0)
	THROW(env, SocketException, strerror(errno));
    mname.ipv6mr_interface = in;
    
    /* Join the multicast group */
    if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IP_DROP_MEMBERSHIP,
		   (char *)&mname, sizeof(struct ipv6_mreq)) < 0)
	THROW(env, SocketException, strerror(errno));
}
