// $Log: MulticastSocket.java,v $
// Revision 1.1  2000/04/27 13:24:14  sdalu
// ajout jdk1.1
//
// Revision 1.3  2000/04/03 08:05:33  chelius
// Javadoc
//
// Revision 1.2  2000/02/14 10:35:24  chelius
// Modifications mineures. Reste QoS et Doc a faire.
//
/*
 * @(#)MulticastSocket.java	1.31 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.io.IOException;
import java.io.InterruptedIOException;

/**
 * The multicast datagram socket class is useful for sending
 * and receiving IP multicast packets.  A MulticastSocket is
 * a (UDP) DatagramSocket, with additional capabilities for
 * joining "groups" of other multicast hosts on the internet.
 * <P>
 * A multicast group is specified by a particular IP address format, those
 * in the range <CODE>224.0.0.1</CODE> to <CODE>239.255.255.255</CODE>,
 * inclusive, and by a standard UDP port number.  One would join a
 * multicast group by first creating a MulticastSocket with the desired
 * port, then invoking the <CODE>joinGroup(InetAddress groupAddr)</CODE>
 * method:
 * <PRE>
 * // join a Multicast group and send the group salutations
 * ...
 * byte[] msg = {'H', 'e', 'l', 'l', 'o'};
 * InetAddress group = InetAddress.getByName("228.5.6.7");
 * MulticastSocket s = new MulticastSocket(6789);
 * s.joinGroup(group);
 * DatagramPacket hi = new DatagramPacket(msg, msg.length,
 *                             group, 6789);
 * s.send(hi);
 * // get their responses!
 * byte[] buf = new byte[1000];
 * DatagramPacket recv = new DatagramPacket(buf, buf.length);
 * s.receive(recv);
 * ...
 * // OK, I'm done talking - leave the group...
 * s.leaveGroup(group);
 * </PRE>
 *
 * When one sends a message to a multicast group, <B>all</B> subscribing
 * recipients to that host and port receive the message (within the
 * time-to-live range of the packet, see below).  The socket needn't
 * be a member of the multicast group to send messages to it.
 * <P>
 * When a socket subscribes to a multicast group/port, it receives
 * datagrams sent by other hosts to the group/port, as do all other
 * members of the group and port.  A socket relinquishes membership
 * in a group by the leaveGroup(InetAddress addr) method.  <B>
 * Multiple MulticastSocket's</B> may subscribe to a multicast group
 * and port concurrently, and they will all receive group datagrams.
 * <P>
 * Currently applets are not allowed to use multicast sockets.
 *
 * @author Pavani Diwanji
 * @author Guillaume Chelius (IPv6 modifications)
 * @since  JDK1.1
 */
public
class MulticastSocket extends DatagramSocket {
    /**
     * Create a multicast socket.
     * 
     * <p>If there is a security manager, 
     * its <code>checkListen</code> method is first called
     * with 0 as its argument to ensure the operation is allowed. 
     * This could result in a SecurityException.
     * 
     * @exception  SecurityException  if a security manager exists and its  
     *             <code>checkListen</code> method doesn't allow the operation.
     * @see SecurityManager#checkListen
     */
    public MulticastSocket() throws IOException {
	super();
    }

    /**
     * Create a multicast socket and bind it to a specific port.
     * 
     * <p>If there is a security manager, 
     * its <code>checkListen</code> method is first called
     * with the <code>port</code> argument
     * as its argument to ensure the operation is allowed. 
     * This could result in a SecurityException.
     * 
     * @param port port to use
     * @exception  SecurityException  if a security manager exists and its  
     *             <code>checkListen</code> method doesn't allow the operation.
     * @see SecurityManager#checkListen
     */
    public MulticastSocket(int port) throws IOException {
	super(port);
    }

