/* -*- Mode: C; c-file-style: "gnu" -*-
   socket.c -- native methods for the plain socket implementation.
   Created: Chris Toshok <toshok@hungry.com>, 2-Aug-97
 */
/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "jni.h"
#include "exceptions.h"
#include "log.h"
#include "options.h"

#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <sys/types.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <fcntl.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <assert.h>

#ifdef HAVE_IO_H
#include <io.h>
#endif
#ifdef HAVE_WINSOCK2_H
#include <winsock2.h>
#endif

#include "compat.h"

#define MYLOG "Native"

/* These should be moved into one place, maybe a header file. */
const char *const IOException = "java/io/IOException";
const char *const BindException = "java/net/BindException";
const char *const SocketException = "java/net/SocketException";

const char *const FileDescriptor = "java/io/FileDescriptor";
const char *const InetAddress = "java/net/InetAddress";
const char *const SocketImpl = "java/net/SocketImpl";
const char *const DatagramSocketImpl = "java/net/DatagramSocketImpl";
const char *const DatagramPacket = "java/net/DatagramPacket";

/* Debug code */
static void
log_sockname(int sock_fd)
{
  struct sockaddr_in sock_addr;
  int addr_len = sizeof(sock_addr);
  int retval;
  memset(&sock_addr, 0, sizeof(sock_addr));
  retval = getsockname(sock_fd, (struct sockaddr*)&sock_addr, &addr_len);

  JAVARLOG5(MYLOG, 2, "%d = getsockname(%d, addr{addr=0x%08x port=0x%04x}, %d)\n",
	    retval, sock_fd, sock_addr.sin_addr.s_addr,
	    sock_addr.sin_port, sizeof(sock_addr));
}

/* Debug code */
static void
log_peername(int sock_fd)
{
  struct sockaddr_in sock_addr;
  int addr_len = sizeof(sock_addr);
  int retval;
  memset(&sock_addr, 0, sizeof(sock_addr));
  retval = getpeername(sock_fd, (struct sockaddr*)&sock_addr, &addr_len);

  JAVARLOG5(MYLOG, 2, "%d = getpeername(%d, addr{addr=0x%08x port=0x%04x}, %d)\n",
	    retval, sock_fd, sock_addr.sin_addr.s_addr,
	    sock_addr.sin_port, sizeof(sock_addr));
}

/*
 * Return classname.fd.fd when classname.fd is java.io.FileDescriptor
 */
static jint
get_object_fd(JNIEnv *env, const jobject obj, const char *classname)
{
  jobject file_desc;
  jfieldID field;
  jclass sock_class, fd_class;

  /* first we need to get the FileDescriptor object. */
  sock_class = (*env)->FindClass(env, classname);
  field = (*env)->GetFieldID(env, sock_class, "fd", "Ljava/io/FileDescriptor;");
  file_desc = (*env)->GetObjectField(env, obj, field);

  /* now we get the fd field of *that* object, and return it. */
  fd_class = (*env)->FindClass(env, FileDescriptor);
  field = (*env)->GetFieldID(env, fd_class, "fd", "I");

  return (*env)->GetIntField(env, file_desc, field);
}

/*
 * Return the value of classname.fieldname when it is integer
 */
static jint
get_object_int(JNIEnv *env, jobject obj,
	       const char *classname, const char *fieldname)
{
  jfieldID field;
  jclass sock_class;

  sock_class = (*env)->FindClass(env, classname);
  assert(NULL != sock_class);

  field = (*env)->GetFieldID(env, sock_class, fieldname, "I");
  if (NULL == field)
    {
      fprintf(stderr, "Unable to find int field %s.%s\n",
	      classname, fieldname);
      assert(NULL != field);
    }

  return (*env)->GetIntField(env, obj, field);
}

/*
 * Store value in classname.fieldname
 */
static void
set_object_int(JNIEnv *env, jobject obj,
	       const char *classname, const char *fieldname, const jint value)
{
  jfieldID field;
  jclass obj_class;

  obj_class = (*env)->FindClass(env, classname);
  assert(NULL != obj_class);
  field = (*env)->GetFieldID(env, obj_class, fieldname, "I");
  assert(NULL != field);
  (*env)->SetIntField(env, obj, field, value);
}

/*
 * Return the object(classname).address object
 * If it doesn't exist, create it and store it in object first.
 */
