/*
 * 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        "@(#)MfMcastMessage.java 1.14     04/07/21 SMI"
 *
 */


package com.sun.mfwk.discovery;

// Package Imports
import java.io.*;
import java.net.InetAddress;
import java.util.Properties;
import java.util.Date;

// JESMF security
import com.sun.mfwk.security.crypto.MfCrypto;
import java.security.InvalidKeyException;

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

// Class
class MfMcastMessage implements MfDiscoveryInfo {

    
    /* Tracing utility. */
    private static Logger logger = MfLogService.getLogger("Discovery");
    
    //charset used 
    private static final String I18NCHARSET = "UTF-8";
    private static final String I18NCHARSETNOTFOUND = I18NCHARSET + " charset not found when should be there... Unexpected behaviour might occur";
    private static final String ASCIICHARSET = "US-ASCII";
    private static final String ASCIICHARSETNOTFOUND = ASCIICHARSET + " charset not found when should be there... Unexpected behaviour might occur";

    // variables containing magic number, RESP and DISC msg types
    // do *not* access them directly but use associated methods (get<variable name>AsciiPattern)
    // as these will ensure the correct byte encoding
    private static final String MAGIC = "MFVER1";
    public static final  String DISCOVERY = "DISCOVER";
    public static final  String RESPONSE  = "RESPONSE";
    
    //length of known header fields
    private static int MSGTYPELENGTH = 8;

    //where MSG TYPE is located in the packet
    private static int MSGTYPEOFFSET = MfMcastMessage.getMagicStringAsciiPattern().length +
                                       MfTypeIOUtil.SIZEOF_SHORT;
    //HEADERSIZE is fixed
    private static int HEADERSIZE =  MfMcastMessage.getMagicStringAsciiPattern().length +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MSGTYPELENGTH +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MfTypeIOUtil.SIZEOF_SHORT +
                                     MfTypeIOUtil.SIZEOF_SHORT;
    
    //UDP packet maximum size
    private static int UDPMAXPACKETSIZE = 65536;
    
    //fields used for Packet initialization
    public static int JESVERSION = 3;
    public static int DISCOVERYVERSION = 1;

    //
    //variables representing the Packet
    //
    
    //header (mandatory)
    private byte[] magic = new byte[MfMcastMessage.getMagicStringAsciiPattern().length];
    private int headerSize = 0;
    private byte[] msgType = new byte[MSGTYPELENGTH];
    private int securityLevelBitField = 0x0;
    private int encryptedDataBit = 0x1;
    private int secureConnectionBit = 0x2;
    private int jesVersion = 0;
    private int discoveryVersion = 0;
    private int productSize = 0;
    private int productInstanceSize = 0;
    private int uriSize = 0;
    private int userDataSize = 0;
    private int timeStampSize = 0;
    private int reserved2Size = 0;
    private int reserved3Size = 0;
    private int reserved4Size = 0;
    private int reserved5Size = 0;

    //body
    private byte[] product = null;
    private byte[] productInstance = null;
    private byte[] uri = null;

    //body
    private byte[] userData = null;    
    private long timeStamp = -1;
    private byte[] reserved2 = null;
    private byte[] reserved3 = null;
    private byte[] reserved4 = null;
    private byte[] reserved5 = null;
    
