/*
 * @(#)InetAddressImpl.c	1.23 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_InetAddressImpl.h"

/* the initial size of our hostent buffers */
#define HENT_BUF_SIZE 1024
#define BIG_HENT_BUF_SIZE 10240	 /* a jumbo-sized one */

/************************************************************************
 * InetAddressImpl
 */

/*
 * Class:     java_net_InetAddressImpl
 * Method:    getLocalHostName
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL
Java_java_net_InetAddressImpl_getLocalHostName(JNIEnv *env, jobject this) {
    char hostname[NI_MAXHOST];

    hostname[0] = '\0';

    /* In Linux/freebsd, gethostname seems to return a complete name 
     * Check "the completeness of the returned name"
     * Check "the need for .ipv6"
     */
    if (gethostname(hostname, NI_MAXHOST)) 
	strcpy(hostname, "localhost");

    return (*env)->NewStringUTF(env, hostname);
}

/*
 * Class:     java_net_InetAddressImpl
 * Method:    makeAnyLocalAddress
 * Signature: (Ljava/net/InetAddress;)V
 *
 * This doesn't appear to justify its own existence.
 */
JNIEXPORT void JNICALL
Java_java_net_InetAddressImpl_makeAnyLocalAddress(JNIEnv *env, jobject this,
						  jobject iaObj) {
  jbyteArray jaddress;

  if (IS_NULL(iaObj)) {
	JNU_ThrowNullPointerException(env, "inet address argument");
	return;
  } else {
      jaddress = (*env)->GetObjectField(env, iaObj, ia_addressID);
      /* Check "warning: passing arg 5 discards `const' from pointer target type" */
      (*env)->SetByteArrayRegion(env, jaddress, 0, V6_SIZE, in6addr_any.s6_addr);
      if ((*env)->ExceptionOccurred(env) != NULL)
	return;
      (*env)->SetIntField(env, iaObj, ia_familyID, AF_INET6);
    }
}

/*
 * Class:     java_net_InetAddressImpl
 * Method:    getInetFamily
 * Signature: ()I
 */
JNIEXPORT jint JNICALL
Java_java_net_InetAddressImpl_getInetFamily(JNIEnv *env, jobject this)
{
    return AF_INET6;
}

/*
 * Find an internet address for a given hostname.  Not this this
 * code works for addresses of type INET and INET6. The policy is to look for 
 * an true IPv6 address, if none is found, return an IPv4 mapped-one.
 * This function may use two different address resolution APIs.
 *
 * Class:     java_net_InetAddressImpl
 * Method:    lookupAllHostAddr
 * Signature: (Ljava/lang/String;)[[B
 */