static jobject
get_object_inetaddress(JNIEnv *env, jobject object, const char *classname)
{
  jobject addr_obj;
  jclass packet_class = (*env)->FindClass(env, classname);
  jfieldID address_obj_field = (*env)->GetFieldID(env, packet_class, "address",
						  "Ljava/net/InetAddress;");
  assert(NULL != address_obj_field);
  assert(NULL != object);
  addr_obj = (*env)->GetObjectField(env, object, address_obj_field);

  if (NULL == addr_obj)
    { /* address == null, create one */
      jclass inetaddress_class = (*env)->FindClass(env, InetAddress);
      jmethodID ctor = (*env)->GetMethodID(env, inetaddress_class,
					   "<init>", "()V");
      JAVARLOG1(MYLOG, 5, "get_object_inetaddress(..., \"%s\") "
		"address == null, creating\n", classname);
      addr_obj = (*env)->NewObject(env, inetaddress_class, ctor, NULL);
      (*env)->SetObjectField(env, object, address_obj_field, addr_obj);
    }

  return addr_obj;
}

/*
 * Fills in InetAddress address and family.
 * address is given in host order
 */
static void
set_inetaddress(JNIEnv *env, jobject address_obj, jint address)
{
  JAVARLOG1(MYLOG, 3, "set_inetaddress(.., addr=0x%08x[host order]);\n",
	    address);
  set_object_int(env, address_obj, InetAddress, "address", address);
}

static void
set_object_fd(JNIEnv *env, jobject obj, int sock_fd, const char *classname)
{
  jobject sock_class;
  jobject file_desc;
  jclass fd_class;
  jfieldID field;

  /* first we need to get the FileDescriptor object. */
  sock_class = (*env)->FindClass(env, classname);
  field = (*env)->GetFieldID(env, sock_class, "fd", "Ljava/io/FileDescriptor;");
  file_desc = (*env)->GetObjectField(env, obj, field);

  /* now we get the fd field of *that* object, and return it. */
  fd_class = (*env)->FindClass(env, FileDescriptor);
  field = (*env)->GetFieldID(env, fd_class, "fd", "I");
  (*env)->SetIntField(env, file_desc, field, sock_fd);
}

static void
fill_in_sockaddr(JNIEnv *env,
		 jobject address,
		 int port,
		 struct sockaddr_in *sock_addr)
{
  jclass address_class = (*env)->FindClass(env, InetAddress);
  jmethodID getAddress = (*env)->GetMethodID(env, address_class, "getAddress", "()[B");
  jbyteArray addr;
  jbyte *addr_bytes;

  memset(sock_addr, 0, sizeof(*sock_addr));

  assert(NULL != getAddress);

  addr = (*env)->CallObjectMethod(env, address, getAddress);

  addr_bytes = (*env)->GetByteArrayElements(env, addr, NULL);

  /* uhh... this works because:
     1: ints are 32 bytes.
     2: the address is returned in network order from getAddress
     3: we're lucky. :)
  */
  memcpy(&sock_addr->sin_addr.s_addr, addr_bytes, 4); 

#ifdef HAVE_SIN_LEN
  sock_addr->sin_len = sizeof(struct sockaddr_in);
#endif
  /* XXX Should probably get family from InetAddress.family */
  sock_addr->sin_family = AF_INET;
  sock_addr->sin_port = htons(port);

  (*env)->ReleaseByteArrayElements(env, addr, addr_bytes, 0);

  JAVARLOG2(MYLOG, 2, "making address bytes=0x%08x "
	    "port=0x%04x network order\n",
	    sock_addr->sin_addr.s_addr, sock_addr->sin_port);
}

/* Return local port */
static jint
socket_bind(JNIEnv *env,
	    int sock_fd,
	    jobject address,
	    jint port)
{
  struct sockaddr_in sock_addr;
  int res = -1;

  fill_in_sockaddr(env, address, port, &sock_addr);

  res = bind(sock_fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr));
  JAVARLOG5(MYLOG, 2, "%d = bind(%d, addr{addr=0x%08x port=0x%04x}, %d)\n",
	    res, sock_fd,
	    sock_addr.sin_addr.s_addr, sock_addr.sin_port, sizeof(sock_addr));

  log_sockname(sock_fd);
  log_peername(sock_fd);

  if (res == -1)
    {
      char buffer[100];
      snprintf(buffer, 99, "bind(%d, (sin_addr.s_addr=0x%x, sin_port=%d),..",
	       sock_fd, sock_addr.sin_addr.s_addr, port);
      switch (errno) {
#ifdef ENOTSOCK
      case ENOTSOCK:
#endif
      case EBADF:
	strcat(buffer, " failed, bad sockfd.");
	throw_Exception(env, BindException, buffer);
	return 0;
	break;
#if defined(EACCESS)
      case EACCESS: /* Unknown on Linux */
#endif
      case EINVAL:
	strcat(buffer, " failed, invalid addr.");
	throw_Exception(env, BindException, buffer);
	return 0;
	break;
      case ENOENT:
      default:
	JAVARLOG1(MYLOG, 2, "bind() failed, errno=0x%x\n",errno);
	strcat(buffer, " failed, unknown error");
	throw_Exception(env, BindException, buffer);
	return 0;
	break;
      }
    }
  {
    int addr_len = sizeof(sock_addr);
    getsockname(sock_fd, (struct sockaddr*)&sock_addr, &addr_len);
  }
  return (jint)ntohs(sock_addr.sin_port);
}