    // Constructor    
    // Builds a Mcast Message using user defined Strings as input
    // This Constructor is intented to be used for encoding Mcast Message           
    public MfMcastMessage(String msgType, 
                        long tStamp,
                        String product, 
                        String productInstance, 
                        String uri,
                        byte[] userdata, 
                        boolean encryptUserData, 
                        byte[] encryptUserDataKey, 
                        boolean secureConnection) throws IllegalArgumentException  {
        
        int mcastMessageSize = 0;
        
        //checks mandatory fields are non-null                
        if ((msgType == null) || (product == null) || (productInstance == null) || (uri == null)) {
            IllegalArgumentException e = new IllegalArgumentException ("msgType, product, productInstance and uri are mandatory parameters");
            throw e;
        }

        //check userdata args consistency
        if (encryptUserData && (encryptUserDataKey == null || userdata == null)) {
            IllegalArgumentException ke = new IllegalArgumentException("userData, encryptUserDataKey cannot be null if encryptUserData is true");
            throw ke;
        }
        
        //sets the encryption bit
        setUserDataEncryptedBit(encryptUserData);
        
        //set the userdata field
        try {
            setUserData(userdata, encryptUserDataKey);
        } catch (InvalidKeyException e) {
             IllegalArgumentException ike = new IllegalArgumentException("Problem occured while encrypting userdata: " + e.getMessage());
             throw ike;
        }
        
        //set the secure connection bit
        setSecureConnectionBit(secureConnection);
        
        try {
            this.msgType = msgType.getBytes(ASCIICHARSET);
        } catch (UnsupportedEncodingException e) {
            logger.severe (ASCIICHARSETNOTFOUND + e.getMessage()); 
            this.msgType = msgType.getBytes();
        }                     
        if ((this.msgType.length != MSGTYPELENGTH) || (!(isResponseTag (this.msgType) || isDiscoveryTag(this.msgType)))) {
            IllegalArgumentException e = new IllegalArgumentException ("msgType not recognized");
            throw e;
        }

        try {
            this.product = product.getBytes(I18NCHARSET);
        } catch (UnsupportedEncodingException e) {    
            logger.severe (I18NCHARSETNOTFOUND + e.getMessage()); 
            this.product = product.getBytes();
        }                
        if (this.product.length <= 0) {
            IllegalArgumentException e = new IllegalArgumentException ("Product has to be set");
            throw e;
        }

        try {
            this.productInstance = productInstance.getBytes(I18NCHARSET);
        } catch (UnsupportedEncodingException e) {    
            logger.severe (I18NCHARSETNOTFOUND + e.getMessage()); 
            this.productInstance = productInstance.getBytes();
        }
        if (this.productInstance.length <= 0) {
            IllegalArgumentException e = new IllegalArgumentException ("Product instance has to be set");
            throw e;
        }

        try {
            this.uri = uri.getBytes(I18NCHARSET);            
        } catch (UnsupportedEncodingException e) {    
            logger.severe (I18NCHARSETNOTFOUND + e.getMessage()); 
            this.uri = uri.getBytes();
        }
        if (this.uri.length <= 0) {
            IllegalArgumentException e = new IllegalArgumentException ("URI has to be set");
            throw e;
        }
        
               
        //sets mandatory fields not defined by user
        this.magic = MfMcastMessage.getMagicStringAsciiPattern();
        this.headerSize = HEADERSIZE;
        this.jesVersion = this.JESVERSION;
        this.discoveryVersion = this.DISCOVERYVERSION;
        this.productSize = this.product.length;
        this.productInstanceSize = this.productInstance.length;
        this.uriSize = this.uri.length;
        if (this.userData != null) 
            this.userDataSize = this.userData.length;

        //  bugId 5068417    
        // reserved 1 used for timestamp
        this.timeStampSize =  MfTypeIOUtil.SIZEOF_LONG;
        this.timeStamp =  tStamp;

        //res 2,3,4,5 not used => set to 0        
        this.reserved2Size = 0;
        this.reserved3Size = 0;
        this.reserved4Size = 0;
        this.reserved5Size = 0;
       
        //finally ensures that total message size does not exceed 
        //the max size of a UDP packet. If it does throws an Exception
        mcastMessageSize = this.headerSize + 
                           this.productSize + 
                           this.productInstanceSize + 
                           this.uriSize + 
                           this.userDataSize +
                           this.timeStampSize +
                           this.reserved2Size +
                           this.reserved3Size +
                           this.reserved4Size +
                           this.reserved5Size;
        
        if (mcastMessageSize > UDPMAXPACKETSIZE) {
            IllegalArgumentException e = new IllegalArgumentException ("Total length of an Mcast Message cannot exceed " +  UDPMAXPACKETSIZE);
            throw e;
        }
    }
        
    // Constructor
    // Builds a Mcast Message using a byte array as input
    // This Constructor is intented to be used for decoding Mcast Message
    // It performs many initialization checks before proceeding
    
