/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * This software is the proprietary information of Sun Microsystems, Inc.
 * Use is subject to license terms.
 * 
 * Copyright 2004 Sun Microsystems, Inc.  Tous droits réservés.
 * Ce logiciel est propriété de Sun Microsystems, Inc.
 * Distribué par des licences qui en restreignent l'utilisation.
 *
 * ident        "@(#)MfDiscoveryResponder.java 1.22     04/07/21 SMI"
 *
 */

package com.sun.mfwk.discovery;

//general stuff
import java.net.*;
import java.io.*;
import java.util.Date;

// JESMF common config file
import com.sun.mfwk.config.*;

// JESMF log
import java.util.logging.*;
import com.sun.mfwk.util.log.MfLogService;


/**
 * Allows to implement the discovery service on the Component Product side.
 * <p>
 * When the <CODE>MfDiscoveryResponder</CODE> is instantiated,
 * a multicast socket is created.
 * The <CODE>MfDiscoveryResponder</CODE> then sends a discovery RESPONSE message
 * to the multicast group containing:<p>
 *  - Component Product information (Product name & instance)<p>
 *  - Monitoring URI<p>
 *  - Optional user data<p>
 * This discovery RESPONSE message is also re-emitted everytime a DISCOVERY frame
 * is received on the Multicast channel.
 * The multicast socket default group and port can be modified setting the
 * appropriate properties in the /etc/opt/SUNWmfwk/config/agent.properties files.
 * The default values for the group and the port are 225.225.225.1 and 12345.
 * These cannot be modified at run time.
 * <p>
 * NB multicast messages are only received on the local node (TTL is forced to 0)
 * @version 1.0
 * @author Sun Microsystems, Inc
 */

public class MfDiscoveryResponder {
    
    //responder
    private MfDiscoveryActualResponder myDiscoveryActualResponder = null;
    
    //state of the responder
    static int OFFLINE = 1;
    static int ONLINE = 2;
    
    //current state of the responder
    private int state = OFFLINE;


    //we are guaranteed these are set even if the config file is not there
    private String myMcastGroup = null;
    private int myMcastPort = 0;

    private boolean disableLoopBack = false;
    private String loopBackIP = "127.0.0.1";
    private int TTL = 0;

    //variables filled in by SDK
    private String myProductName = null;
    private String myProductInstance = null;
    private String myUri = null;
    
    //optional variables
    private byte[] myUserData = null;
    private boolean myEncryptUserData = false;
    private byte[] myEncryptUserDataKey = null;

    //added for bugId 5068417
    private long myTimeStamp = 0;
    private Date myStartDate = null;

    // Tracing utility. */
    private static Logger logger = MfLogService.getLogger("Discovery");
    
    /** Creates a new instance of MfDiscoveryResponder and starts it
     * @param productName The name of the component Product to be discovered
     * @param productInstance The unique identifier of the instance to be discovered
     * @param uri The unique uri where the agent should connect to retrieve monitoring information
     * @throws IOException if an I/O error occurs
     */
    public MfDiscoveryResponder(String productName, String productInstance, String uri)  throws IOException {
        this(productName, productInstance, uri, null,null);
    }

    /** Creates a new instance of MfDiscoveryResponder and starts it
     * @param productName The name of the component Product to be discovered
     * @param productInstance The unique identifier of the instance to be discovered
     * @param uri The unique uri where the agent should connect to retrieve monitoring information
     * @param userData The additional information that the user add to the RESP frame
     *
     * Allows the user to specify additional information in the discovery RESPONSE message. The following limitation applies to the length of the byte array parameter: The length of a UDP packet is maximum 64 Kbytes.
     * @throws IOException if an I/O error occurs
     */
    public MfDiscoveryResponder(String productName, String productInstance, String uri, byte[] userData) throws IOException  {
        this(productName, productInstance, uri, userData, null);
    }    
        
