/***********************************************************************
 * 
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *  
 ***********************************************************************/

package com.novell.casa.authtoksvc;

import java.io.InputStream;
import java.util.Arrays;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import org.apache.log4j.Logger;


/**
 * AuthReqMsg Class.
 * <p>
 * This class deals with the message sent by Casa Client when requesting
 * that an entity be authenticated. The format of the message is as
 * follows:
 * <p>
 *    <?xml version="1.0" encoding="UTF-8"?>
 *    <auth_req>
 *    <realm>realm value</realm>
 *    <mechanism>mechanism id</mechanism>
 *    <auth_mech_token>mechanism token data</auth_mech_token>
 *    </auth_req>
 * 
 */
public final class AuthReqMsg
{
   private static final Logger m_log = Logger.getLogger(AuthReqMsg.class);

   protected String  m_realm = null;
   protected char[]  m_authMechToken = null;
   protected String  m_authMechanism = null;

   /**
    * Class for handling Authentication Request parsing events.
    */
   private final class SAXHandler extends org.xml.sax.helpers.DefaultHandler
   {
      private final static int AWAITING_ROOT_ELEMENT_START = 0;
      private final static int AWAITING_ROOT_ELEMENT_END = 1;
      private final static int AWAITING_REALM_ELEMENT_START = 2;
      private final static int AWAITING_REALM_ELEMENT_END = 3;
      private final static int AWAITING_REALM_DATA = 4;
      private final static int AWAITING_MECH_ELEMENT_START = 5;
      private final static int AWAITING_MECH_ELEMENT_END = 6;
      private final static int AWAITING_MECH_DATA = 7;
      private final static int AWAITING_AUTH_MECH_TOKEN_ELEMENT_START = 8;
      private final static int AWAITING_AUTH_MECH_TOKEN_ELEMENT_END = 9;
      private final static int AWAITING_AUTH_MECH_TOKEN_DATA = 10;
      private final static int DONE_PARSING = 11;

      private final AuthReqMsg   m_authReqMsg;
      private int m_state;

      /**
       * Constructor.
       *
       * @param authReqMsg Authentication request message object.
       */
      public SAXHandler (AuthReqMsg authReqMsg)
      {
         super();

         // Initialize our members
         m_authReqMsg = authReqMsg;
         m_state = AWAITING_ROOT_ELEMENT_START;
      }

      /**
       * endDocument() implementation.
       *
       * @throws SAXException
       */
      public final void endDocument () throws SAXException
      {
         // Verify that we obtained all of the required elements
         if (m_state != DONE_PARSING)
         {
            m_log.error("SAXHandler.endDocument()- Missing element");
            throw new SAXException("Missing element");
         }
      }

      /**
       * startElement() implementation.
       *
       * @param uri     Uri.
       * @param name    Local name.
       * @param qName   Qualified name.
       * @param atts    Attributes.
       * @throws SAXException
       */
      public final void startElement (String uri, String name, String qName, org.xml.sax.Attributes atts) throws SAXException
      {
         // Proceed based on our state
         switch (m_state)
         {
            case AWAITING_ROOT_ELEMENT_START:
               // Verify that we are processing the expected tag
               if (ProtoDefs.authRequestElementName.equalsIgnoreCase(qName))
               {
                  // Advance to the next state
                  m_state = AWAITING_REALM_ELEMENT_START;
               }
               else
               {
                  m_log.error("SAXHandler.startElement()- Un-expected element");
                  throw new SAXException("Un-expected element");
               }
               break;

            case AWAITING_REALM_ELEMENT_START:
               // Verify that we are processing the expected tag
               if (ProtoDefs.realmElementName.equalsIgnoreCase(qName))
               {
                  // Advance to the next state
                  m_state = AWAITING_REALM_DATA;
               }
               else
               {
                  m_log.error("SAXHandler.startElement()- Un-expected element");
                  throw new SAXException("Un-expected element");
               }
               break;

            case AWAITING_MECH_ELEMENT_START:
               // Verify that we are processing the expected tag
               if (ProtoDefs.mechanismElementName.equalsIgnoreCase(qName))
               {
                  // Advance to the next state
                  m_state = AWAITING_MECH_DATA;
               }
               else
               {
                  m_log.error("SAXHandler.startElement()- Un-expected element");
                  throw new SAXException("Un-expected element");
               }
               break;

            case AWAITING_AUTH_MECH_TOKEN_ELEMENT_START:
               // Verify that we are processing the expected tag
               if (ProtoDefs.authMechTokenElementName.equalsIgnoreCase(qName))
               {
                  // Advance to the next state
                  m_state = AWAITING_AUTH_MECH_TOKEN_DATA;
               }
               else
               {
                  m_log.error("SAXHandler.startElement()- Un-expected element");
                  throw new SAXException("Un-expected element");
               }
               break;

            default:
               m_log.error("SAXHandler.startElement()- State error");
               throw new SAXException("State error");
         }
      }