    protected MfMcastMessage(byte[] mcastPacket) throws IllegalArgumentException, IOException  {

        //checks wether it is a Java ES-MF Mcast Packet
        //if not throws an exception
        if (!(isMcastMessage(mcastPacket))) {
            IllegalArgumentException e = new IllegalArgumentException("Packet is not a Java ES-MF multicast Packet");
            logger.info ("Packet is not a Java ES-MF multicast Packet");
            throw e;
        }
        
        //creates a byte input stream with the byte array received
        //and fills in object private fields with found values

        ByteArrayInputStream input = new ByteArrayInputStream(mcastPacket);
        
        input.read(magic,0,MfMcastMessage.getMagicStringAsciiPattern().length);
        logger.finer ("magic:" + magic.toString());

        headerSize       = MfTypeIOUtil.readShort(input);
        logger.finer ("headerSize:" + headerSize);
        input.read(msgType,0,MSGTYPELENGTH);
        logger.finer ("msgType:" + msgType.toString());
        securityLevelBitField    = MfTypeIOUtil.readShort(input);       
        logger.finer ("securityLevelBitField:" + securityLevelBitField);
        jesVersion       = MfTypeIOUtil.readShort(input);
        logger.finer ("jesVersion:" + jesVersion);
        discoveryVersion = MfTypeIOUtil.readShort(input);
        logger.finer ("discoveryVersion:" + discoveryVersion);

        productSize         = MfTypeIOUtil.readShort(input);
        logger.finer ("productSize:" + productSize);
        productInstanceSize = MfTypeIOUtil.readShort(input);
        logger.finer ("productInstanceSize:" + productInstanceSize);
        uriSize             = MfTypeIOUtil.readShort(input);
        logger.finer ("uriSize:" + uriSize);
        userDataSize        = MfTypeIOUtil.readShort(input);        
        logger.finer ("userDataSize:" + userDataSize);
        timeStampSize        = MfTypeIOUtil.readShort(input);
        logger.finer ("timeStampSize:" + timeStampSize);
        reserved2Size        = MfTypeIOUtil.readShort(input);
        logger.finer ("reserved2Size:" + reserved2Size);
        reserved3Size        = MfTypeIOUtil.readShort(input);        
        logger.finer ("reserved3Size:" + reserved3Size);
        reserved4Size        = MfTypeIOUtil.readShort(input);
        logger.finer ("reserved4Size:" + reserved4Size);
        reserved5Size        = MfTypeIOUtil.readShort(input);        
        logger.finer ("reserved5Size:" + reserved5Size);

        product =  new byte[productSize];
        input.read (product,0,productSize);
        logger.finer ("product:" + product.toString());
        
        productInstance =  new byte[productInstanceSize];
        input.read (productInstance,0,productInstanceSize);
        logger.finer ("productInstance:" + productInstance.toString());
     
        uri = new byte[uriSize];
        input.read (uri,0,uriSize);
        logger.finer ("uri:" + uri.toString());
        
        if (userDataSize != 0) 
        {
          userData = new byte[userDataSize];
          input.read (userData,0,userDataSize);
        }

        if (timeStampSize != 0) 
        {
          timeStamp = MfTypeIOUtil.readLong(input);
        }

        if (reserved2Size != 0) 
        {
          reserved2 = new byte[reserved2Size];
          input.read (reserved2,0,reserved2Size);
        }
        
        if (reserved3Size != 0) 
        {
          reserved3 = new byte[reserved3Size];
          input.read (reserved3,0,reserved3Size);
        }        
        
        if (reserved4Size != 0) 
        {
          reserved4 = new byte[reserved4Size];
          input.read (reserved4,0,reserved4Size);
        }       
        
        if (reserved5Size != 0) 
        {
          reserved5 = new byte[reserved5Size];
          input.read (reserved5,0,reserved5Size);
        }       
    }


    //returns byte array representation of Discovery encoded using "US-ASCII" representation
    public static byte[] getDiscoveryStringAsciiPattern()
    {
      byte[] asciiDiscovery = null;
      
      try {          
        asciiDiscovery =  MfMcastMessage.DISCOVERY.getBytes(ASCIICHARSET);        
      } catch (UnsupportedEncodingException e) {
          logger.severe (ASCIICHARSETNOTFOUND + e.getMessage()); 
          asciiDiscovery =  MfMcastMessage.DISCOVERY.getBytes();        
      }
      return asciiDiscovery;
    }    

