// $Log: InetAddress.java,v $
// Revision 1.5  2000/04/27 15:51:28  sdalu
// mise a jour jdk[12]: phase 2
//
// Revision 1.4  2000/04/27 13:13:56  sdalu
// mise a jour jdk[12] phase1
//
// Revision 1.3  2000/04/03 08:05:33  chelius
// Javadoc
//
// Revision 1.2  2000/02/14 10:35:23  chelius
// Modifications mineures. Reste QoS et Doc a faire.
//
/*
 * @(#)InetAddress.java	1.65 99/04/22
 *
 * Copyright 1995-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.
 */

package java.net;

import java.util.HashMap;
import java.util.Random;
import java.security.AccessController;
import sun.security.action.*;
import sun.net.InetAddressCachePolicy;

/**
 * This class represents an Internet Protocol version 6 (IPv6) address.
 * <p>
 * Applications should use the methods <code>getLocalHost</code>,
 * <code>getByName</code>, or <code>getAllByName</code> to
 * create a new <code>InetAddress</code> instance.
 *
 * @author  Chris Warth
 * @author  Guillaume Chelius (IPv6 modifications)
 * @version 1.65, 04/22/99
 * @see     java.net.InetAddress#getAllByName(java.lang.String)
 * @see     java.net.InetAddress#getByName(java.lang.String)
 * @see     java.net.InetAddress#getLocalHost()
 * @since   JDK1.0
 */
public final
class InetAddress implements java.io.Serializable {

 
    final static int ADDRESS_SIZE = 16;

   /**
     * @serial
     */
    String hostName;

    /*
     * Currently we effectively with 128-bit addresses.
     *
     * @serial
     */
    byte[] address;

    /**
     * @serial
     */
    int family;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = 3286316764910316507L;

    /*
     * Load net6 library into runtime, and perform initializations.
     */
    static {
	AccessController.doPrivileged(new LoadLibraryAction("net6"));
        init();
    }

    /**
     * Constructor for the Socket.accept() method.
     * This creates an empty InetAddress which is filled in by
     * the accept() method.  This InetAddress, however, is not
     * put in the address cache, since it is not created by name.
     * 
     */
    InetAddress() {
      family = impl.getInetFamily();
      address = new byte[ADDRESS_SIZE];
    }

    /**
     * Creates an InetAddress with the specified host name and IP address.
     *
     * @param hostName the specified host name
     * @param addr the specified IP address.  The address should be 16 bytes long.
     * @exception UnknownHostException If the address is unknown.
     */
    InetAddress(String hostName, byte addr[]) {
	this.hostName = hostName;
	this.family = impl.getInetFamily();
	address=new byte[ADDRESS_SIZE];
	
	for(int i = 0; i < ADDRESS_SIZE; i++)
	    address[i] = addr[i];
    }
    
    /**
     * Utility routine to check if the InetAddress is a
     * IP multicast address (pure IPv6 or IPv4-mapped).
     * @since   JDK1.1
     */
     public boolean isMulticastAddress() {
	 return((address[0] == 0xff) || (isMappedAddress() && ((address[12] & 0xf0) == 0xe0))); 
    }

    /**
     * Utility routine to check if the InetAddress is an
     * IPv4-mapped one.
     * @since   JDK1.1
     */
     public boolean isMappedAddress() {
	 for (int i = 0; i < 10; i++) 
	     if (address[i] != 0x00) return false;
	 if ((address[10] != 0xff) ||  (address[11] != 0xff))
	     return false;
	 return true;
     }



    /**
     * Returns the hostname for this address.
     * If the host is equal to null, then this address refers to any
     * of the local machine's available network addresses.
     *
     * <p>If there is a security manager, its
     * <code>checkConnect</code> method is first called
     * with the hostname and <code>-1</code> 
     * as its arguments to see if the operation is allowed.
     *
     * @return  the host name for this IP address.
     * 
     * @exception  SecurityException  if a security manager exists and its  
     *  <code>checkConnect</code> method doesn't allow the operation .
     * 
     * @see SecurityManager#checkConnect
     */
    public String getHostName() {
	return getHostName(true);
    }