      /**
       * endElement() immplementation.
       *
       * @param uri     Uri.
       * @param name    Local name.
       * @param qName   Qualified name.
       * @throws SAXException
       */
      public final void endElement (String uri, String name, String qName) throws SAXException
      {
         // Proceed based on our state
         switch (m_state)
         {
            case AWAITING_ROOT_ELEMENT_END:
               // Verify that we are processing the expected tag
               if (ProtoDefs.authRequestElementName.equalsIgnoreCase(qName))
               {
                  // Advance to the next state
                  m_state = DONE_PARSING;
               }
               else
               {
                  m_log.error("SAXHandler.endElement()- Un-expected element");
                  throw new SAXException("Un-expected element");
               }
               break;

            case AWAITING_REALM_ELEMENT_END:
               // Verify that we are processing the expected tag
               if (ProtoDefs.realmElementName.equalsIgnoreCase(qName))
               {
                  // Advance to the next state
                  m_state = AWAITING_MECH_ELEMENT_START;
               }
               else
               {
                  m_log.error("SAXHandler.endElement()- Un-expected element");
                  throw new SAXException("Un-expected element");
               }
               break;

            case AWAITING_MECH_ELEMENT_END:
               // Verify that we are processing the expected tag
               if (ProtoDefs.mechanismElementName.equalsIgnoreCase(qName))
               {
                  // Advance to the next state
                  m_state = AWAITING_AUTH_MECH_TOKEN_ELEMENT_START;
               }
               else
               {
                  m_log.error("SAXHandler.endElement()- Un-expected element");
                  throw new SAXException("Un-expected element");
               }
               break;

            case AWAITING_AUTH_MECH_TOKEN_ELEMENT_END:
               // Verify that we are processing the expected tag
               if (ProtoDefs.authMechTokenElementName.equalsIgnoreCase(qName))
               {
                  // Advance to the next state
                  m_state = AWAITING_ROOT_ELEMENT_END;
               }
               else
               {
                  m_log.error("SAXHandler.endElement()- Un-expected element");
                  throw new SAXException("Un-expected element");
               }
               break;

            default:
               m_log.error("SAXHandler.endElement()- State error");
               throw new SAXException("State error");
         }
      }