    //returns byte array representation of Response encoded using "US-ASCII" representation
    public static byte[] getResponseStringAsciiPattern()
    {
      byte[] asciiResponse = null;
      
      try {          
        asciiResponse =  MfMcastMessage.RESPONSE.getBytes(ASCIICHARSET);        
      } catch (UnsupportedEncodingException e) {
          logger.warning (ASCIICHARSETNOTFOUND + e.getMessage()); 
          asciiResponse =  MfMcastMessage.RESPONSE.getBytes();        
      }
      return asciiResponse;
    }    

    
    /////////
    //getters
    /////////
    
        
    //returns String representation of the magic byte array
    public String getMagic() {
        String magicStr = null;
        try {
            magicStr = new String (magic,ASCIICHARSET);
        } catch (UnsupportedEncodingException e) {
            logger.severe (ASCIICHARSETNOTFOUND + e.getMessage()); 
            magicStr = new String (magic);
        }
        return magicStr;
    } 
    

    
    //returns "official" magic byte array representation of MAGIC string 
    //encoded using "US-ASCII" representation
    public static byte[] getMagicStringAsciiPattern()
    {
      byte[] asciiMagic = null;
      
      try {          
        asciiMagic =  MfMcastMessage.MAGIC.getBytes(ASCIICHARSET);        
      } catch (UnsupportedEncodingException e) {
          logger.severe (ASCIICHARSETNOTFOUND + e.getMessage()); 
          asciiMagic =  MfMcastMessage.MAGIC.getBytes();        
      }
      return asciiMagic;
    }
    
    
    
    
    
    private int getHeaderSize() {
        return headerSize;
    } 

    private int getSecurityLevelBitField() {
        return securityLevelBitField;
    } 

    private int getProductSize() {
        return productSize;
    } 

    private int getProductInstanceSize() {
        return productInstanceSize;
    }     

    //BS tb moved to private
    public int getUriSize() {
        return uriSize;
    } 
    
    private int getUserDataSize() {
        return userDataSize;
    } 

    //BS tb moved to private
    public String getMsgType() {
        String type = null;
        try {
            type = new String (msgType,ASCIICHARSET);
        } catch (UnsupportedEncodingException e) {
            logger.severe (ASCIICHARSETNOTFOUND + e.getMessage()); 
            type = new String (msgType);
        }
        return type;
    }

    private int getJesVersion() {
        return jesVersion;
    }

    private int getDiscoveryVersion() {
        return discoveryVersion;
    }
    
    public String getProduct() {
        String prod = null;
        try {
            prod = new String (product,I18NCHARSET);
        } catch (UnsupportedEncodingException e) {
            logger.severe (I18NCHARSETNOTFOUND + e.getMessage()); 
            prod = new String (product);
        }
        return prod;               
    }
        
    public String getProductInstance() {
        String prodInst = null;
        try {
            prodInst = new String (productInstance,I18NCHARSET);
        } catch (UnsupportedEncodingException e) {
            logger.severe (I18NCHARSETNOTFOUND + e.getMessage()); 
            prodInst = new String (productInstance);
        }
        return prodInst;                      
    }
 
    public String getUri() {
        String uriStr = null;
        try {
            uriStr = new String (uri,I18NCHARSET);
        } catch (UnsupportedEncodingException e) {
            logger.severe (I18NCHARSETNOTFOUND + e.getMessage()); 
            uriStr = new String (uri);
        }
        return uriStr;    
    }
       
    //returns raw user data (does not decrypt if data is encrypted)
    public byte[] getUserData() {
       return this.userData ;
    }

    protected byte[] getUserData(byte[] key) throws InvalidKeyException {
        if (isUserDataEncrypted()) {
                MfCrypto jc = new MfCrypto(key);
                byte [] decryptedUserData = jc.decrypt(this.userData);
                return decryptedUserData;                    
        } else {
            //no need to decrypt
            return this.userData;
        }
    }

    public long getTimeStamp() {
        return this.timeStamp;
    }
        
    //setters
    protected void setUserDataEncryptedBit(boolean setBit) {
      if (setBit) {
          this.securityLevelBitField |= this.encryptedDataBit;         
      } else {
          this.securityLevelBitField &= ~this.encryptedDataBit;         
      }
    }

    protected void setSecureConnectionBit(boolean setBit) {
      if (setBit) {
          this.securityLevelBitField |= this.secureConnectionBit;         
      } else {
          this.securityLevelBitField &= ~this.secureConnectionBit;         
      }
    }

    protected void setUserData(byte[] data) {       
            this.userData = data;
    }
    
    protected void setUserData(byte[] data, byte[] key) throws InvalidKeyException {
        if (isUserDataEncrypted() && (key != null)) {
                MfCrypto jc = new MfCrypto(key);
                this.userData = jc.encrypt(data);
        } else {
            //no need to encrypt
            this.userData = data;
        }
    }
    