/*
 * Return address_object(InetAddress).address in host order
 */
static jint
get_inetaddress(JNIEnv *env, jobject address_obj)
{
  jclass address_class = (*env)->FindClass(env, InetAddress);
  jfieldID add_field = (*env)->GetFieldID(env, address_class, "address", "I");

  return (*env)->GetIntField(env, address_obj, add_field);
}

/* return value using ((java.lang.Integer) value).intValue() */
static jint
getIntegerObjValue(JNIEnv *env, jobject value)
{
  jclass cls = (*env)->GetObjectClass(env, value);
  jmethodID intValue = (*env)->GetMethodID(env, cls, "intValue", "()I");
  assert(NULL != cls);
  assert(NULL != intValue);
  return (*env)->CallIntMethod(env, value, intValue);
}

static jint
get_so_reuseaddr(JNIEnv *env, int sock_fd)
{
  int value;
  int value_size = sizeof(value);
  int retval;
  retval = getsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
		      &value, &value_size);
  if (0 > retval || value_size != sizeof(value))
    {
      throw_Exception(env, SocketException,
		      "Unable to get socket option SO_REUSEADDR");
      return -1;
    }
  return value;
}

static void
set_so_reuseaddr(JNIEnv *env, int sock_fd, jint value)
{
  int retval;

  retval = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
		      (char*)&value, sizeof(value));
  if (0 > retval)
    throw_Exception(env, SocketException,
		    "Unable to set socket option SO_REUSEADDR");
}

static jint
get_so_bindaddr(JNIEnv *env, int sock_fd)
{
  struct sockaddr_in name;
  int namelen;
  int retval = getsockname(sock_fd, (struct sockaddr*)&name, &namelen);

  if (0 > retval || namelen != sizeof(struct sockaddr_in))
    {
      throw_Exception(env, SocketException,
		      "Error getting bind address for socket.");
      return -1;
    }

  /* XXX What is the correct byte order of this value? [pere] */
  return ntohl(name.sin_addr.s_addr);
}

static jint
get_so_ip_multicast_if(JNIEnv *env, int sock_fd)
{
#if defined(IP_MULTICAST_IF)
  struct sockaddr_in addr;
  int addr_size = sizeof(struct sockaddr_in);
  int retval;

  retval = getsockopt(sock_fd, IPPROTO_IP, IP_MULTICAST_IF,
		      &addr, &addr_size);
  if (0 > retval || addr_size != sizeof(addr))
    {
      throw_Exception(env, SocketException,
		      "Unable to get socket option IP_MULTICAST_IF");
      return -1;
    }
  return ntohl(addr.sin_addr.s_addr);
#else
  throw_Exception(env, SocketException,
		  "Unimplemented socket option IP_MULTICAST_IF");
  return -1;
#endif
}

static void
set_so_ip_multicast_if(JNIEnv *env, int sock_fd, jobject value)
{
#if defined(IP_MULTICAST_IF)
  struct sockaddr_in addr;
  int retval;

  fill_in_sockaddr(env, value, 0 /* port */, &addr);
  retval = setsockopt(sock_fd, IPPROTO_IP, IP_MULTICAST_IF,
		      &addr, sizeof(addr));
  if (0 > retval)
    throw_Exception(env, SocketException,
		    "Unable to set socket option IP_MULTICAST_IF");
#else
  throw_Exception(env, SocketException,
		  "Unimplemented socket option IP_MULTICAST_IF");
#endif
}

static jint
get_so_linger(JNIEnv *env, int sock_fd)
{
  struct linger linger;
  int linger_length = sizeof(struct linger);
  int retval;
		
  retval = getsockopt(sock_fd, SOL_SOCKET, SO_LINGER,
		      (char*)&linger, &linger_length);
		
  if (0 > retval || linger_length != sizeof(linger))
    {
      throw_Exception(env, SocketException,
		      "Unable to get socket option SO_LINGER");
      return -1;
    }
  return linger.l_linger;
}

static void
set_so_linger(JNIEnv *env, int sock_fd,
	      jint centisecond)  /* in 1/100 second */
{
  struct linger linger;
  int retval;

  linger.l_onoff = ( 0 > centisecond ? 1 /* ON */ : 0 /* OFF */);
  linger.l_linger = centisecond;

  retval = setsockopt(sock_fd, SOL_SOCKET, SO_LINGER,
		      (char*)&linger, sizeof(struct linger));
  if (0 > retval)
    throw_Exception(env, SocketException,
		    "Unable to set socket option SO_LINGER");
}