    /* do the work of creating a vanilla multicast socket.  It is
     * important that the signature of this method not change,
     * even though it is package-private, since it is overrides a
     * method from DatagramSocket, which must not set SO_REUSEADDR.
     */
    void create(int port, InetAddress ignore) throws SocketException {
	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkListen(port);
	}
	try {
	    this.impl = (DatagramSocketImpl) implClass.newInstance();
	} catch (Exception e) {
	    throw new SocketException("can't instantiate DatagramSocketImpl" + e.toString());
	}
	impl.create();
	impl.setOption(SocketOptions.SO_REUSEADDR, new Integer(-1));
	impl.bind(port, InetAddress.anyLocalAddress);
    }

    /**
     * The lock on the socket's TTL. This is for set/getTTL and
     * send(packet,ttl).
     */
    private Object ttlLock = new Object();

    /**
     * This method is still implemented under IPv6 for 
     * compatibility reasons. It simply performs a call to 
     * the setHOPS method.
     *
     * @param ttl the time-to-live
     *
     * @see MulticastSocket#setHOPS(int)
     * @deprecated use the setTimeToLive method instead, which uses
     * <b>int</b> instead of <b>byte</b> as the type for ttl.
     */
    public void setTTL(byte ttl) throws IOException {
	setHOPS((int) ttl);
    }



    /**
     * This method is still implemented under IPv6 for 
     * compatibility reasons. It simply performs a call to 
     * the setHOPS method.
     *
     * <P> The ttl <B>must</B> be in the range <code> 0 <= ttl <=
     * 255</code> or an IllegalArgumentException will be thrown.
     *
     * @param ttl the time-to-live
     * @see MulticastSocket#setHOPS(int)
     */
    public void setTimeToLive(int ttl) throws IOException {
	if (ttl < 0 || ttl > 255) {
	    throw new IllegalArgumentException("ttl out of range");
	}
	setHOPS(ttl);
    }

    /**
     * Set the default hops limit value for multicast packets sent out
     * on this socket.  The HOPS sets the IP hops number for
     * <code>DatagramPackets</code> sent to a MulticastGroup, which
     * specifies how many "hops" that the packet will be forwarded
     * on the network before it expires.
     *
     * <P> The ttl <B>must</B> be in the range <code> 0 <= ttl <=
     * 255</code> or an IllegalArgumentException will be thrown.
     *
     * @param ttl the hops number
     */
    public void setHOPS(int ttl) throws IOException {
	if (ttl < 0 || ttl > 255) {
	    throw new IllegalArgumentException("ttl out of range");
	}
	impl.setHOPS(ttl);
    }

    /**
     * This method is still implemented under IPv6 for 
     * compatibility reasons. It simply performs a call to 
     * the getHOPS method.
     *
     * @see MulticastSocket#getHOPS()
     * @deprecated use the getTimeToLive method instead, which returns
     * an <b>int</b> instead of a <b>byte</b>.
     */
    public byte getTTL() throws IOException {
	return (byte) getHOPS();
    }

    /**
     * This method is still implemented under IPv6 for 
     * compatibility reasons. It simply performs a call to 
     * the getHOPS method.
     *
     * @see MulticastSocket#getHOPS()
     */
    public int getTimeToLive() throws IOException {
	return getHOPS();
    }

    /**
     * Return the default hops value of the socket.
     */
    public int getHOPS() throws IOException {
	return impl.getHOPS();
    }

    /**
     * Joins a multicast group.Its behavior may be affected
     * by <code>setInterface</code>.
     * 
     * <p>If there is a security manager, this method first
     * calls its <code>checkMulticast</code> method
     * with the <code>mcastaddr</code> argument
     * as its argument.
     * 
     * @param mcastaddr is the multicast address to join
     * 
     * @exception IOException if there is an error joining
     * or when the address is not a multicast address.
     * @exception  SecurityException  if a security manager exists and its  
     * <code>checkMulticast</code> method doesn't allow the join.
     * 
     * @see SecurityManager#checkMulticast(InetAddress)
     */
    public void joinGroup(InetAddress mcastaddr) throws IOException {

	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkMulticast(mcastaddr);
	}
	impl.join(mcastaddr);
    }

    /**
     * Leave a multicast group. Its behavior may be affected
     * by <code>setInterface</code>.
     * 
     * <p>If there is a security manager, this method first
     * calls its <code>checkMulticast</code> method
     * with the <code>mcastaddr</code> argument
     * as its argument.
     * 
     * @param mcastaddr is the multicast address to leave
     * @exception IOException if there is an error leaving
     * or when the address is not a multicast address.
     * @exception  SecurityException  if a security manager exists and its  
     * <code>checkMulticast</code> method doesn't allow the operation.
     * 
     * @see SecurityManager#checkMulticast(InetAddress)
     */
    public void leaveGroup(InetAddress mcastaddr) throws IOException {

	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkMulticast(mcastaddr);
	}
	impl.leave(mcastaddr);
    }

    /**
     * Set the multicast network interface used by methods
     * whose behavior would be affected by the value of the
     * network interface. Useful for multihomed hosts.
     */
    public void setInterface(int inf) throws SocketException {
	Integer value = new Integer(inf); 
	impl.setOption(SocketOptions.IPV6_MULTICAST_IF, value);
    }
    
    /**
     * This method is still implemented under IPv6 for 
     * compatibility reasons. It throws a socket exception.
     *
     * @exception SocketException This API is no more valid.
     */
    public void setInterface(InetAddress inf) throws SocketException {
	throw new SocketException("API of this option has changed !");
    }

    /**
     * Retrieve the address of the network interface used for
     * multicast packets.
     */
    public int getInterface() throws SocketException {
	Integer res = (Integer) impl.getOption(SocketOptions.IPV6_MULTICAST_IF);
	return res.intValue();
    }

    /**
     * Sends a datagram packet to the destination, with a HOPS 
     * other than the default for the socket.  This method
     * need only be used in instances where a particular HOPS is desired;
     * otherwise it is preferable to set a HOPS once on the socket, and
     * use that default HOPS for all packets.  This method does <B>not
     * </B> alter the default TTL for the socket. Its behavior may be
     * affected by <code>setInterface</code>.
     *
     * <p>If there is a security manager, this method first performs some
     * security checks. First, if <code>p.getAddress().isMulticastAddress()</code>
     * is true, this method calls the
     * security manager's <code>checkMulticast</code> method
     * with <code>p.getAddress()</code> and <code>ttl</code> as its arguments.
     * If the evaluation of that expression is false,
     * this method instead calls the security manager's 
     * <code>checkConnect</code> method with arguments
     * <code>p.getAddress().getHostAddress()</code> and
     * <code>p.getPort()</code>. Each call to a security manager method
     * could result in a SecurityException if the operation is not allowed.
     * 
     * @param p	is the packet to be sent. The packet should contain
     * the destination multicast IP address and the data to be sent.
     * One does not need to be the member of the group to send
     * packets to a destination multicast address.
     * @param ttl optional HOPS for multicast packet.
     * default ttl is 1.
     * 
     * @exception IOException is raised if an error occurs i.e
     * error while setting ttl.
     * @exception  SecurityException  if a security manager exists and its  
     *             <code>checkMulticast</code> or <code>checkConnect</code> 
     *             method doesn't allow the send.
     * 
     * @see DatagramSocket#send
     * @see DatagramSocket#receive
     * @see SecurityManager#checkMulticast(java.net.InetAddress, byte)
     * @see SecurityManager#checkConnect
     */
    public void send(DatagramPacket p, byte ttl)
        throws IOException {
            synchronized(ttlLock) {
                synchronized(p) {
		    if (!connected) {
                        // Security manager makes sure that the multicast address
                        // is allowed one and that the ttl used is less
                        // than the allowed maxttl.
                        SecurityManager security = System.getSecurityManager();
                        if (security != null) {
                            if (p.getAddress().isMulticastAddress()) {
                                security.checkMulticast(p.getAddress(), ttl);
                            } else {
                                security.checkConnect(p.getAddress().getHostAddress(),
                                                      p.getPort());
                            }
                        }
		    } else {
			// we're connected
			InetAddress packetAddress = null;
			packetAddress = p.getAddress();
			if (packetAddress == null) {
			    p.setAddress(connectedAddress);
			    p.setPort(connectedPort);
			} else if ((!packetAddress.equals(connectedAddress)) ||
				   p.getPort() != connectedPort) {
			    throw new SecurityException("connected address and packet address" +
							" differ");
			}
		    }
                    int dttl = getHOPS();
                    try {
                        if (ttl != dttl) {
                            // set the ttl
                            impl.setHOPS(ttl);
                        }
                        // call the datagram method to send
                        impl.send(p);
                    } finally {
                        // set it back to default
                        if (ttl != dttl) {
                            impl.setHOPS(dttl);
                        }
                    }
                } // synch p
            }  //synch ttl
    } //method
}