    protected void setUserDataSize(int size) {
      this.userDataSize = size;         
    }
    
   // returns byte formatted Mcast message
   public byte[] getByteArrayMcastMessage() throws IOException {
         
        ByteArrayOutputStream output = new ByteArrayOutputStream();        

        //write header
        output.write(magic,0,MfMcastMessage.getMagicStringAsciiPattern().length);
        MfTypeIOUtil.writeShort(output, (short) headerSize);        
        output.write (msgType,0,MSGTYPELENGTH);
        MfTypeIOUtil.writeShort(output, (short) securityLevelBitField);        
        MfTypeIOUtil.writeShort(output, (short) jesVersion);        
        MfTypeIOUtil.writeShort(output, (short) discoveryVersion);
        MfTypeIOUtil.writeShort(output, (short) productSize);        
        MfTypeIOUtil.writeShort(output, (short) productInstanceSize);                
        MfTypeIOUtil.writeShort(output, (short) uriSize);        
        MfTypeIOUtil.writeShort(output, (short) userDataSize);        
        MfTypeIOUtil.writeShort(output, (short) timeStampSize);        
        MfTypeIOUtil.writeShort(output, (short) reserved2Size);        
        MfTypeIOUtil.writeShort(output, (short) reserved3Size);        
        MfTypeIOUtil.writeShort(output, (short) reserved4Size);        
        MfTypeIOUtil.writeShort(output, (short) reserved5Size);        

        output.write (product,0,productSize);
        output.write (productInstance,0,productInstanceSize);
        output.write (uri,0,uriSize);

        if (userDataSize != 0)
            output.write (userData,0,userDataSize);
        if (timeStampSize != 0)
            MfTypeIOUtil.writeLong(output, (long) timeStamp);        
        if (reserved2Size != 0)
            output.write (reserved2,0,reserved2Size);
        if (reserved3Size != 0)
            output.write (reserved3,0,reserved3Size);
        if (reserved4Size != 0)
            output.write (reserved4,0,reserved4Size);
        if (reserved5Size != 0)
            output.write (reserved5,0,reserved5Size);

        return (output.toByteArray());
    }


    
    //checking method
    public boolean isUserDataEncrypted() {
      if ((this.securityLevelBitField & this.encryptedDataBit) == this.encryptedDataBit) {
          return true;
      }
      return false;
    }
        
    public boolean isSecureConnection() {
      if ((this.securityLevelBitField & this.secureConnectionBit) == this.secureConnectionBit) {
          return true;
      }
      return false;
    }
    

    
    private static boolean checkMagicNumber( final ByteArrayInputStream input )
        throws IOException
    {
        final byte[] magic = MfMcastMessage.getMagicStringAsciiPattern();
        for( int i = 0; i < magic.length; i++ )
        {
            final byte b = magic[ i ];
            final int in = input.read();
            if( b != in )
            {
                return false;
            }
        }
        return true;
    }

    //checks wether the packet given as arg is of type discovery  (Java ES-MF packet assumed)
    protected static boolean isDiscoveryPacket(final  byte[] packet)
    {
        byte[] discovery = new byte[MSGTYPELENGTH];
        if (packet.length >= HEADERSIZE) {
            System.arraycopy (packet, MSGTYPEOFFSET, discovery, 0, MSGTYPELENGTH);
            if (isDiscoveryTag(discovery)) {
                logger.finer("Packet is a Discovery Packet");
                return true;
            }
        }
        logger.finer("Not a Discovery Packet");
        return false;
    }

    //checks wether the array given as arg is equal to MfMcastMessage.getDiscoveryStringAsciiPattern()
    private static boolean isDiscoveryTag( final  byte[] msgType)
    {
        final byte[] discovery = MfMcastMessage.getDiscoveryStringAsciiPattern();         
        for( int i = 0; i < MSGTYPELENGTH; i++ )
        {
             if (discovery[ i ] != msgType[i])
             {
                return false;
             }
        }
         return true;           
    }
    
    //checks wether the packet given as arg is of type response  (Java ES-MF packet assumed)
    protected static boolean isResponsePacket(final  byte[] packet)
    {
        byte[] response = new byte[MSGTYPELENGTH];
        if (packet.length >= HEADERSIZE) {
            System.arraycopy (packet, MSGTYPEOFFSET, response, 0, MSGTYPELENGTH);
            if (isResponseTag(response)) {
                logger.finer("Packet is a Response Packet");
                return true;
            }
        }
        logger.finer("Not a Response Packet");
        return false;
    }

