/* Copyright (C) 2003  Free Software Foundation

   This file is part of libgcj.

This software is copyrighted work licensed under the terms of the
Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
details.  */

#include <config.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <string.h>
#include <errno.h>

#include <sys/param.h>
#include <sys/types.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif

#include <gcj/cni.h>
#include <jvm.h>
#include <java/net/InetAddress.h>
#include <java/net/UnknownHostException.h>
#include <java/lang/SecurityException.h>

#if defined(HAVE_UNAME) && ! defined(HAVE_GETHOSTNAME)
#include <sys/utsname.h>
#endif

#ifndef HAVE_GETHOSTNAME_DECL
extern "C" int gethostname (char *name, int namelen);
#endif

jbyteArray
java::net::InetAddress::aton (jstring host)
{
  char *hostname;
  char buf[100];
  int len = JvGetStringUTFLength(host);
  if (len < 100)
    hostname = buf;
  else
    hostname = (char*) _Jv_AllocBytes (len+1);
  JvGetStringUTFRegion (host, 0, host->length(), hostname);
  buf[len] = '\0';
  char* bytes = NULL;
  int blen = 0;
#ifdef HAVE_INET_ATON
  struct in_addr laddr;
  if (inet_aton (hostname, &laddr))
    {
      bytes = (char*) &laddr;
      blen = 4;
    }
#elif defined(HAVE_INET_ADDR)
#if ! HAVE_IN_ADDR_T
  typedef jint in_addr_t;
#endif
  in_addr_t laddr = inet_addr (hostname);
  if (laddr != (in_addr_t)(-1))
    {
      bytes = (char*) &laddr;
      blen = 4;
    }
#endif
#if defined (HAVE_INET_PTON) && defined (HAVE_INET6)
  char inet6_addr[16];
  if (len != 0 && inet_pton (AF_INET6, hostname, inet6_addr) > 0)
    {
      bytes = inet6_addr;
      blen = 16;
    }
#endif
  if (blen == 0)
    return NULL;
  jbyteArray result = JvNewByteArray (blen);
  memcpy (elements (result), bytes, blen);
  return result;
}

jint
java::net::InetAddress::getFamily (jbyteArray bytes)
{
  int len = bytes->length;
  if (len == 4)
    return AF_INET;
#ifdef HAVE_INET6
  else if (len == 16)
    return AF_INET6;
#endif /* HAVE_INET6 */
  else
    JvFail ("unrecognized size");
}