    /**
     * Returns the hostname for this address.
     * If the host is equal to null, then this address refers to any
     * of the local machine's available network addresses.
     * this is package private so SocketPermission can make calls into
     * here without a security check.
     *
     * <p>If there is a security manager, this method first
     * calls its <code>checkConnect</code> method
     * with the hostname and <code>-1</code> 
     * as its arguments to see if the calling code is allowed to know
     * the hostname for this IP address, i.e., to connect to the host.
     * 
     * @return  the host name for this IP address.
     * 
     * @param check make security check if true
     * 
     * @exception  SecurityException  if a security manager exists and its  
     *  <code>checkConnect</code> method doesn't allow the connection.
     * 
     * @see SecurityManager#checkConnect
     */
    String getHostName(boolean check) {
	if (hostName == null) {
	    try {
		// first lookup the hostname
		hostName = impl.getHostByAddr(address);

		/* check to see if calling code is allowed to know
		 * the hostname for this IP address, ie, connect to the host
		 */
		if (check) {
		    SecurityManager sec = System.getSecurityManager();
		    if (sec != null) {
			sec.checkConnect(hostName, -1);
		    }
		}

		/* now get all the IP addresses for this hostname,
		 * and make sure one of them matches the original IP
		 * address. We do this to try and prevent spoofing.
		 */

		InetAddress[] arr = getAllByName0(hostName, check);
		boolean ok = false;

		if(arr != null) {
		    for(int i = 0; !ok && i < arr.length; i++) {
			//System.out.println("check "+this+" "+arr[i]);
			ok = compare(address, arr[i].address);
		    }
		}

		//XXX: if it looks a spoof just return the address?
		if (!ok) {
		    hostName = getHostAddress();
		    return getHostAddress();
		}

	    } catch (SecurityException e) {
		hostName = getHostAddress();
	    } catch (UnknownHostException e) {
		hostName = getHostAddress();
	    }
	}
	return hostName;
    }

    public boolean compare(byte[] tab1, byte[] tab2) {
	int i = 0;
	while((i<ADDRESS_SIZE) && (tab1[i] == tab2[i]))
	    i++;
	if (i < ADDRESS_SIZE)
	    return(false);
	return(true);
    }

    /**
     * Returns the raw IP address of this <code>InetAddress</code>
     * object. The result is in network byte order: the highest order
     * byte of the address is in <code>getAddress()[0]</code>.
     *
     * @return  the raw IP address of this object.
     */
    public byte[] getAddress() {	
	byte[] addr = new byte[ADDRESS_SIZE];

	for(int i = 0; i < ADDRESS_SIZE; i++)
	 addr[i] = address[i];
	return addr;
    }

    /**
     * Returns the IP address string.
     *
     * @return  the raw IP address in a string format.
     * @since   JDK1.0.2
     */
    public String getHostAddress() {
	return impl.gettextaddress(address);
     }


    /**
     * Returns a hashcode for this IP address. The hash function just converts
     * all bytes of the 128 bits address into int and sums them.
     * @return  a hash code value for this IP address.
     */
    public int hashCode() {
	int somme = 0;   
	for(int i = 0; i < ADDRESS_SIZE; i++)
	    somme = somme + (int) address[i];
	return somme;
    }


    /**
     * Compares this object against the specified object.
     * The result is <code>true</code> if and only if the argument is
     * not <code>null</code> and it represents the same IP address as
     * this object.
     * <p>
     * Two instances of <code>InetAddress</code> represent the same IP
     * address if the length of the byte arrays returned by
     * <code>getAddress</code> is the same for both, and each of the
     * array components is the same for the byte arrays.
     *
     * @param   obj   the object to compare against.
     * @return  <code>true</code> if the objects are the same;
     *          <code>false</code> otherwise.
     * @see     java.net.InetAddress#getAddress()
     */
    public boolean equals(Object obj) {
	return (obj != null) && (obj instanceof InetAddress) &&
	    compare(this.address, ((InetAddress) obj).address);
    }

    /**
     * Converts this IP address to a <code>String</code>.
     *
     * @return  a string representation of this IP address.
     */
    public String toString() {
	return getHostName() + "/" + getHostAddress();
    }

    /*
     * Cached addresses - our own litle nis, not!
     */
    private static HashMap	    addressCache = new HashMap();
    private static InetAddress	    unknownAddress;
    private static InetAddress      localHost;
    private static InetAddress[]    unknown_array; // put THIS in cache
    static InetAddress	    anyLocalAddress;
    static InetAddressImpl  impl;

    private static HashMap          lookupTable = new HashMap();

    static final class CacheEntry {

	CacheEntry(String hostname, Object address, long expiration) {
	    this.hostname = hostname;
	    this.address = address;
	    this.expiration = expiration;
	}

	String hostname;
	Object address;
	long expiration;
    }