static jint
get_so_timeout(JNIEnv *env, int sock_fd)
{
#if defined( SO_RCVTIMEO )
  jint timeout;
  int timeout_length = sizeof(timeout);
  int retval;

  /* choose either the read timeout or the write timeout.  we assume
     they're both set to the same thing. */
  retval = getsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO,
		      (char *)&timeout, &timeout_length);
  if (0 > retval || timeout_length != sizeof(timeout))
    {
      throw_Exception(env, SocketException,
		      "Unable to get socket option SO_RCVTIMEO");
      return -1;
    }
  return timeout;
#else
#warning "Missing SO_RCVTIMEO!"
  throw_Exception(env, SocketException,
		  "Unimplemented socket option SO_RCVTIMEO");
  return -1;
#endif
}

static void
set_so_timeout(JNIEnv *env, int sock_fd, jint timeout)
{
#if defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO)
  int retval;

  retval = setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO,
		      (char *)&timeout, sizeof(timeout));
  if (0 > retval)
    throw_Exception(env, SocketException,
		    "Unable to set socket option SO_RCVTIMEO");

  retval = setsockopt(sock_fd, SOL_SOCKET, SO_SNDTIMEO,
		      (char *)&timeout, sizeof(timeout));
  if (0 > retval)
    throw_Exception(env, SocketException,
		    "Unable to set socket option SO_SNDTIMEO");
#else
#warning "Missing SO_RCVTIMEO or SO_SNDTIMEO!"
  throw_Exception(env, SocketException,
		  "Unimplemented socket option RCVTIMEO");
#endif
}

/***  java.net.PlainDatagramSocketImpl  ***/

/*
 * The PlainDatagramSocketImpl native stub implementation was mostly done
 * by Petter with almost no previous socket layer programming experience.
 * There are probably lots of improvments and optimizing to do.
 */

JNIEXPORT void JNICALL
Java_java_net_PlainDatagramSocketImpl_bind(JNIEnv *env,
					   jobject obj,
					   jint lport,
					   jobject laddr)
{
  int sock_fd = (int)get_object_fd(env, obj, DatagramSocketImpl);

  jint port = socket_bind(env, sock_fd, laddr, lport);
  set_object_int(env, obj, DatagramSocketImpl, "localPort", port);
}

JNIEXPORT void JNICALL
Java_java_net_PlainDatagramSocketImpl_send(JNIEnv *env,
					   jobject obj,
					   jobject packet)
{
  struct sockaddr_in sock_addr;
  size_t addr_len = sizeof(sock_addr);
  int sock_fd = get_object_fd(env, obj, DatagramSocketImpl);
  int retval = -1;
  jobject packet_class = (*env)->GetObjectClass(env, packet);
  jfieldID length_field = (*env)->GetFieldID(env, packet_class, "length", "I");
  jfieldID buf_field = (*env)->GetFieldID(env, packet_class, "buf", "[B");
  jbyte *buf = NULL;
  jint length;

  assert(NULL != packet_class);
  assert(NULL != length_field);
  assert(NULL != buf_field);

  length = (*env)->GetIntField(env, packet, length_field);

  { /* set addr from packet->address->address and port */
    jint port = get_object_int(env, packet, DatagramPacket, "port");
    jobject address_obj = get_object_inetaddress(env, packet, DatagramPacket);

    fill_in_sockaddr(env, address_obj, (int)port, &sock_addr);
  }

  { /* Dig out buffer from packet */
    jbyteArray buf_array = (*env)->GetObjectField(env, packet, buf_field);
    assert(NULL != buf_array);
    buf = (*env)->GetByteArrayElements(env, buf_array, NULL);
    assert(NULL != buf);

    JAVARLOG6(MYLOG, 2, "sendto(%d, 0x%x, %d, 0, addr{addr=0x%08x port=0x%04x}, %d);\n",
	      sock_fd, buf, length,
	      sock_addr.sin_addr.s_addr, sock_addr.sin_port, addr_len);

    retval = sendto(sock_fd, (const char*)buf, (int)length, 0,
		    (const struct sockaddr *)&sock_addr, addr_len);

    JAVARLOG7(MYLOG, 2, "%d = sendto(%d, 0x%x, %d, 0, addr{addr=0x%08x port=0x%04x}, %d);\n",
	      retval, sock_fd, buf, length,
	      sock_addr.sin_addr.s_addr, sock_addr.sin_port, addr_len);

    log_sockname(sock_fd);

    /* Nothing should have been written to the buffer, so I throw it away. */
    (*env)->ReleaseByteArrayElements(env, buf_array, buf, JNI_ABORT);
  }

  if (0 > retval) {
    JAVARLOG2(MYLOG, 2, "Error in sendto, errno=%d (%s)\n",
	      errno, strerror(errno));
    throw_Exception(env, SocketException, "sendto failed");
    return;
  }

}