JArray<java::net::InetAddress*> *
java::net::InetAddress::lookup (jstring host, java::net::InetAddress* iaddr,
				jboolean all)
{
  struct hostent *hptr = NULL;
#if defined (HAVE_GETHOSTBYNAME_R) || defined (HAVE_GETHOSTBYADDR_R)
  struct hostent hent_r;
#if HAVE_STRUCT_HOSTENT_DATA
  struct hostent_data fixed_buffer, *buffer_r = &fixed_buffer;
#else
#if defined (__GLIBC__) 
  // FIXME: in glibc, gethostbyname_r returns NETDB_INTERNAL to herr and
  // ERANGE to errno if the buffer size is too small, rather than what is 
  // expected here. We work around this by setting a bigger buffer size and 
  // hoping that it is big enough.
  char fixed_buffer[1024];
#else
  char fixed_buffer[200];
#endif
  char *buffer_r = fixed_buffer;
  int size_r = sizeof (fixed_buffer);
#endif
#endif

  if (host != NULL)
    {
      char *hostname;
      char buf[100];
      int len = JvGetStringUTFLength(host);
      if (len < 100)
	hostname = buf;
      else
	hostname = (char*) _Jv_AllocBytes (len+1);
      JvGetStringUTFRegion (host, 0, host->length(), hostname);
      buf[len] = '\0';
#ifdef HAVE_GETHOSTBYNAME_R
      while (true)
	{
	  int ok;
#if HAVE_STRUCT_HOSTENT_DATA
	  ok = ! gethostbyname_r (hostname, &hent_r, buffer_r);
#else
	  int herr = 0;
#ifdef GETHOSTBYNAME_R_RETURNS_INT
	  ok = ! gethostbyname_r (hostname, &hent_r, buffer_r, size_r,
				  &hptr, &herr);
#else
	  hptr = gethostbyname_r (hostname, &hent_r, buffer_r, size_r, &herr);
	  ok = hptr != NULL;
#endif /* GETHOSTNAME_R_RETURNS_INT */
	  if (! ok && herr == ERANGE)
	    {
	      size_r *= 2;
	      buffer_r = (char *) _Jv_AllocBytes (size_r);
	    }
	  else
#endif /* HAVE_STRUCT_HOSTENT_DATA */
	    break;
	}
#else
      // FIXME: this is insufficient if some other piece of code calls
      // this gethostbyname.
      JvSynchronize sync (java::net::InetAddress::localhostAddress);
      hptr = gethostbyname (hostname);
#endif /* HAVE_GETHOSTBYNAME_R */
    }
  else
    {
      jbyteArray bytes = iaddr->addr;
      char *chars = (char*) elements (bytes);
      int len = bytes->length;
      int type;
      char *val;
      if (len == 4)
	{
	  val = chars;
	  type = iaddr->family = AF_INET;
	}
#ifdef HAVE_INET6
      else if (len == 16)
	{
	  val = (char *) &chars;
	  type = iaddr->family = AF_INET6;
	}
#endif /* HAVE_INET6 */
      else
	JvFail ("unrecognized size");

#ifdef HAVE_GETHOSTBYADDR_R
      while (true)
	{
	  int ok;
#if HAVE_STRUCT_HOSTENT_DATA
	  ok = ! gethostbyaddr_r (val, len, type, &hent_r, buffer_r);
#else
	  int herr = 0;
#ifdef GETHOSTBYADDR_R_RETURNS_INT
	  ok = ! gethostbyaddr_r (val, len, type, &hent_r,
				  buffer_r, size_r, &hptr, &herr);
#else
	  hptr = gethostbyaddr_r (val, len, type, &hent_r,
				  buffer_r, size_r, &herr);
	  ok = hptr != NULL;
#endif /* GETHOSTBYADDR_R_RETURNS_INT */
	  if (! ok && herr == ERANGE)
	    {
	      size_r *= 2;
	      buffer_r = (char *) _Jv_AllocBytes (size_r);
	    }
	  else 
#endif /* HAVE_STRUCT_HOSTENT_DATA */
	    break;
	}
#else /* HAVE_GETHOSTBYADDR_R */
      // FIXME: this is insufficient if some other piece of code calls
      // this gethostbyaddr.
      JvSynchronize sync (java::net::InetAddress::localhostAddress);
      hptr = gethostbyaddr (val, len, type);
#endif /* HAVE_GETHOSTBYADDR_R */
    }
  if (hptr != NULL)
    {
      if (!all)
        host = JvNewStringUTF (hptr->h_name);
      java::lang::SecurityException *ex = checkConnect (host);
      if (ex != NULL)
	{
	  if (iaddr == NULL || iaddr->addr == NULL)
	    throw ex;
	  hptr = NULL;
	}
    }
  if (hptr == NULL)
    {
      if (iaddr != NULL && iaddr->addr != NULL)
	{
	  iaddr->hostName = iaddr->getHostAddress();
	  return NULL;
	}
      else
	throw new java::net::UnknownHostException(host);
    }
  int count;
  if (all)
    {
      char** ptr = hptr->h_addr_list;
      count = 0;
      while (*ptr++)  count++;
    }
  else
    count = 1;
  JArray<java::net::InetAddress*> *result;
  java::net::InetAddress** iaddrs;
  if (all)
    {
      result = java::net::InetAddress::allocArray (count);
      iaddrs = elements (result);
    }
  else
    {
      result = NULL;
      iaddrs = &iaddr;
    }

  for (int i = 0;  i < count;  i++)
    {
      if (iaddrs[i] == NULL)
	iaddrs[i] = new java::net::InetAddress (NULL, NULL);
      if (iaddrs[i]->hostName == NULL)
        iaddrs[i]->hostName = host;
      if (iaddrs[i]->addr == NULL)
	{
	  char *bytes = hptr->h_addr_list[i];
	  iaddrs[i]->addr = JvNewByteArray (hptr->h_length);
	  iaddrs[i]->family = getFamily (iaddrs[i]->addr);
	  memcpy (elements (iaddrs[i]->addr), bytes, hptr->h_length);
	}
    }
  return result;
}

jstring
java::net::InetAddress::getLocalHostname ()
{
  char *chars;
#ifdef HAVE_GETHOSTNAME
  char buffer[MAXHOSTNAMELEN];
  if (gethostname (buffer, MAXHOSTNAMELEN))
    return NULL;
  chars = buffer;
#elif HAVE_UNAME
  struct utsname stuff;
  if (uname (&stuff) != 0)
    return NULL;
  chars = stuff.nodename;
#else
  return NULL;
#endif
  // It is admittedly non-optimal to convert the hostname to Unicode
  // only to convert it back in getByName, but simplicity wins.  Note
  // that unless there is a SecurityManager, we only get called once
  // anyway, thanks to the InetAddress.localhost cache.
  return JvNewStringUTF (chars);
}