     /** Creates a new instance of MfDiscoveryResponder and starts it
      *
      * A mechanism is provided for encrypting the userData part provided in the MfDiscoveryResponder.
      * The principle is that you can encrypt these data using a key and setting the flag encryptData
      * to true.
      * The DiscoveryClient will decode the user data *before* notifying the listener using the key
      * in the /etc/opt/SUNWmfwk/security/disc.key.
      *
      * Read carefully what follows if you intend to use this mechanism
      *
      * - the key you provide in the API *has to be the same* as the one contained in the /etc/opt/SUNWmfwk/security/disc.key
      *  file
      * - when you retrieve the data in the CP module using the MfDiscoveryInfo.getUserData() method, the data is already
      *  decrypted
      * - if the keys do not match, the key is invalid (not generated with /opt/SUNWmfwk/bin/discgenkey), the key file
      *  is not there or not readable by the agent, the discovery packet will be dropped and ignored.
      * @param encryptUserDataKey The key you provide in order to encrypt the userData field
      * The key you provide in the API *has to be the same* as the one contained in the /etc/opt/SUNWmfwk/security/disc.key
      *  file
      * @param productName The name of the component Product to be discovered
      * @param productInstance The unique identifier of the instance to be discovered
      * @param uri The unique uri where the agent should connect to retrieve monitoring information
      * @param userData The additional information that the user add to the RESP frame
      * @param encryptUserDataKey if set to the userData will be encrypted using this key as a parameter
      * @throws IOException if an I/O error occurs
      */
    public MfDiscoveryResponder(String productName, String productInstance, String uri, byte[] userData, byte[] encryptUserDataKey)  throws IOException {
        logger.entering("MfDiscoveryResponder", "MfDiscoveryResponder", new Object[]{productName,productInstance,uri,userData,encryptUserDataKey});        
             
        myProductName = productName;
        myProductInstance = productInstance;
        myUri = uri;
        myUserData = userData;                                     
        myEncryptUserDataKey = encryptUserDataKey;

        //fix for bugId 5068417
        myStartDate = new Date();
        myTimeStamp = myStartDate.getTime();

        //changed for bugID 5048153 
        //MfDiscoveryResponder assumes encryptUserData
        //is false if encryptUserDataKey is null, true otherwise
        if (encryptUserDataKey != null) {
            myEncryptUserData = true;
        } else {
            myEncryptUserData = false;
        }
        
        //Retrieves common (Agent and CP) config parameters, i.e. mcast group & port
        MfConfig commonConfig = MfConfig.getConfig();            
        myMcastGroup = commonConfig.getProperty("mfwk.multicast.group");
        myMcastPort  = (new Integer(commonConfig.getProperty("mfwk.multicast.port"))).intValue();
        disableLoopBack = (new Boolean(commonConfig.getProperty("mfwk.multicast.disableloopback"))).booleanValue();

        //Added for BugId 5068417
        
        
        logger.config (" McastGroup = " + myMcastGroup +
                         " \nMcastPort = " + myMcastPort +
                         " \nTimeStamp = " + myTimeStamp +
                         " \nProductName = " + myProductName +
                         " \nProductInstance = " + myProductInstance +
                         " \nUri = " + myUri +
                         " \nUserData = " + MfTypeIOUtil.byteArrayToHexString (myUserData) +
                         " \nencryptUserData = " + MfTypeIOUtil.boolToString (myEncryptUserData) +
                         " \nencryptUserDataKey = " + MfTypeIOUtil.byteArrayToHexString (myEncryptUserDataKey));
                         
        if (state==OFFLINE) {
            try {
                state = ONLINE;
                
                // Initializes the Multicast Message to be sent
                // at startup or in reply to Discovery Msg
                MfMcastMessage respMsg = new MfMcastMessage(MfMcastMessage.RESPONSE, 
                                                        myTimeStamp,
                                                        myProductName, 
                                                        myProductInstance,                                                        
                                                        myUri, 
                                                        myUserData, 
                                                        myEncryptUserData, 
                                                        myEncryptUserDataKey, 
                                                        false);		  
                logger.finer("packet sent in reply to disc:\n " + respMsg.printMcastMessage()); 

                // creates responder
		myDiscoveryActualResponder = new MfDiscoveryActualResponder(myMcastGroup, myMcastPort, respMsg);

		//set TTL = 0 => msg does not go out
		myDiscoveryActualResponder.setTimeToLive(TTL);

                //Ensures loopback mode is enable (i.e. we want to receive what is sent on the loopback...)
		myDiscoveryActualResponder.setLoopbackMode(false);
               
                //forces to use the loopback interface (whose IP is given as a parameter)
                //NB default behavior is to use loopBack (enter the if{}),
                //using default interface alternative has been added for solaris 2.8 only
                //BugId 5065408 & BugId 5025737
                if (!disableLoopBack) {
                  InetAddress loopBack = InetAddress.getByName(loopBackIP);
                  myDiscoveryActualResponder.setInterface(loopBack);                 
                }
                logger.finer("interface used: " + myDiscoveryActualResponder.getNetworkInterface().getName());                
                 	
                logger.finer("joins multicast group");                
		myDiscoveryActualResponder.connectToGroup();
	    
                logger.finer("start Listening Thread");                
		myDiscoveryActualResponder.startListeningThread();	      

                logger.info("sends initial RESP message in SDK");                
                //BS TBD send function
                byte myPacketByteArray[];
                myPacketByteArray = respMsg.getByteArrayMcastMessage();
                DatagramPacket myPacket = new DatagramPacket(myPacketByteArray, myPacketByteArray.length,InetAddress.getByName(myMcastGroup),myMcastPort);
                myDiscoveryActualResponder.send(myPacket);
                
            } catch (IOException ioe) {
                logger.warning("Problem occured while creating discovery responder: " + ioe.getMessage());
                throw ioe;
            }
        }   
        logger.exiting("MfDiscoveryResponder", "MfDiscoveryResponder");        
    }
    
    /** Return the product name (includes version)
     * @return Return the product name (includes version)
     */
    public String getProductName() {
        return myProductName;
    }
    
    /** Return the product instance unique indentifier
     * @return Return the product instance unique indentifier
     */
    public String getProductInstance() {
        return myProductInstance;
    }
    
    /** Return the uri to connect to retrieve monitoring information
     * @return Return the uri to connect to retrieve monitoring information
     */
    public String getUri() {
        return myUri;
    }
    
    /** Return the multicast group
     * @return Return the multicast group
     */
    public String getMulticastGroup() {
        return myMcastGroup;
    }
    
    /** Return the multicast port
     * @return Return the multicast port
     */
    public int getMulticastPort() {
        return myMcastPort;
    }
    
    /** Return the user defined data
     * @return Return the user defined data
     */
    public byte[] getUserData() {
        return myUserData;
    }

    /** Return the key used to encode user defined data
     * @return Return the key used to encode user defined data 
     */    
    public byte[] getUserDataKey() {
        return myEncryptUserDataKey;
    }
    
    /** Return a boolean indicating wether encryption has been asked or not
     * if it has then user defined data will be encrypted using the key when sent on Multicast channel
     * @return Return the key used to encode user defined data 
     */    
    public boolean getIsUserDataEncryted() {
        return myEncryptUserData;
    }
    
    
    /** return the current state of the responder
     * @return 1 -> OFFLINE
     * 2 -> ONLINE
     */    
    public int getState() {
        return state;
    }
}