    private static void cacheAddress(String hostname, Object address) {
	synchronized (InetAddressCachePolicy.class) {
	    // if the cache policy is to cache nothing, just return
	    if (InetAddressCachePolicy.get() == 0) {
		return;
	    }
	    long expiration = -1;
	    if (InetAddressCachePolicy.get() != InetAddressCachePolicy.FOREVER) {
		expiration = System.currentTimeMillis() + 
		    (InetAddressCachePolicy.get() * 1000);
	    }
	    cacheAddress(hostname, address, expiration);
	}
    }

    private static void cacheAddress(String hostname, Object address, long expiration) {
        hostname = hostname.toLowerCase();
	synchronized (addressCache) {
	    CacheEntry entry = (CacheEntry)addressCache.get(hostname);
	    if (entry == null) {
		entry = new CacheEntry(hostname, address, expiration);
		addressCache.put(hostname, entry);
	    } else {
		entry.address = address;
		entry.expiration = expiration;
	    }
	}
    }

    private static Object getCachedAddress(String hostname) {
        hostname = hostname.toLowerCase();
	synchronized (addressCache) {
	    if (InetAddressCachePolicy.get() == 0) {
		return null;
	    }
	    CacheEntry entry = (CacheEntry)addressCache.get(hostname);
	    if (entry != null && entry.expiration < System.currentTimeMillis() &&
		entry.expiration >= 0) {
		entry = null;
	    }
	    return entry != null ? entry.address : null;
	}
    }

    /*
     * generic localHost to give back to applets
     * - private so not API delta
     */
    private static InetAddress      loopbackHost;

    static {
	/*
	 * Property "impl.prefix" will be prepended to the classname
	 * of the implementation object we instantiate, to which we
	 * delegate the real work (like native methods).  This
	 * property can vary across implementations of the java.
	 * classes.  The default is an empty String "".
	 */
	String prefix = (String)AccessController.doPrivileged(
		      new GetPropertyAction("impl.prefix", ""));

	try {
	    impl = null;
	    impl = (InetAddressImpl)(Class.forName("java.net." + prefix +
						   "InetAddressImpl")
				     .newInstance());
	} catch (ClassNotFoundException e) {
	    System.err.println("Class not found: java.net." + prefix +
			       "InetAddressImpl:\ncheck impl.prefix property " +
			       "in your properties file.");
	} catch (InstantiationException e) {
	    System.err.println("Could not instantiate: java.net." + prefix +
			       "InetAddressImpl:\ncheck impl.prefix property " +
			       "in your properties file.");
	} catch (IllegalAccessException e) {
	    System.err.println("Cannot access class: java.net." + prefix +
			       "InetAddressImpl:\ncheck impl.prefix property " +
			       "in your properties file.");
	}

	if (impl == null) {
	    try {
		impl = (InetAddressImpl)(Class.forName("java.net.InetAddressImpl")
					 .newInstance());
	    } catch (Exception e) {
		throw new Error("System property impl.prefix incorrect");
	    }
	}

	unknownAddress = new InetAddress();
	anyLocalAddress = new InetAddress();
	impl.makeAnyLocalAddress(anyLocalAddress);
	byte[] IP = new byte[ADDRESS_SIZE];
	for(int i = 0; i < 15; i++)
	    IP[i] = 0x00;
	IP[15] = 0x01;    
	loopbackHost = new InetAddress("localhost", IP);

	/* find the local host name */
	try {
	    localHost = new InetAddress();
	    localHost.hostName = impl.getLocalHostName();
	    /* we explicitly leave the address of the local host
	     * uninitialized.  A DNS lookup in this, the static
	     * initializer, will cause a machine disconnected
	     * from the network to hang - it'll be trying to query
	     * a DNS server that isn't there.
	     *
	     * Instead, we just get the hostname of the local host.
	     * The native code for this just calls gethostname()
	     * which should be pretty innocuous - it shouldn't try
	     * to contact a DNS server.  If any application
	     * calls InetAddress.getLocalHost(), we initialize
	     * the local host's address there if not already initialized.
	     *
	     * Note that for this to work it is also essential that
	     * the localHost InetAddress is _NOT_ put into the address cache
	     * here in the static initializer (which happens if we call
	     * getByName() from the static initializer).  It _IS_ OK
	     * to put it in the addressCache after initialization.
	     *
	     * The unitialized state of the localHost's address is -1,
	     * or IP address ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 
	     * which we know cannot be a legal host address.
	     */
	    for (int i = 0; i < ADDRESS_SIZE; i++)
		localHost.address[i] = (byte)0xff; 
	} catch (Exception ex) { /* this shouldn't happen */
	    localHost = unknownAddress;
	}

	/* cache the name/address pair "::"/:: */
	String unknownByAddr = "::";
	unknown_array = new InetAddress[1];
	unknown_array[0] = new InetAddress(unknownByAddr,
					   unknownAddress.getAddress());
	cacheAddress(unknownByAddr, unknown_array, InetAddressCachePolicy.FOREVER);
    }