      /**
       * character() implementation.
       *
       * @param ch      Characters with element data.
       * @param start   Start position in the character array.
       * @param length  Number of characters to use in the array.
       * @throws SAXException
       */
      public final void characters (char ch[], int start, int length) throws SAXException
      {
         // Proceed based on our state
         switch (m_state)
         {
            case AWAITING_REALM_DATA:
               // Consume the data
               m_authReqMsg.m_realm = new String(ch, start, length);

               // Advance to the next state
               m_state = AWAITING_REALM_ELEMENT_END;
               break;

            case AWAITING_REALM_ELEMENT_END:
               // Consume the data
               m_authReqMsg.m_realm = m_authReqMsg.m_realm.concat(new String(ch, start, length));
               break;

            case AWAITING_MECH_DATA:
               // Consume the data
               m_authReqMsg.m_authMechanism = new String(ch, start, length);

               // Advance to the next state
               m_state = AWAITING_MECH_ELEMENT_END;
               break;

            case AWAITING_MECH_ELEMENT_END:
               // Consume the data
               m_authReqMsg.m_authMechanism = m_authReqMsg.m_authMechanism.concat(new String(ch, start, length));
               break;

            case AWAITING_AUTH_MECH_TOKEN_DATA:
               // Consume the data
               //
               // Allocate array to consume the data
               m_authReqMsg.m_authMechToken = new char[length];

               // Copy the data to the newly allocated array
               System.arraycopy(ch, start, m_authReqMsg.m_authMechToken, 0, length);

               // Clear the token data in the source array for security purposes
               Arrays.fill(ch, start, start + length, ' ');

               // Advance to the next state
               m_state = AWAITING_AUTH_MECH_TOKEN_ELEMENT_END;
               break;

            case AWAITING_AUTH_MECH_TOKEN_ELEMENT_END:
               // Consume the data
               //
               // Allocate array to contain the already consumed data and the
               // data to be consumed.
               char[] newSink = new char[m_authReqMsg.m_authMechToken.length + length];

               // Copy the already consumed data and the data to be consumed to the
               // newly allocated array.
               System.arraycopy(m_authReqMsg.m_authMechToken, 0, newSink, 0, m_authReqMsg.m_authMechToken.length);
               System.arraycopy(ch, start, newSink, m_authReqMsg.m_authMechToken.length, length);

               // Clear the token data in the previous data sink for security purposes
               Arrays.fill(m_authReqMsg.m_authMechToken, ' ');

               // Keep track of the new token data sink array
               m_authReqMsg.m_authMechToken = newSink;

               // Clear the token data in the source array for security purposes
               Arrays.fill(ch, start, start + length, ' ');
               break;

            default:
               // Do nothing
               break;
         }
      }
   }

   /**
    * Constructor.
    *
    * @param inStream   Input stream with authentication request data.
    * @throws Exception
    */
   public AuthReqMsg (InputStream inStream) throws Exception
   {
      try
      {
         // Parse the AuthReqMsg
         XMLReader xr = XMLReaderFactory.createXMLReader();
         SAXHandler handler = new SAXHandler(this);
         xr.setContentHandler(handler);
         xr.setErrorHandler(handler);

         InputSource source = new InputSource(inStream);
         xr.parse(source);
      }
      catch (SAXException e)
      {
         m_log.error("Constructor()- Parse exception: " + e.toString());
         throw new Exception("Protocol error", e);
      }
   }

   /**
    * Method to cleanup resources associated with the object. This
    * method needs to be executed for objects that are no longer of
    * use for security reasons.
    */
   public void cleanup()
   {
      // Clear the token data if present
      if (m_authMechToken != null)
         Arrays.fill(m_authMechToken, ' ');
   }

   /**
    * Finalize method.
    *
    * @throws Throwable
    */
   protected void finalize() throws Throwable
   {
      try
      {
         cleanup();
      }
      finally
      {
         super.finalize();
      }
   }

   /**
    * Method to get the authentication realm.
    *
    * @return  Authentication realm name.
    */
   public String getRealm()
   {
      return m_realm;
   }

   /**
    * Method to get the authentication mechanism token.
    *
    * @return  Authentication mechanism token.
    */
   public char[] getAuthMechToken()
   {
      return m_authMechToken;
   }

   /**
    * Method to get the authentication mechanism id.
    *
    * @return  Id of authentication mechanism targeted.
    */
   public String getMechanismId()
   {
      return m_authMechanism;
   }
}