JNIEXPORT jobjectArray JNICALL
Java_java_net_InetAddressImpl_lookupAllHostAddr(JNIEnv *env, jobject this,
						jstring host) {
    const char *hostname;
    int i = 0, max;
    struct hostent *hp;
    char **paddress;
    jobjectArray result;
    jclass generic_class = (*env)->FindClass(env, "[B");
    jbyteArray jarray;

#ifdef GETIPNODEBYNAME
    int error_num;
#endif /* GETIPNODEBYNAME */

    /* get host name in UTF format */  
    // temporary buffer, on the off chance we need to expand 
    if (IS_NULL(host)) {
	JNU_ThrowNullPointerException(env, "host is null");
	return 0;
    }
#if JDK_VERSION < 2
    if ((hostname = (*env)->GetStringUTFChars(env, host, NULL)) == NULL) {
	JNU_ThrowByName(env, JNU_JAVAPKG "RuntimeException", NULL);    
	return NULL; 
    }  
#else 
    hostname = JNU_GetStringPlatformChars(env, host, JNI_FALSE);
#endif
    
    /* fill in the hp variable */
#ifdef GETHOSTBYNAME2
    if ((hp = gethostbyname2(hostname, AF_INET6)) == NULL) {
      if ((hp = gethostbyname2(hostname, AF_INET)) == NULL) {
	jclass newExcCls = (*env)->FindClass(env, "java/net/UnknownHostException");
	if (newExcCls == 0)  
	  return NULL;
      (*env)->ThrowNew(env, newExcCls, (const char *)hostname);
      return NULL;
      }
    }
#endif //GETHOSTBYNAME2

#ifdef GETIPNODEBYNAME
    if ((hp = getipnodebyname(hostname, AF_INET6, AI_DEFAULT, &error_num)) == NULL) {
      jclass newExcCls = (*env)->FindClass(env, "java/net/UnknownHostException");
      if (newExcCls == 0)  
	return NULL;
      (*env)->ThrowNew(env, newExcCls, (const char *)hostname);
      return NULL;
    }
#endif //GETIPNODEBYNAME
    
    /* get the number of addresses */
    i = 0;
    paddress = hp->h_addr_list;
    while (paddress[i])
	i++;
    max = i;
    
    /* create the result array */
    if ((result = (*env)->NewObjectArray(env, max, generic_class, NULL)) == NULL) {
	JNU_ThrowOutOfMemoryError(env, "Address allocation failed");
#ifdef GETIPNODEBYNAME
	freehostent(hp);
#endif //GETIPNODEBYNAME
	return NULL;
    }
    
    paddress = hp->h_addr_list;
    for (i = 0; i < max; i++) {
	if ((jarray = (*env)->NewByteArray(env, V6_SIZE)) == NULL) {
	    JNU_ThrowOutOfMemoryError(env, "Address allocation failed");
#ifdef GETIPNODEBYNAME
	    freehostent(hp);
#endif //GETIPNODEBYNAME	    
	    return NULL;
	}
	switch (hp->h_addrtype) {
	case AF_INET6:
	    (*env)->SetByteArrayRegion(env, jarray, 0, V6_SIZE, paddress[i]);
	    break;
	case AF_INET: {
	    char mapped[12] = { 0,0,0,0,0,0,0,0,0,0,0xff,0xff };
	    (*env)->SetByteArrayRegion(env, jarray, 0, 12, mapped);
	    (*env)->SetByteArrayRegion(env, jarray, 12, 4, paddress[i]);
	    break;
	}
	}

	if ((*env)->ExceptionOccurred(env) != NULL) {
#ifdef GETIPNODEBYNAME
	    freehostent(hp);
#endif //GETIPNODEBYNAME	    
	    return NULL;
	}
	
	(*env)->SetObjectArrayElement(env, result, i, jarray);
	if ((*env)->ExceptionOccurred(env) != NULL) {
#ifdef GETIPNODEBYNAME
	    freehostent(hp);
#endif //GETIPNODEBYNAME	    
	    return NULL;
	}
    }
#ifdef GETIPNODEBYNAME
    freehostent(hp);
#endif //GETIPNODEBYNAME    
    return result;
}    

/*
 * Class:     java_net_InetAddressImpl
 * Method:    getHostByAddr
 * Signature: (I)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL
Java_java_net_InetAddressImpl_getHostByAddr(JNIEnv *env, jobject this,
					    jbyteArray addr) {

  int i;
  jbyte *address;
  struct sockaddr_in6 him;

#ifdef GETNAMEINFO
  char hostname[NI_MAXHOST];
#endif//GETNAMEINFO

#ifdef GETIPNODEBYADDR
  int error_num;
  struct hostent *hp;
  jstring result;
#endif //GETIPNODEBYADDR
  
  /* get the address in net form */
  if ((address = (*env)->GetByteArrayElements(env, addr, NULL)) == NULL) {
    jclass newExcCls = (*env)->FindClass(env, "java/lang/RuntimeException");
    if (newExcCls == 0)  
      return NULL;
    (*env)->ThrowNew(env, newExcCls, "");
    return NULL;
  }
  
  /* fill the him variable */
  memset((char *) &him, 0, sizeof(him));
#ifdef SIN6_LEN
  him.sin6_len = sizeof(him);
#endif    
  him.sin6_family = AF_INET6;
  for (i = 0; i < V6_SIZE; i++) 
    him.sin6_addr.s6_addr[i] = address[i];  
  
    /* get the name */