    /**
     * Determines the IP address of a host, given the host's name. The
     * host name can either be a machine name, such as
     * "<code>java.sun.com</code>", or a string representing its IP
     * address, such as "<code>16ea::14d2:1345</code>".
     *
     * @param      host   the specified host, or <code>null</code> for the
     *                    local host.
     * @return     an IP address for the given host name.
     * @exception  UnknownHostException  if no IP address for the
     *               <code>host</code> could be found.
     */
    public static InetAddress getByName(String host)
	throws UnknownHostException {
	Object obj = null;
	if (host == null || host.length() == 0 || host.equals ("localhost")) {
	    return loopbackHost;
	}

	/* The string (probably) represents a numerical IP address.
	 * If it isn't an IP address, (i.e., no ":" symbol), 
	 * we treat it as a hostname, and lookup that way.
	 * This seems to be 100% compliant to the RFC1123 spec: 
	 * a partial hostname like 3com.domain4 is technically valid.
	 */
	if (host.indexOf(':') != -1) {
	    InetAddress in = new InetAddress();
	    in.address = impl.getbynameipv6(host);
	    in.hostName = null;
	    return in;
	}
	
	return getAllByName0(host)[0];
    }

    /**
     * Determines all the IP addresses of a host, given the host's name.
     * The host name can either be a machine name, such as
     * "<code>java.sun.com</code>", or a string representing
     * its IP address, such as "<code>16ea::14d2:1345</code>".
     *
     * <p>If there is a security manager and <code>host</code> is not 
     * null and <code>host.length() </code> is not equal to zero, the
     * security manager's
     * <code>checkConnect</code> method is called
     * with the hostname and <code>-1</code> 
     * as its arguments to see if the operation is allowed.
     *
     * @param      host   the name of the host.
     * @return     an array of all the IP addresses for a given host name.
     * 
     * @exception  UnknownHostException  if no IP address for the
     *               <code>host</code> could be found.
     * @exception  SecurityException  if a security manager exists and its  
     *               <code>checkConnect</code> method doesn't allow the operation.
     * 
     * @see SecurityManager#checkConnect
     */
    public static InetAddress[] getAllByName(String host)
	throws UnknownHostException {

	if (host == null || host.length() == 0) {
	    throw new UnknownHostException("empty string");
	}
	
	/* If it is an IP address (i.e. symbol ":" present),
	 * return it.
	 */
	if (host.indexOf(':') != -1) {
	    InetAddress[] ret = new InetAddress[1];
	     ret[0] = getByName(host);
	     return ret;
	}
	return getAllByName0(host);
    }

    private static InetAddress[] getAllByName0 (String host)
	throws UnknownHostException
    {
	return getAllByName0(host, true);
    }

    /**
     * package private so SocketPermission can call it
     */
    static InetAddress[] getAllByName0 (String host, boolean check)
	throws UnknownHostException  {
	/* If it gets here it is presumed to be a hostname */
	/* Cache.get can return: null, unknownAddress, or InetAddress[] */
        Object obj = null;
	Object objcopy = null;

	/* make sure the connection to the host is allowed, before we
	 * give out a hostname
	 */
	if (check) {
	    SecurityManager security = System.getSecurityManager();
	    if (security != null) {
		security.checkConnect(host, -1);
	    }
	}

	obj = getCachedAddress(host);

	/* If no entry in cache, then do the host lookup */

	if (obj == null) {
	    obj = getAddressFromNameService(host);
	}
        if (obj == unknown_array)
            throw new UnknownHostException(host);

	/* Make a copy of the InetAddress array */
	try {
	    objcopy = ((InetAddress [])obj).clone();
	    // the following line is a hack, to ensure that the code
	    // can compile for both the broken compiler and the fixed one.
	    if (objcopy == null)
		throw new CloneNotSupportedException();
	} catch (CloneNotSupportedException cnse) {
	    cnse.printStackTrace();
	}

	return (InetAddress [])objcopy;
    }