JNIEXPORT jint JNICALL
Java_java_net_PlainDatagramSocketImpl_peek(JNIEnv *env,
					   jobject obj,
					   jobject address)
{
  int sock_fd = get_object_fd(env, obj, DatagramSocketImpl);
  struct sockaddr_in sock_addr;
  size_t addr_len = sizeof(sock_addr);
  int retval = -1;

  retval = recvfrom(sock_fd, 0, 0, MSG_PEEK,
		    (struct sockaddr*)&sock_addr, &addr_len);
  if (0 > retval)
    {
      throw_Exception(env, SocketException,
		      "recvfrom(.., MSG_PEEK) failed");
      return 0;
    }

  set_inetaddress(env, address, ntohl(sock_addr.sin_addr.s_addr));

  return retval;
}

JNIEXPORT void JNICALL
Java_java_net_PlainDatagramSocketImpl_receive(JNIEnv *env,
					      jobject obj,
					      jobject packet)
{
  int sock_fd = (int)get_object_fd(env, obj, DatagramSocketImpl);
  struct sockaddr_in sock_addr;
  size_t addr_len = sizeof(sock_addr);
  jclass packet_class = (*env)->FindClass(env, DatagramPacket);
  jfieldID buf_field = (*env)->GetFieldID(env, packet_class, "buf", "[B");
  jbyte *buf = NULL;
  int retval = -1;

  assert(NULL != packet_class);

  /* Dig out buffer from packet */
  {
    jint length =
      get_object_int(env, packet, DatagramPacket, "length");
    jbyteArray buf_array = (*env)->GetObjectField(env, packet, buf_field);
    buf = (*env)->GetByteArrayElements(env, buf_array, NULL);

    memset(&sock_addr, 0, sizeof(sock_addr));

    JAVARLOG6(MYLOG, 3,
	      "recvfrom(fd=%d, buf=0x%d, len=%d, 0, addr{addr=0x%08x port=0x%04x}, %d);\n",
	      sock_fd, buf, length,
	      sock_addr.sin_addr.s_addr, sock_addr.sin_port, addr_len);
  
    retval = recvfrom(sock_fd, (char*)buf, (int)length, 0,
		      (struct sockaddr*)&sock_addr, &addr_len);
    
    JAVARLOG7(MYLOG, 3,
	      "%d = recvfrom(fd=%d, buf=0x%d, len=%d, 0, addr{addr=0x%08x port=0x%04x}, %d);\n",
	      retval, sock_fd, buf, length,
	      sock_addr.sin_addr.s_addr, (int)sock_addr.sin_port, addr_len);

    (*env)->ReleaseByteArrayElements(env, buf_array, buf, 0);
  }

  if (0 > retval)
    {
      throw_Exception(env, SocketException, "recvfrom failed");
      return;
    }

  set_object_int(env, packet, DatagramPacket, "length", (jint)retval);
  set_object_int(env, packet, DatagramPacket, "port",
		 (jint)ntohs((int)sock_addr.sin_port));

  { /* set packet->address->address from addr.sin_addr.s_addr */
    jobject address =
      get_object_inetaddress(env, packet, DatagramPacket);
    set_inetaddress(env, address, ntohl(sock_addr.sin_addr.s_addr));
  }
}

JNIEXPORT void JNICALL
Java_java_net_PlainDatagramSocketImpl_setTTL(JNIEnv *env,
					     jobject obj,
					     jbyte ttl)
{
#ifdef IP_TTL
  int sock_fd = (int)get_object_fd(env, obj, DatagramSocketImpl);
  int ittl = (int)ttl;
  int retval;
  /* Hm, Kaffe 1.0 uses IP_MULTICAST_TTL [pere] */
  retval = setsockopt(sock_fd, IPPROTO_IP, IP_TTL, (char*)&ittl, sizeof(int));
  if (0 > retval)
#endif
    throw_Exception(env, SocketException,
		    "Unable to set socket option IP_TTL");
}

JNIEXPORT jbyte JNICALL
Java_java_net_PlainDatagramSocketImpl_getTTL(JNIEnv *env,
					     jobject obj)
{
#ifdef IP_TTL
  int sock_fd = (int)get_object_fd(env, obj, DatagramSocketImpl);
  int ttl = 0;
  /*
   * Solaris 2.5, Irix 6.2 and Linux RedHat 5.0 have int* as 5. param
   * to getsockopt(), not size_t*.
   */
  int len = sizeof(ttl);
  int retval;

  retval = getsockopt(sock_fd, IPPROTO_IP, IP_TTL, (char*)&ttl, &len);
  if (0 > retval)
    {
      throw_Exception(env, SocketException,
		      "Unable to get socket option IP_TTL");
      return -1;
    }
  return (jbyte)ttl;
#else
  throw_Exception(env, SocketException,
		  "Unable to get socket option IP_TTL");
  return -1;
#endif
}