#ifdef GETNAMEINFO
  if (getnameinfo((struct sockaddr *) &him, sizeof(him), hostname, NI_MAXHOST, NULL, 0, 0)) {
    jclass newExcCls = (*env)->FindClass(env, "java/net/UnknownHostException");
    if (newExcCls == 0)  
      return NULL;
    (*env)->ThrowNew(env, newExcCls, "Not a valid IP address");
    return NULL;
  }
#endif //GETNAMEINFO
  
#ifdef GETIPNODEBYADDR
  if ((hp = getipnodebyaddr((void *) &him, sizeof(him), AF_INET6, &error_num))) {
    jclass newExcCls = (*env)->FindClass(env, "java/net/UnknownHostException");
    if (newExcCls == 0)  
      return NULL;
    (*env)->ThrowNew(env, newExcCls, "Not a valid IP address");
    return NULL;
  }
#endif //GETIPNODEBYADDR
#ifdef GETNAMEINFO
  return (*env)->NewStringUTF(env, hostname);
#endif //GETNAMEINFO
#ifdef GETIPNODEBYADDR
  result = (*env)->NewStringUTF(env, hp->h_name);
  freehostent(hp);
  return result;
#endif //GETIPNODEBYADDR
  return 0;
}

JNIEXPORT jstring JNICALL Java_java_net_InetAddressImpl_gettextaddress(JNIEnv *env, jobject me, jbyteArray jaddress) {

  char aux[INET6_ADDRSTRLEN];
  struct in6_addr in6;
  jbyte *address;
  int i, length;
  
  /* get address value */
  if ((address = (*env)->GetByteArrayElements(env, jaddress, NULL)) == NULL) {
    jclass newExcCls = (*env)->FindClass(env, "java/lang/RuntimeException");
    if (newExcCls == 0)  
      return NULL;
    (*env)->ThrowNew(env, newExcCls, "");
    return NULL;
  }
  length = (*env)->GetArrayLength(env, jaddress);
  
  bzero((void *) &in6, sizeof(in6));
  for(i = 0; i < length; i++)
    in6.s6_addr[i] = address[i];
  
  /* get address in text form */
  if(inet_ntop(AF_INET6, (void *) &in6, aux, sizeof(aux)) == NULL) {
    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
		    "address family not recognized");
    return 0;
  }

  return (*env)->NewStringUTF(env, aux);
}

/*
 * Class:     fr_loria_resedas_net6_InetAddressImpl6
 * Method:    getbynameipv6
 * Signature: (Ljava/lang/String;)[B
 */

JNIEXPORT jbyteArray JNICALL Java_java_net_InetAddressImpl_getbynameipv6(JNIEnv *env, jobject me, jstring jaddress) {
  
  int aux;
  struct in6_addr in6;
  const char *address;
  jbyteArray result;

  /* get the address value */
  if ((address = (*env)->GetStringUTFChars(env, jaddress, NULL)) == NULL) {
    jclass newExcCls = (*env)->FindClass(env, "java/lang/RuntimeException");
    if (newExcCls == 0)  
      return NULL;
    (*env)->ThrowNew(env, newExcCls, "");
    return NULL;
  }  
    
  /* convert the address */
  if ((aux = inet_pton(AF_INET6, address, (void *) &in6)) == 0) {
    jclass newExcCls = (*env)->FindClass(env, "java/net/UnknownHostException");
    if (newExcCls == 0)  
      return NULL;
    (*env)->ThrowNew(env, newExcCls, "Not a valid address");
    return NULL;
  }
  if (aux  == -1) {
    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
		    "address family not recognized");
    return 0;
  }
  (*env)->ReleaseStringUTFChars(env, jaddress, address);
  
  /* build the result */
  if ((result = (*env)->NewByteArray(env, V6_SIZE)) == NULL) {
      JNU_ThrowOutOfMemoryError(env, "Address allocation failed");
      return 0;
  }
  (*env)->SetByteArrayRegion(env, result, 0, V6_SIZE, in6.s6_addr);
  if ((*env)->ExceptionOccurred(env) != NULL)
    return NULL;
  
  return result;
}