    //checks wether the array given as arg is equal to MfMcastMessage.getResponseStringAsciiPattern()
    private static boolean isResponseTag( final byte[] msgType)
    {
        final byte[] response = MfMcastMessage.getResponseStringAsciiPattern();
        for( int i = 0; i < MSGTYPELENGTH; i++ )
        {
            if (response[ i ] != msgType[i])
            {
                return false;
            }
        }
        return true;
    }
    
    protected static boolean isMcastMessage(byte[] mcastPacket) throws IOException {

        int securityLevel;
        int headerSize = 0;
        int productSize = 0;
        int productInstanceSize = 0;
        int uriSize = 0;
        int userDataSize = 0;
        int timeStampSize = 0;
        int reserved2Size = 0;
        int reserved3Size = 0;
        int reserved4Size = 0;
        int reserved5Size = 0;
        int totalSize;
        int jesVersion = 0;
        int discoveryVersion = 0;
        byte[] msgType = new byte[MSGTYPELENGTH];

           
        ByteArrayInputStream input = new ByteArrayInputStream(mcastPacket);
        
        //does it have the minimal required length?
        if ((mcastPacket.length < HEADERSIZE) || (mcastPacket.length > UDPMAXPACKETSIZE)) {
            logger.info("Packet too small or too big, size:" + mcastPacket.length);
            return false;
        }
        
        //is this a Java ES-MF packet?
        if (!checkMagicNumber(input)) {
            logger.info("Magic Number not recognized");
            return false;
        }
        
        //checks packet length info is coherent with actual packet size
        headerSize = MfTypeIOUtil.readShort(input);        

        //set a mark to get back to further processing once size has been checked
        //skips unnecessary values
        input.mark(mcastPacket.length - (MfMcastMessage.getMagicStringAsciiPattern().length + headerSize));
        input.skip (MSGTYPELENGTH + MfTypeIOUtil.SIZEOF_SHORT + MfTypeIOUtil.SIZEOF_SHORT + MfTypeIOUtil.SIZEOF_SHORT);
        
        productSize =  MfTypeIOUtil.readShort(input);
        productInstanceSize =  MfTypeIOUtil.readShort(input);
        uriSize =  MfTypeIOUtil.readShort(input);
        userDataSize =  MfTypeIOUtil.readShort(input);
        timeStampSize =  MfTypeIOUtil.readShort(input);
        reserved2Size =  MfTypeIOUtil.readShort(input);
        reserved3Size =  MfTypeIOUtil.readShort(input);
        reserved4Size =  MfTypeIOUtil.readShort(input);
        reserved5Size =  MfTypeIOUtil.readShort(input);

        totalSize = headerSize + 
                    productSize + 
                    productInstanceSize + 
                    uriSize + 
                    userDataSize +
                    timeStampSize +
                    reserved2Size +
                    reserved3Size +
                    reserved4Size +
                    reserved5Size;
        
        if (totalSize != (short) mcastPacket.length){
            logger.info("Sum of header + body size should be equal to packet length" + "\n" +
                        "headerSize: " + headerSize + "\n" +
                        "productSize: " + productSize + "\n" +
                        "productInstanceSize: " + productInstanceSize + "\n" +
                        "uriSize: "+ uriSize + "\n" +
                        "userDataSize: " + userDataSize + "\n" +
                        "timeStampSize: " + timeStampSize + "\n" +
                        "reserved2Size: " + reserved2Size + "\n" +
                        "reserved3Size: " + reserved3Size + "\n" +
                        "reserved4Size: " + reserved4Size + "\n" +
                        "reserved5Size: " + reserved5Size + "\n" +
                        "total size: " + totalSize + "\n" +
                        "packet size: " + mcastPacket.length);            
            return false;
        }
        
        //reset to the previously set mark
        input.reset();

        //reads in msgtype and ensure it is a known one        
        input.read(msgType,0, MSGTYPELENGTH);
        if (!((isResponseTag(msgType) || isDiscoveryTag(msgType)))) {
            logger.info("Unknown message type");
            return false;
        }

        //reads in security level and does nothing
        securityLevel = MfTypeIOUtil.readShort(input);
        
        //reads in JES version & does nothing
        jesVersion = MfTypeIOUtil.readShort(input);
        
        //reads in discovery version & ensure it is compatible
        //with our version
        discoveryVersion = MfTypeIOUtil.readShort(input);
        if (discoveryVersion != DISCOVERYVERSION) {
            logger.info("Unable to deal with this discovery version: " + discoveryVersion);
            return false;
        }
              
        //everything seem to be ok
        logger.finer("Packet seems to be a Java ES-MF packet");
        return true;
    }
    