JNIEXPORT void JNICALL
Java_java_net_PlainDatagramSocketImpl_join(JNIEnv *env,
					   jobject obj,
					   jobject multicast_addr)
{
#ifdef IP_ADD_MEMBERSHIP
  int sock_fd = (int)get_object_fd(env, obj, DatagramSocketImpl);
  struct ip_mreq mreq;
  int retval;

  mreq.imr_multiaddr.s_addr = htonl(get_inetaddress(env, multicast_addr));
  mreq.imr_interface.s_addr = htonl(INADDR_ANY);

  retval = setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
		      (char *)&mreq, sizeof(mreq));
  if (0 > retval)
#endif
    throw_Exception(env, SocketException,
		    "Unable to get socket option IP_ADD_MEMBERSHIP");
}

JNIEXPORT void JNICALL
Java_java_net_PlainDatagramSocketImpl_leave(JNIEnv *env,
					    jobject obj,
					    jobject multicast_addr)
{
#ifdef IP_DROP_MEMBERSHIP
  int sock_fd = (int)get_object_fd(env, obj, DatagramSocketImpl);
  struct ip_mreq mreq;
  int retval;

  mreq.imr_multiaddr.s_addr = htonl(get_inetaddress(env, multicast_addr));
  mreq.imr_interface.s_addr = htonl(INADDR_ANY);

  retval = setsockopt(sock_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
		      (char*)&mreq, sizeof(mreq));
  if (0 > retval)
#endif
    throw_Exception(env, SocketException,
		    "Unable to get socket option IP_DROP_MEMBERSHIP");
}

JNIEXPORT void JNICALL
Java_java_net_PlainDatagramSocketImpl_datagramSocketCreate(JNIEnv *env,
							   jobject obj)
{
  int sock_fd;

  sock_fd = socket(PF_INET, SOCK_DGRAM, 0);

  if (sock_fd == -1)
    {
      throw_Exception(env, IOException,
		      "datagram socket creation failed.");
    }

  /* we need to set all the system fd's to non blocking. */
  /* toshok - actually, we do want them blocking, since the thread
     library is the one that's supposed to take care of blocking
     threads instead of process.  We're not supposed to care about this. */
  /*  fcntl(sock_fd, F_SETFL, O_NONBLOCK);*/

  set_object_fd(env, obj, sock_fd, DatagramSocketImpl);
}

JNIEXPORT void JNICALL
Java_java_net_PlainDatagramSocketImpl_datagramSocketClose(JNIEnv *env,
							  jobject obj)
{
  int sock_fd = (int)get_object_fd(env, obj, DatagramSocketImpl);

  close(sock_fd);
}

JNIEXPORT void JNICALL
Java_java_net_PlainDatagramSocketImpl_socketSetOption(JNIEnv *env,
						      jobject obj,
						      jint opt,
						      jobject value)
{
  int sock_fd = (int)get_object_fd(env, obj, DatagramSocketImpl);

  JAVARLOG2(MYLOG, 2, "socketSetOption(.., opt=%d, value=0x%x)\n", opt, value);

  /* XXX I'm not sure which options are in use. */
  switch (opt)
    {
    case Socket_SO_REUSEADDR:
      set_so_reuseaddr(env, sock_fd, getIntegerObjValue(env, value));
      break;
    case Socket_IP_MULTICAST_IF:
      set_so_ip_multicast_if(env, sock_fd, value);
      break;
    case Socket_SO_LINGER:
      /* Hm, how are one supposed to know when to turn this off ? */
      set_so_linger(env, sock_fd, getIntegerObjValue(env, value));
      break;
    case Socket_SO_TIMEOUT:
      set_so_timeout(env, sock_fd, getIntegerObjValue(env, value));
      break;
    default:
      throw_Exception(env, SocketException,
		      "Invalid option sent to socketSetOption\n");
      break;
    }
}

JNIEXPORT jint JNICALL
Java_java_net_PlainDatagramSocketImpl_socketGetOption(JNIEnv *env,
						      jobject obj,
						      jint opt)
{
  int sock_fd = (int)get_object_fd(env, obj, DatagramSocketImpl);
  
  switch (opt)
    {
    case Socket_SO_REUSEADDR:
      return get_so_reuseaddr(env, sock_fd);
      break;
    case Socket_SO_BINDADDR:
      return get_so_bindaddr(env, sock_fd);
      break;
    case Socket_SO_LINGER:
      return get_so_linger(env, sock_fd);
      break;
    case Socket_IP_MULTICAST_IF:
      return get_so_ip_multicast_if(env, sock_fd);
      break;
    case Socket_SO_TIMEOUT:
      return get_so_timeout(env, sock_fd);
      break;
    default:
      throw_Exception(env, SocketException,
		      "Invalid option sent to socketGetOption\n");
      return -1;
      break;
    }
}

