/* Copyright (c) 1999 by Inria Lorraine.  All Rights Reserved */

/***
   NAME
     RawSocket
   PURPOSE
     
   NOTES
     
   HISTORY
     chelius - Jul 25, 1999: Created.
     $Log: RawSocket.java,v $
     Revision 1.1  2000/04/27 13:24:15  sdalu
     ajout jdk1.1

     Revision 1.2  2000/04/03 08:05:34  chelius
     Javadoc

     Revision 1.1.1.1  2000/02/08 16:31:59  chelius
     Initial import

     Revision 1.1.1.1  2000/02/01 11:10:46  chelius
     Mise sous CVS des sources de libnet6 for jdk1.2.*

     Revision 1.1.1.1  1999/07/25 15:01:57  chelius
     First release of the ipv6 packages for java

***/
package java.net;


import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InterruptedIOException;

/**
 * This class represents a socket for sending and receiving raw packets.
 *
 * <p>A raw socket is the sending or receiving point for a packet
 * delivery service. Each packet sent or received on a datagram socket
 * is individually addressed and routed. Multiple packets sent from
 * one machine to another may be routed differently, and may arrive in
 * any order.
 *
 * @author  Guillaume Chelius (IPv6 modifications)
 * @version 1.0, 02/04/2000
 * @see     java.net.RawPacket
 * @since JDK1.2
 */
public class RawSocket {
    /*
     * The implementation of this DatagramSocket.
     */
    RawSocketImpl impl;

    /*
     * The Class of DatagramSocketImpl we use for this runtime.  
     */

    static Class implClass;

     static {

	if (implClass == null) {
	    try {
		implClass = Class.forName("java.net.PlainRawSocketImpl");
	    } catch (Exception e) {
		throw new Error("System property impl.prefix incorrect");
	    }
	}
    }
    
    /**
     * Constructs a raw socket. 
     *
     * @exception  SocketException  if the socket could not be opened.
     * @since      JDK1.2
     */
    public RawSocket() throws SocketException {
	// create a raw socket.
	create(0, null);
    }

    /**
     * Creates a raw socket, bound to the specified local
     * address. 
     * @param laddr local address to bind
     * @since   JDK1.2
     */
    public RawSocket(InetAddress laddr) throws SocketException {
	create(0, laddr);
    }

    /**
     * Constructs a raw socket with the specified protocol ID.
     *
     * @param protocol protocol ID
     * @exception  SocketException  if the socket could not be opened,
     *               or the socket could not bind to the specified local port.
     * @since      JDK1.2
     */
    public RawSocket(int protocol) throws SocketException {
	// create a datagram socket.
	create(protocol, null);
    }

    /**
     * Creates a raw socket, bound to the specified local
     * address with the specified protocol ID.
     * @param protocol protocol ID
     * @param laddr local address to bind
     * @since   JDK1.2
     */
    public RawSocket(int protocol, InetAddress laddr) throws SocketException {
	create(protocol, laddr);
    }

    /* do the work of creating a vanilla rawsocket. 
     */
    void create(int protocol, InetAddress laddr) throws SocketException {
	try {
	    impl = (RawSocketImpl) implClass.newInstance();
	} catch (Exception e) {
	    throw new SocketException("can't instantiate RawSocketImpl");
	}
	// creates a raw socket
	impl.create(protocol);
	// binds the raw socket to desired address
	if (laddr != null) {
	    impl.bind(laddr);	
	}
    }