    //utils for debug
    //should not be public
    public String printMcastMessage() {
        return       ("magic: "                + getMagic()                           + "/ " + getMagicByteArrayRepresentation() + "\n" +                      
                     "headerSize: "            + getHeaderSize()                      + "\n" +
                     "msgType: "               + getMsgType()                         + "/ " + getMsgTypeByteArrayRepresentation() + "\n" + 
                     "securityLevelBitField: " + getSecurityLevelBitField()           + "\n" +
                     "JesVersion: "            + getJesVersion()                      + "\n" +
                     "DiscoveryVersion: "      + getDiscoveryVersion()                + "\n" +
                     "ProductSize: "           + getProductSize()                     + "\n" +
                     "ProductInstanceSize: "   + getProductInstanceSize()             + "\n" +
                     "uriSize: "               + getUriSize()                         + "\n" +
                     "userDataSize: "          + getUserDataSize()                    + "\n" +
                     "timeStampSize: "         + timeStampSize                        + "\n" +
                     "Product: "               + getProduct()                         + "/ " + getProductByteArrayRepresentation() + "\n" +
                     "Product Instance: "      + getProductInstance()                 + "/ " + getProductInstanceByteArrayRepresentation() + "\n" +
                     "Uri: "                   + getUri()                             + "/ " + getUriByteArrayRepresentation()               + "\n" +        
                     "userData: "              + getUserDataByteArrayRepresentation() + "\n" +
                     "timeStamp: "             + timeStamp                            + "/ " + getTimeStampByteArrayRepresentation()               + "/ " + Long.toHexString(timeStamp) + "\n" +
                     "dump: "                  + getPacketDumpByteArrayRepresentation() + "\n");
    }
    
    //returns String containing byte array, for debug puposes
    private String getMagicByteArrayRepresentation() {
        return MfTypeIOUtil.byteArrayToHexString (this.magic);
    }     

    private String getMsgTypeByteArrayRepresentation() {
        return MfTypeIOUtil.byteArrayToHexString (this.msgType);
    }     
    
    private String getProductByteArrayRepresentation() {
        return MfTypeIOUtil.byteArrayToHexString (this.product);
    }
    
    private String getProductInstanceByteArrayRepresentation() {
        return MfTypeIOUtil.byteArrayToHexString (this.productInstance);
    }    
    
    private String getUriByteArrayRepresentation() {
        return MfTypeIOUtil.byteArrayToHexString (this.uri);
    }    

    private String getUserDataByteArrayRepresentation() {
        return MfTypeIOUtil.byteArrayToHexString (this.userData);
    }    

    private String getTimeStampByteArrayRepresentation() {
        ByteArrayOutputStream output = new ByteArrayOutputStream();        
        try {
            MfTypeIOUtil.writeLong(output,this.timeStamp);
        } catch (Exception e) {
            return null;
        }
        return MfTypeIOUtil.byteArrayToHexString (output.toByteArray());
    }    

    private String getReserved2ByteArrayRepresentation() {
        return MfTypeIOUtil.byteArrayToHexString (this.reserved2);
    }    
    
    private String getReserved3ByteArrayRepresentation() {
        return MfTypeIOUtil.byteArrayToHexString (this.reserved3);
    }    
    
    private String getReserved4ByteArrayRepresentation() {
        return MfTypeIOUtil.byteArrayToHexString (this.reserved4);
    }    
    
    private String getReserved5ByteArrayRepresentation() {
        return MfTypeIOUtil.byteArrayToHexString (this.reserved5);
    }       
    
    private String getPacketDumpByteArrayRepresentation() {
        byte[] dump;  
        try {
          dump = this.getByteArrayMcastMessage();  
        } catch (Exception e) {
            return null;
        }
        return MfTypeIOUtil.byteArrayToHexString (dump);
    }     
    
}