#ifndef JDK1_1
JNIEXPORT jint JNICALL
Java_java_net_PlainDatagramSocketImpl_getTimeToLive(JNIEnv *env,
						    jobject obj)
{
  (*env)->FatalError(env, "Java_java_net_PlainDatagramSocketImpl_getTimeToLive not implemented");
  return -1; /* XXX */
}

JNIEXPORT void JNICALL
Java_java_net_PlainDatagramSocketImpl_init(JNIEnv *env,
					   jclass cls)
{
  (*env)->FatalError(env, "Java_java_net_PlainDatagramSocketImpl_init not implemented");
}

JNIEXPORT void JNICALL
Java_java_net_PlainDatagramSocketImpl_setTimeToLive(JNIEnv *env,
						    jobject obj,
						    jint ttl)
{
  (*env)->FatalError(env, "Java_java_net_PlainDatagramSocketImpl_setTimeToLive not implemented");
}
#endif

/***  java.net.PlainSocketImpl  ***/

JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketCreate(JNIEnv *env,
					   jobject obj,
					   jboolean isServer /* what's this do? */)
{
  int sock_fd;

  sock_fd = socket(PF_INET, SOCK_STREAM, 0);

  JAVARLOG1(MYLOG, 2, "%d = socket(PF_INET, SOCK_STREAM, 0);\n", sock_fd);
  log_sockname(sock_fd);
  log_peername(sock_fd);

  if (sock_fd == -1)
    {
      throw_Exception(env, IOException, "socket creation failed.");
    }

  /* we need to set all the system fd's to non blocking. */
  /* toshok - actually, we do want them blocking, since the thread
     library is the one that's supposed to take care of blocking
     threads instead of process.  We're not supposed to care about this. */
  /*  fcntl(sock_fd, F_SETFL, O_NONBLOCK);*/

  set_object_fd(env, obj, sock_fd, SocketImpl);
}

JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketConnect(JNIEnv *env,
					    jobject obj,
					    jobject address,
					    jint port)
{
  struct sockaddr_in sock_addr;
  int sock_fd = get_object_fd(env, obj, SocketImpl);
  int retval = -1;

  fill_in_sockaddr(env, address, port, &sock_addr);

  JAVARLOG4(MYLOG, 3, "connect(%d, addr{addr=0x%08x port=0x%04x}, %d);\n",
	    sock_fd, sock_addr.sin_addr.s_addr, sock_addr.sin_port,
	    sizeof(sock_addr));

  retval = connect(sock_fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr));

  JAVARLOG5(MYLOG, 3, "%d = connect(%d, addr{addr=0x%08x port=0x%04x}, %d);\n",
	    retval, sock_fd, sock_addr.sin_addr.s_addr, sock_addr.sin_port,
	    sizeof(sock_addr));

  if (retval == -1)
    {
      JAVARLOG2(MYLOG, 3, "connect failed, errno=%d (%s)\n",
		errno, strerror(errno));
      throw_Exception(env, IOException, "socket connect failed.");
    }

  log_sockname(sock_fd);
  log_peername(sock_fd);
}

JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketBind(JNIEnv *env,
					 jobject obj,
					 jobject address,
					 jint lport)
{
  int sock_fd = get_object_fd(env, obj, SocketImpl);

  jint port = socket_bind(env, sock_fd, address, lport);
  set_object_int(env, obj, SocketImpl, "localport", port);
}

JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketListen(JNIEnv *env,
					   jobject obj,
					   jint count)
{
  int sock_fd = get_object_fd(env, obj, SocketImpl);
  int retval = -1;
  JAVARLOG2(MYLOG, 3, "listen(%d, %d);\n", sock_fd, count);
  retval = listen(sock_fd, count);
  JAVARLOG3(MYLOG, 3, "%d = listen(%d, %d);\n", retval, sock_fd, count);
  if (retval == -1)
    {
      throw_Exception(env, IOException, "socket listen failed.");
      return;
    }
  log_sockname(sock_fd);
  log_peername(sock_fd);
}

JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketAccept(JNIEnv *env,
					   jobject obj,
					   jobject socket1)
{
  int sock_fd = get_object_fd(env, obj, SocketImpl);
  struct sockaddr_in addr;
  size_t addr_len = sizeof(addr);
  int retval = -1;

  JAVARLOG4(MYLOG, 3, "accept(fd=%d, port=0x%04x addr=0x%08x, len=%d\n",
	    sock_fd, (int)addr.sin_port, addr.sin_addr.s_addr, addr_len);

  retval = accept(sock_fd, (struct sockaddr*)&addr, &addr_len);

  JAVARLOG5(MYLOG, 3, "%d = accept(fd=%d, port=0x%04x addr=0x%08x, len=%d\n",
	    retval, sock_fd, (int)addr.sin_port, addr.sin_addr.s_addr, addr_len);

  if (0 > retval)
    {
      JAVARLOG2(MYLOG, 3, "accept failed, errno=%d (%s)\n",
		errno, strerror(errno));
      throw_Exception(env, IOException, "accept() failed");
      return;
    }
  set_object_fd(env, socket1, retval, SocketImpl);
  JAVARLOG5(MYLOG, 2, "%d = accept(fd=%d, port=0x%04x addr=0x%08x, len=%d\n",
	    retval, sock_fd, (int)addr.sin_port, addr.sin_addr.s_addr, addr_len);

  /* Fill socket with info */
  set_object_int(env, socket1, SocketImpl, "port",
		 ntohs((int)addr.sin_port));
  {
    jobject addr_obj = get_object_inetaddress(env, socket1,
					      SocketImpl);
    assert(sizeof(jint) == sizeof(addr.sin_addr.s_addr));
    set_inetaddress(env, addr_obj, ntohl(addr.sin_addr.s_addr));
  }
}

JNIEXPORT jint JNICALL
Java_java_net_PlainSocketImpl_socketAvailable(JNIEnv *env,
					      jobject obj)
{
  int sock_fd = get_object_fd(env, obj, SocketImpl);
  int retval = -1;
  int length = 0;
#if defined( HAVE_IOCTL ) && defined( FIONREAD )
  retval = ioctl(sock_fd, FIONREAD, &len);
  if (retval < 0) {
    throw_Exception(env, IOException, "ioctl(,FIONREAD,) failed");
  }
#else
  static struct timeval tm = {0,0};
  fd_set rd;
  FD_ZERO(&rd);
  FD_SET(sock_fd, &rd);
  retval = select(sock_fd+1, &rd, NULL, NULL, &tm);
  if (1 == retval)
    length = 1;
#endif
  return length;
}

JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketClose(JNIEnv *env,
					  jobject obj)
{
  int sock_fd = get_object_fd(env, obj, SocketImpl);

  close(sock_fd);
}

JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_initProto(JNIEnv *env,
					jobject obj)
{
  /* this doesn't do anything on unix.. at least, not that I *know* of */
}

JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketSetOption(JNIEnv *env,
					      jobject obj,
					      jint opt,
					      jboolean on,
					      jobject value)
{
  int sock_fd = get_object_fd(env, obj, SocketImpl);

  switch (opt)
    {
    case Socket_TCP_NODELAY:
      {
	int v;
	int retval;

	v = (on ? 1 : 0);
	JAVARLOG1(MYLOG, 1, "Setting socket option TCP_NODELAY %s\n",
		  (on ? "on" : "off" ));

	retval = setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY,
			    &v, sizeof(v));
	if (0 > retval)
	  throw_Exception(env, SocketException,
			  "Unable to set socket option SO_NODELAY");
      }
      break;
    case Socket_SO_REUSEADDR:
      set_so_reuseaddr(env, sock_fd, getIntegerObjValue(env, value));
      break;
    case Socket_IP_MULTICAST_IF:
      set_so_ip_multicast_if(env, sock_fd, value);
      break;
    case Socket_SO_LINGER:
      /* If the integer value is 0 when on is false, this if-test is a waste */
      if (on)
	set_so_linger(env, sock_fd, getIntegerObjValue(env, value) );
      else
	set_so_linger(env, sock_fd, 0 );
      break;
    case Socket_SO_TIMEOUT:
      set_so_timeout(env, sock_fd, getIntegerObjValue(env, value));
      break;
    default:
      throw_Exception(env, SocketException,
		      "Invalid option sent to socketSetOption\n");
      break;
    }
}

JNIEXPORT jint JNICALL
Java_java_net_PlainSocketImpl_socketGetOption(JNIEnv *env,
					      jobject obj,
					      jint opt)
{
  int sock_fd = get_object_fd(env, obj, SocketImpl);
  
  switch (opt)
    {
    case Socket_TCP_NODELAY:
      {
	int value;
	int value_size = sizeof(value);
	int retval;

	retval = getsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY,
			    &value, &value_size);

	if (0 > retval || value_size != sizeof(value))
	  {
	    throw_Exception(env, SocketException,
			    "Unable to get socket option SO_NODELAY");
	    return -1;
	  }
	else
	  return value;
      }
      break;
    case Socket_SO_REUSEADDR:
      return get_so_reuseaddr(env, sock_fd);
      break;
    case Socket_SO_BINDADDR:
      return get_so_bindaddr(env, sock_fd);
      break;
    case Socket_IP_MULTICAST_IF:
      return get_so_ip_multicast_if(env, sock_fd);
      break;
    case Socket_SO_LINGER:
      return get_so_linger(env, sock_fd);
      break;
    default:
      {
	throw_Exception(env, SocketException,
			"Invalid option sent to socketGetOption\n");
	return -1;
      }
    }
}
