// $Id: ANEPSocket.java,v 1.10 2001/02/28 11:54:10 sdalu Exp $

package java.net;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketOptions;

/**
 * This class represents a socket for sending and receiving ANEP packets. <p>
 *
 * An ANEP socket is the sending or receiving point for a packet
 * delivery service. Each packet sent or received on a ANEP socket
 * is individually addressed and routed. Multiple packets sent from
 * one machine to another may be routed differently, and may arrive in
 * any order. <p>
 *
 * ANEP broadcasts sends and receives are always enabled on a
 * ANEPSocket. <p>
 *
 * <strong>No security manager is implemented for ANEP</strong>.
 */
public
class ANEPSocket {
    /*
     * The implementation of this ANEPSocket.
     */
    ANEPSocketImpl impl;

    boolean	connected		= false;
    InetAddress	connectedAddress	= null;
    int		connectedType		= -1;
    int		ltype			= -1;

    /*
     * The Class of ANEPSocketImpl we use for this runtime.
     */
    static Class implClass;

    static {
        String prefix = "";
        try {
            prefix = (String) java.security.AccessController.doPrivileged(
                    new sun.security.action.GetPropertyAction("impl.prefix",
                                                              "Plain"));
            implClass = Class.forName("java.net."+prefix+"ANEPSocketImpl");
        } catch (Exception e) {
            System.err.println("Can't find class: java.net." + prefix +
                               "ANEPSocketImpl: check impl.prefix property");
        }

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


    //-- Constructors ------------------------------------------------------

    /**
     * Creates an ANEP socket, bound to the specified local
     * address.  The local type must be between 0 and 65535 inclusive.
     * 
     * 
     * @param type  local type to use
     * @param laddr local address to bind
     * @param reuse should we be allowed to reuse this address/type
     * 
     * @exception  IOException  if the socket could not be opened.
     * @exception  BindException if the socket could not bind to the 
     *              specified local address/type.
     */
    public ANEPSocket(int type, InetAddress laddr, boolean reuse) 
	throws IOException {
        ANEPPacket.checkType(type);
	create();
	setSoReuse(reuse ? 1 : 0);
        bind(type, laddr);
    }

    /**
     * Creates an ANEP socket, bound to the specified local
     * address.  The local type must be between 0 and 65535 inclusive.
     * 
     * 
     * @param type  local type to use
     * @param laddr local address to bind
     * 
     * @exception  IOException  if the socket could not be opened.
     * @exception  BindException if the socket could not bind to the 
     *              specified local type.
     */
    public ANEPSocket(int type, InetAddress laddr) throws IOException {
        ANEPPacket.checkType(type);
	create();
        bind(type, laddr);
    }

    /**
     * Constructs an ANEP socket and binds it to the specified type
     * on the local host machine.
     * 
     * @param      type type to use.
     * @exception  IOException  if the socket could not be opened.
     * @exception  BindException if the socket could not bind to the 
     *              specified local type.
     */
    public ANEPSocket(int type) throws IOException {
        this(type, null);
    }


    /**
     * Creates an ANEP socket, not bound. This could be used when
     * you only want to send data, otherwise you will need to
     * call <code>bind</code> to be able to receive data.
     * 
     * @exception  IOException  if the socket could not be opened
     */
    public ANEPSocket() throws IOException {
	create();
    }

    //----------------------------------------------------------------------

    void create() throws IOException {
        try {
	    impl = (ANEPSocketImpl) implClass.newInstance();
        } catch (Exception e) {
            throw new IOException("can't instantiate ANEPSocketImpl");
        }

        impl.create();
    }

    //----------------------------------------------------------------------

    /**
     * Bind a socket, to listen for a type coming from the specified 
     * address.
     *
     * @param type  local type to use
     * @param laddr local address to bind
     *
     * @exception  BindException if the socket could not bind to the 
     *              specified local address/type.
     * @exception  IOException  otherwise.
     */
    public void bind(int type, InetAddress laddr) throws IOException {
        ANEPPacket.checkType(type);
	ltype = type;

        if (laddr == null)
            laddr = InetAddress.anyLocalAddress;

	impl.bind(type, laddr);
    }



    /** 
     * Connects the socket to a remote address for this socket. When a
     * socket is connected to a remote address, packets may only be
     * sent to or received from that address. By default an ANEP
     * socket is not connected. <p>
     *
     * On a send operation, if the packet's address
     * is set and the packet's address and the socket's address do not
     * match, an IllegalArgumentException will be thrown. A socket
     * connected to a multicast address may only be used to send packets.
     *
     * @param address the remote address for the socket
     * @param type the remote type for the socket.
     *
     * @exception IllegalArgumentException if the address is invalid
     * or the type is out of range.
     *
     * @see #disconnect
     * @see #send
     * @see #receive 
     */
    public void connect(InetAddress address, int type) {
        synchronized (this) {
            if (type < 0 || type > ANEPPacket.MAX_TYPE)
                throw new IllegalArgumentException("connect: " + type);

            if (address == null)
                throw new IllegalArgumentException("connect: null address");

            connectedAddress = address;
            connectedType    = type;
            connected        = true;
        }
    }

    /** 
     * Disconnects the socket. This does nothing if the socket is not
     * connected.
     *
     * @see #connect
     */
    public void disconnect() {
        synchronized (this) {
            connectedAddress = null;
            connectedType    = -1;
            connected        = false;
        }
    }

    /**
     * Returns the address to which this socket is connected. Returns null
     * if the socket is not connected.
     *
     * @return the address to which this socket is connected.
     */
    public InetAddress getInetAddress() {
        return connectedAddress;
    }

    /**
     * Returns the type for this socket. Returns -1 if the socket is not
     * connected.
     *
     * @return the type to which this socket is connected.
     */
    public int getType() {
        return connectedType;
    }




    /**
     * Sends a ANEP packet from this socket. The
     * <code>ANEPPacket</code> includes information indicating the
     * data to be sent, its length, the IP address of the remote host,
     * and the type on the remote host.
     *
     * @param      p   the <code>ANEPPacket</code> to be sent.
     * 
     * @exception  IOException  if an I/O error occurs.
     * 
     * @see        java.net.ANEPPacket
     */
    public void send(ANEPPacket p) throws IOException  {
        InetAddress packetAddress = null;
        synchronized (p) {
            if (connected) {
                // we're connected
                packetAddress = p.getAddress();
                if (packetAddress == null) {
                    p.setAddress(connectedAddress);
                    p.setType(connectedType);
                } else if ((!packetAddress.equals(connectedAddress)) ||
                           p.getType() != connectedType) {
                    throw new IllegalArgumentException("connected address " +
                                                       "and packet address" +
                                                       " differ");
                }
            }
            // call the  method to send
            impl.send(p);
        }
    }

    /**
     * Receives an ANEP packet from this socket. When this method
     * returns, the <code>ANEPPacket</code>'s buffer is filled with
     * the data received. The ANEP packet also contains the sender's
     * IP address, and the packet type.
     * <p>
     * This method blocks until a datagram is received. The
     * <code>length</code> field of the ANEP packet object contains
     * the length of the received message. If the message is longer than
     * the packet's length, the message is truncated.
     * <p>
     * 
     * @param      p   the <code>ANEPPacket</code> into which to place
     *                 the incoming data.
     * @exception  IOException  if an I/O error occurs.
     * @see        java.net.ANEPPacket
     * @see        java.net.ANEPSocket
     */
    public synchronized void receive(ANEPPacket p) throws IOException {
        synchronized (p) {
            if (connected) {
                while(true) {
                    // peek at the packet to see who it is from.
                    InetAddress peekAddress = new InetAddress();
                    int         peekType    = impl.peek(peekAddress);
                    
		    if (!connectedAddress.equals(peekAddress) ||
			(connectedType != peekType)) {
			// throw the packet away and silently continue
			ANEPPacket tmp = new ANEPPacket(new byte[1], 0, 1);
			impl.receive(tmp);
			continue;
		    } else {
			break;
		    }
                }
            }
            impl.receive(p);
        }
    }

    /**
     * Gets the local address to which the socket is bound.
     */
    public InetAddress getLocalAddress() {
        InetAddress in = null;
        try {
            in = (InetAddress) impl.getOption(SocketOptions.SO_BINDADDR);
        } catch (Exception e) {
            in = InetAddress.anyLocalAddress;
        }
        return in;
    }

    /**
     * Gets the local type to which the socket is bound.
     */
    public int getLocalType() {
	return ltype;
    }


    /** 
     * Enable/disable SO_TIMEOUT with the specified timeout, in
     * milliseconds. With this option set to a non-zero timeout,
     * a call to receive() for this ANEPSocket will block for only this 
     * amount of time.  If the timeout expires, a 
     * <B>java.io.InterruptedIOException</B> is raised. 
     * The option <B>must</B> be enabled
     * prior to entering the blocking operation to have effect.  The
     * timeout must be > 0.
     * A timeout of zero is interpreted as an infinite timeout.
     */
    public synchronized void setSoTimeout(int timeout) throws SocketException {
        impl.setOption(SocketOptions.SO_TIMEOUT, new Integer(timeout));
    }

    /**
     * Retrive setting for SO_TIMEOUT.  0 returns implies that the
     * option is disabled (i.e., timeout of infinity).
     */
    public synchronized int getSoTimeout() throws SocketException {
        Object o = impl.getOption(SocketOptions.SO_TIMEOUT);
        /* extra type safety */
        if (o instanceof Integer) {
            return ((Integer) o).intValue();
        } else {
            return 0;
        }
    }

    /**
     * Sets the SO_SNDBUF option to the specified value for this
     * ANEPSocket. The SO_SNDBUF option is used by the platform's
     * networking code as a hint for the size to use to allocate set
     * the underlying network I/O buffers.
     *
     * <p>Increasing buffer size can increase the performance of
     * network I/O for high-volume connection, while decreasing it can
     * help reduce the backlog of incoming data. For UDP, this sets
     * the maximum size of a packet that may be sent on this socket.
     *
     * <p>Because SO_SNDBUF is a hint, applications that want to
     * verify what size the buffers were set to should call
     * <href="#getSendBufferSize>getSendBufferSize</a>.
     *
     * @param size the size to which to set the send buffer
     * size. This value must be greater than 0.
     *
     * @exception IllegalArgumentException if the value is 0 or is
     * negative.
     */
    public synchronized void setSendBufferSize(int size)
    throws SocketException{
        if (!(size > 0))
            throw new IllegalArgumentException("negative send size");

        impl.setOption(SocketOptions.SO_SNDBUF, new Integer(size));
    }

    /**
     * Get value of the SO_SNDBUF option for this socket, that is the
     * buffer size used by the platform for output on the this Socket.
     *
     * @see #setSendBufferSize
     */
    public synchronized int getSendBufferSize() throws SocketException {
        int result = 0;
        Object o = impl.getOption(SocketOptions.SO_SNDBUF);
        if (o instanceof Integer) {
            result = ((Integer)o).intValue();
        }
        return result;
    }

    /**
     * Sets the SO_RCVBUF option to the specified value for this
     * ANEPSocket. The SO_RCVBUF option is used by the platform's
     * networking code as a hint for the size to use to allocate set
     * the underlying network I/O buffers.
     *
     * <p>Increasing buffer size can increase the performance of
     * network I/O for high-volume connection, while decreasing it can
     * help reduce the backlog of incoming data. For UDP, this sets
     * the maximum size of a packet that may be sent on this socket.
     *
     * <p>Because SO_RCVBUF is a hint, applications that want to
     * verify what size the buffers were set to should call
     * <href="#getReceiveBufferSize>getReceiveBufferSize</a>.
     *
     * @param size the size to which to set the receive buffer
     * size. This value must be greater than 0.
     *
     * @exception IllegalArgumentException if the value is 0 or is
     * negative.
     */
    public synchronized void setReceiveBufferSize(int size)
    throws SocketException{
        if (size < 0) {
            throw new IllegalArgumentException("invalid receive size");
        }
        impl.setOption(SocketOptions.SO_RCVBUF, new Integer(size));
    }

    /**
     * Get value of the SO_RCVBUF option for this socket, that is the
     * buffer size used by the platform for input on the this Socket.
     *
     * @see #setReceiveBufferSize
     */
    public synchronized int getReceiveBufferSize()
    throws SocketException{
        int result = 0;
        Object o = impl.getOption(SocketOptions.SO_RCVBUF);
        if (o instanceof Integer) {
            result = ((Integer)o).intValue();
        }
        return result;
    }

    /**
     * Closes this ANEP socket.
     */
    public void close() {
        impl.close();
    }

    /**
     * Specify the list of option type the application known.
     *
     * Based on this list incoming packet with option type not known
     * by the application will either result int discarding the packet
     * or ignoring the option. <p>
     *
     * Even if allowed by the method, it <strong>is not</strong>
     * a good idea to specify more than once the same type.
     *
     * @see ANEPOptionType
     *
     * @param optionTypes an array of option type to accept
     */
    public synchronized void allowOptionTypes(int [] optionTypes) 
	throws SocketException {
	for (int i = 0 ; i < optionTypes.length ; i++)
	    if (optionTypes[i] < 0 || optionTypes[i] > ANEPOption.MAX_TYPE)
		throw new IllegalArgumentException("option type out of range" +
						   " for element " + i);
	impl.allowOptionTypes(optionTypes);
    }

    /**
     * Set the discard bit on every packet send, except if
     * stated otherwise in the packet itself.
     *
     * @see ANEPPacket
     * 
     * @param value if not null packet will be discarded
     */
    public synchronized void setANEPDiscard(int value)
	throws SocketException {
        impl.setOption(SocketOptions.ANEP_DISCARD, new Integer(value));
    }

    /**
     * Allow to reuse the same addresse/type.
     *
     * @see ANEPPacket
     * 
     * @param value if not null reuse is allowed
     */
    public synchronized void setSoReuse(int value)
	throws SocketException {
        impl.setOption(SocketOptions.SO_REUSEADDR, new Integer(value));
    }

    /**
     * Set value of the IPV6_ESP_NETWORK_LEVEL option for this socket, 
     * that is the ESP policy in tunnel mode to apply on the socket.
     *
     * @param value value of the policy to apply on the socket
     * @see #getNetEncryption
     */
    public synchronized void setNetEncryption(int value) 
	throws SocketException {
        impl.setOption(SocketOptions.IPV6_ESP_NETWORK_LEVEL, 
		       new Integer(value));
    }

    /**
     * Set value of the IPV6_ESP_TRANS_LEVEL option for this socket, 
     * that is the ESP policy in transport mode to apply on the socket.
     *
     * @param value value of the policy to apply on the socket
     * @see #getTransportEncryption
     */
    public synchronized void setTransportEncryption(int value) 
	throws SocketException {
        impl.setOption(SocketOptions.IPV6_ESP_TRANS_LEVEL, new Integer(value));
    }

    /**
     * Set value of the IPV6_AH_NETWORK_LEVEL option for this socket, 
     * that is the ESP policy in tunnel mode to apply on the socket.
     *
     * @param value value of the policy to apply on the socket
     * @see #getNetworkAuthenticate
     */
    public synchronized void setNetAuthenticate(int value) 
	throws SocketException {
        impl.setOption(SocketOptions.IPV6_AUTH_NETWORK_LEVEL, 
		       new Integer(value));
    }

    /**
     * Set value of the IPV6_AH_TRANS_LEVEL option for this socket, 
     * that is the ESP policy in transport mode to apply on the socket.
     *
     * @param value value of the policy to apply on the socket
     * @see #getTransportAuthenticate
     */
    public synchronized void setTransportAuthenticate(int value) 
	throws SocketException {
        impl.setOption(SocketOptions.IPV6_AUTH_TRANS_LEVEL, 
		       new Integer(value));
    }

    /**
     * Get the value of SO_REUSEADDR (which allow to reuse the same 
     * addresse/type).
     *
     * @see ANEPPacket
     * 
     * @see #setSoReuse
     */
    public synchronized int getSoReuse() throws SocketException {
	Object o = impl.getOption(SocketOptions.SO_REUSEADDR);
        if (o instanceof Integer) {
            return ((Integer) o).intValue();
        } else {
            return 0;
        }
    }

    /**
     * Get value of the IPV6_ESP_NETWORK_LEVEL option for this socket, 
     * that is the ESP policy in tunnel mode that is applied on the socket.
     *
     * @see #setNetEncryption
     */
    public synchronized int getNetEncryption() throws SocketException {
        Object o = impl.getOption(SocketOptions.IPV6_ESP_NETWORK_LEVEL);
        /* extra type safety */
        if (o instanceof Integer) {
            return ((Integer) o).intValue();
        } else {
            return 0;
        }
    }

    /**
     * Get value of the IPV6_ESP_TRANS_LEVEL option for this socket, 
     * that is the ESP policy in transport mode that is applied on the socket.
     *
     * @see #setTransportEncryption
     */
    public synchronized int getTransportEncryption() throws SocketException {
        Object o = impl.getOption(SocketOptions.IPV6_ESP_TRANS_LEVEL);
        /* extra type safety */
        if (o instanceof Integer) {
            return ((Integer) o).intValue();
        } else {
            return 0;
        }
    }

    /**
     * Get value of the IPV6_AUTH_NETWORK_LEVEL option for this socket, 
     * that is the AH policy in tunnel mode that is applied on the socket.
     *
     * @see #setNetAuthenticate
     */
    public synchronized int getNetAuthenticate() throws SocketException {
        Object o = impl.getOption(SocketOptions.IPV6_AUTH_NETWORK_LEVEL);
        /* extra type safety */
        if (o instanceof Integer) {
            return ((Integer) o).intValue();
        } else {
            return 0;
        }
    }

    /**
     * Get value of the IPV6_AUTH_TRANS_LEVEL option for this socket, 
     * that is the AH policy in transport that is applied on the socket.
     *
     * @see #setTransportAuthenticate
     */
    public synchronized int getTransportAuthenticate() throws SocketException {
        Object o = impl.getOption(SocketOptions.IPV6_AUTH_TRANS_LEVEL);
        /* extra type safety */
        if (o instanceof Integer) {
            return ((Integer) o).intValue();
        } else {
            return 0;
        }
    }

    /**
     * Set value of the flow label for this socket, 
     * 
     * @param value value of the flow label
     * @see #getFlowLabel
     */
    public synchronized void setFlowLabel(int value) throws SocketException {
	impl.setOption(SocketOptions.IPV6_FLOW_LABEL, new Integer(value));
    }

    /**
     * Set value of the traffic class for this socket, 
     * 
     * @param value value of the traffic class
     * @see #getTrafficClass
     */
    public synchronized void setTrafficClass(int value) throws SocketException {
	impl.setOption(SocketOptions.IPV6_TRAFFIC_CLASS, new Integer(value));
    }

    /**
     * Get value of the flow label for this socket, 
     *
     * @see #setFlowLabel
     */
    public synchronized int getFlowLabel() throws SocketException {
	Object o = impl.getOption(SocketOptions.IPV6_FLOW_LABEL);
	/* extra type safety */
	if (o instanceof Integer) {
	    return ((Integer) o).intValue();
	} else {
	    return 0;
	}
    }

    /**
     * Get value of the traffic class for this socket, 
     *
     * @see #setTrafficClass
     */
    public synchronized int getTrafficClass() throws SocketException {
	Object o = impl.getOption(SocketOptions.IPV6_TRAFFIC_CLASS);
	/* extra type safety */
	if (o instanceof Integer) {
	    return ((Integer) o).intValue();
	} else {
	    return 0;
	}
    }

}