    /**
     * Sends a raw packet from this socket. The 
     * <code>RawPacket</code> includes information indicating the 
     * data to be sent, its length, the IP address of the remote host. 
     *
     * @param      p   the <code>RawPacket</code> to be sent.
     * @exception  IOException  if an I/O error occurs.
     * @see        java.net.RawPacket
     * @since      JDK1.2
     */
    public void send(RawPacket p) throws IOException  {

	// check the address is ok wiht the security manager on every send.
	SecurityManager security = System.getSecurityManager();

	// The reason you want to synchronize on raw packet
	// is because you dont want an applet to change the address 
	// while you are trying to send the packet for example 
	// after the security check but before the send.
	synchronized (p) {
	    if (security != null) {
		if (p.getAddress().isMulticastAddress()) {
		 security.checkMulticast(p.getAddress());
		} else {
			security.checkConnect(p.getAddress().getHostAddress(), -1);
		}
	    }
            // call the  method to send
	    if (p == null)
		throw (new NullPointerException("Null pointer for RawPacket6"));
            impl.send(p);
        }
    }
    

    /**
     * Receives a raw packet from this socket. When this method 
     * returns, the <code>RawPacket</code>'s buffer is filled with 
     * the data received. The raw packet also contains the sender's 
     * IP address. 
     * <p>
     * This method blocks until a raw is received. The 
     * <code>length</code> field of the raw packet object contains 
     * the length of the received message. If the message is longer than 
     * the buffer length, the message is truncated. 
     *
     * @param      p   the <code>RawPacket</code> into which to place
     *                 the incoming data.
     * @exception  IOException  if an I/O error occurs.
     * @see        java.net.RawPacket
     * @see        java.net.RawSocket
     * @since      JDK1.2
     */
    public synchronized void receive(RawPacket p) throws IOException {
	// check the address is ok with the security manager before every recv.
	SecurityManager security = System.getSecurityManager();

      	synchronized (p) {
	    if (security != null) {

		while(true) {
		    // peek at the packet to see who it is from.
		    InetAddress peekAddress = new InetAddress();
		    impl.peek(peekAddress);

		    try {
			   security.checkConnect(p.getAddress().getHostAddress(), -1);
			// security check succeeded - so now break and recv the packet.
			break;
		    } catch (SecurityException se) {
			// Throw away the offending packet by consuming
			// it in a tmp buffer.
			RawPacket tmp = new RawPacket(new byte[1], 1);
			impl.receive(tmp);

		    	// silently discard the offending packet and continue:
			// unknown/malicious entities on nets should not make
			// runtime throw security exception and disrupt the applet
			// by sending random datagram packets.
			continue;
		    } 
		} // end of while
	    }
	    // If the security check succeeds, then receive the packet
	    if (p == null)
		throw (new NullPointerException("Null pointer for RawPacket6"));
 	    impl.receive(p);
	}	
    }



    /** 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 RawSocket
     *  will block for only this amount of time.  If the timeout expires,
     *  a <B>java.io.InterruptedIOException</B> is raised, though the
     *  ServerSocket is still valid.  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.  
     *
     * @since   JDK1.2
     */
    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).
     *
     * @since   JDK1.2
     */
    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;
	}
    }

    /**
     * Activate the checksum option and situates it a the specified offset.
     * @param ofset the offset where to place the checksum
     * @since   JDK1.2
     */
    public void setChecksum(int offset) throws SocketException {
	impl.setChecksum(offset);
    }

    /**
     * Get the offset at which the checsum is placed in the packet
     */
    public int getChecksum() throws SocketException {
	return impl.getChecksum();
    }

    /**
     * Gets the local address to which the socket is bound.
     *
     * @since   1.2
     */
    public InetAddress getLocalAddress() {
	InetAddress in = null;
	try {
	    in = (InetAddress) impl.getOption(SocketOptions.SO_BINDADDR);
	    SecurityManager s = System.getSecurityManager();
	    if (s != null) {
		    s.checkConnect(in.getHostAddress(), -1);
	    }
	} catch (Exception e) {
	    in = InetAddress.anyLocalAddress; // "::"
	}
	return in;
    }    

    /**
     * Sets the SO_SNDBUF option to the specified value for this
     * DatagramSocket. 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
     * DatagramSocket. 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 datagram socket. 
     *
     * @since   JDK1.2
     */
    public void close() {
	impl.close();
    }

    /**
     * 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 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;
	}
    }

}