    private static Object getAddressFromNameService(String host) {
	Object obj = null;

	// Check whether the host is in the lookupTable.
	// 1) If the host isn't in the lookupTable when
	//    checkLookupTable() is called, checkLookupTable()
	//    would add the host in the lookupTable and
	//    return null. So we will do the lookup.
	// 2) If the host is in the lookupTable when
	//    checkLookupTable() is called, the current thread
	//    would be blocked until the host is removed
	//    from the lookupTable. Then this thread
	//    should try to look up the addressCache.
	//     i) if it found the address in the
	//        addressCache, checkLookupTable()  would
	//        return the address.
	//     ii) if it didn't find the address in the
	//         addressCache for any reason,
	//         it should add the host in the
	//         lookupTable and return null so the
	//         following code would do  a lookup itself.
	if ((obj = checkLookupTable(host)) == null) {
	    // This is the first thread which looks up the address 
	    // this host or the cache entry for this host has been
	    // expired so this thread should do the lookup.
	    try {
		/*
		 * Do not put the call to lookup() inside the
		 * constructor.  if you do you will still be
		 * allocating space when the lookup fails.
		 */
		byte[][] byte_array;
		byte_array = impl.lookupAllHostAddr(host);
		InetAddress[] addr_array =
		    new InetAddress[byte_array.length];

		for (int i = 0; i < byte_array.length; i++) {
		    byte addr[] = byte_array[i];
		    addr_array[i] = new InetAddress(host, addr);
		}
		obj = addr_array;
	    } catch (UnknownHostException e) {
		obj  = unknown_array;
	    } finally {
		// Cache the address.
		cacheAddress(host, obj);
		// Delete the host from the lookupTable, and
		// notify all threads waiting for the monitor
		// for lookupTable.
		updateLookupTable(host);
	    }
	}

	return obj;
    }
	
		
    private static Object checkLookupTable(String host) {
	// make sure obj  is null.
	Object obj = null;
	
	synchronized (lookupTable) {
	    // If the host isn't in the lookupTable, add it in the
	    // lookuptable and return null. The caller should do
	    // the lookup.
	    if (lookupTable.containsKey(host) == false) {
		lookupTable.put(host, null);
		return obj;
	    }

	    // If the host is in the lookupTable, it means that another
	    // thread is trying to look up the address of this host.
	    // This thread should wait.
	    while (lookupTable.containsKey(host)) {
		try {
		    lookupTable.wait();
		} catch (InterruptedException e) {
		}
	    }
	}

	// The other thread has finished looking up the address of
	// the host. This thread should retry to get the address
	// from the addressCache. If it doesn't get the address from
	// the cache,  it will try to look up the address itself.
	obj = getCachedAddress(host);
	if (obj == null) {
	    synchronized (lookupTable) {
		lookupTable.put(host, null);
	    }
	}
	 
	return obj;
    }

    private static void updateLookupTable(String host) {
	synchronized (lookupTable) {
	    lookupTable.remove(host);
	    lookupTable.notifyAll();
	}
    }

    /**
     * Returns the local host.
     *
     * <p>If there is a security manager, its
     * <code>checkConnect</code> method is called
     * with the local host name and <code>-1</code> 
     * as its arguments to see if the operation is allowed.
     *
     * @return     the IP address of the local host.
     * 
     * @exception  UnknownHostException  if no IP address for the
     *               <code>host</code> could be found.
     * @exception  SecurityException  if a security manager exists and its  
     *               <code>checkConnect</code> method doesn't allow the operation.
     * 
     * @see SecurityManager#checkConnect
     */
    public synchronized static InetAddress getLocalHost() throws UnknownHostException {
        if (localHost.equals(unknownAddress)) {
	    throw new UnknownHostException();
	}

        /* make sure the connection to the host is allowed: if yes,
	 * return the "real" localHost; if not, return loopback "::1"
	 */
	SecurityManager security = System.getSecurityManager();
	try {

	    /* If the localhost's address is not initialized yet, initialize
	     * it.  It is no longer initialized in the static initializer
	     * (see comment there).
	     */

	    if (localHost.address[0] == -1) {
		localHost = getAllByName(localHost.hostName)[0];
		/* This puts it in the address cache as well */
	    }

	    if (security != null)
		security.checkConnect(localHost.getHostName(), -1);
	} catch (java.lang.SecurityException e) {
	    return loopbackHost;
	}
	return localHost;
    }

    /**
     * Perform class load-time initializations.
     */
    private static native void init();
}

class InetAddressImpl {
    native String getLocalHostName() throws UnknownHostException;
    native void makeAnyLocalAddress(InetAddress addr);
    native byte[][]
        lookupAllHostAddr(String hostname) throws UnknownHostException;
    native String getHostByAddr(byte[] addr) throws UnknownHostException;
    native int getInetFamily();
    native byte[] getbynameipv6(String host);
    native String gettextaddress(byte addr[]);
}






