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


//===[ Include files ]=====================================================

#include "internal.h"

//===[ Type definitions ]==================================================

//
// Parse states
//
#define AWAITING_ROOT_ELEMENT_START          0x0
#define AWAITING_ROOT_ELEMENT_END            0x1
#define AWAITING_SIGNATURE_DATA              0x2
#define AWAITING_SIGNATURE_ELEMENT_START     0x3
#define AWAITING_SIGNATURE_ELEMENT_END       0x4
#define AWAITING_LIFETIME_DATA               0x5
#define AWAITING_LIFETIME_ELEMENT_START      0x6
#define AWAITING_LIFETIME_ELEMENT_END        0x7
#define AWAITING_IDENT_TOKEN_ELEMENT_START   0x8
#define AWAITING_IDENT_TOKEN_ELEMENT_END     0x9
#define AWAITING_IDENT_TOKEN_DATA            0xA
#define AWAITING_TYPE_ELEMENT_START          0xB
#define AWAITING_TYPE_ELEMENT_END            0xC
#define AWAITING_TYPE_DATA                   0xD
#define DONE_PARSING                         0xE

//
// Authentication Token Parse Structure
//
typedef struct _AuthTokenParse
{
   XML_Parser           p;
   int                  state;
   int                  elementDataProcessed;
   AuthToken            *pAuthToken;
   CasaStatus           status;

} AuthTokenParse, *PAuthTokenParse;


//===[ Function prototypes ]===============================================

//===[ Global variables ]==================================================

//++=======================================================================
static
void XMLCALL
AuthTokenStartElementHandler(
   IN    void *pUserData,
   IN    const XML_Char *name,
   IN    const XML_Char **atts)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   AuthTokenParse    *pAuthTokenParse = (AuthTokenParse*) pUserData;

   DbgTrace(2, "-AuthTokenStartElementHandler- Start\n", 0);

   // Proceed based on the state
   switch (pAuthTokenParse->state)
   {
      case AWAITING_ROOT_ELEMENT_START:

         // In this state, we are only expecting the Authentication
         // Response Element.
         if (strcmp(name, AUTH_TOKEN_ELEMENT_NAME) == 0)
         {
            // Good, advance to the next state.
            pAuthTokenParse->state = AWAITING_SIGNATURE_ELEMENT_START;
         }
         else
         {
            DbgTrace(0, "-AuthTokenStartElementHandler- Un-expected start element\n", 0);
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;

      case AWAITING_SIGNATURE_ELEMENT_START:
   
         // In this state, we are only expecting the Signature Element.
         if (strcmp(name, SIGNATURE_ELEMENT_NAME) == 0)
         {
            // Good, advance to the next state.
            pAuthTokenParse->state = AWAITING_SIGNATURE_DATA;
         }
         else
         {
            DbgTrace(0, "-AuthTokenStartElementHandler- Un-expected start element\n", 0);
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;

      case AWAITING_LIFETIME_ELEMENT_START:
   
         // In this state, we are only expecting the Lifetime Element.
         if (strcmp(name, LIFETIME_ELEMENT_NAME) == 0)
         {
            // Good, advance to the next state.
            pAuthTokenParse->state = AWAITING_LIFETIME_DATA;
         }
         else
         {
            DbgTrace(0, "-AuthTokenStartElementHandler- Un-expected start element\n", 0);
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;

      case AWAITING_IDENT_TOKEN_ELEMENT_START:
   
         // In this state, we are only expecting the Identity Token Element.
         if (strcmp(name, IDENTITY_TOKEN_ELEMENT_NAME) == 0)
         {
            // Good, advance to the next state.
            pAuthTokenParse->state = AWAITING_TYPE_ELEMENT_START;
         }
         else
         {
            DbgTrace(0, "-AuthTokenStartElementHandler- Un-expected start element\n", 0);
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;
   
      case AWAITING_TYPE_ELEMENT_START:
   
         // In this state, we are only expecting the Type Element.
         if (strcmp(name, TYPE_ELEMENT_NAME) == 0)
         {
            // Good, advance to the next state.
            pAuthTokenParse->state = AWAITING_TYPE_DATA;
         }
         else
         {
            DbgTrace(0, "-AuthTokenStartElementHandler- Un-expected start element\n", 0);
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;

      default:
         DbgTrace(0, "-AuthTokenStartElementHandler- Un-expected state = %d\n", pAuthTokenParse->state);
         XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         break;
   }

   DbgTrace(2, "-AuthTokenStartElementHandler- End\n", 0);
}


//++=======================================================================
static
CasaStatus
ConsumeElementData(
   IN    AuthTokenParse *pAuthTokenParse,
   IN    const XML_Char *s,
   IN    int len,
   INOUT char **ppElementData,
   INOUT int *pElementDataLen)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus  retStatus = CASA_STATUS_SUCCESS;

   DbgTrace(3, "-ConsumeElementData- Start\n", 0);

   // Proceed based on whether or not we have already consumed data
   // for this element.
   if (*ppElementData == NULL)
   {
      // We have not yet consumed data for this element
      pAuthTokenParse->elementDataProcessed = len;

      // Allocate a buffer to hold this element data (null terminated).
      *ppElementData = (char*) malloc(len + 1);
      if (*ppElementData)
      {
         memset(*ppElementData, 0, len + 1);
         memcpy(*ppElementData, s, len);

         // Return the length of the element data buffer
         *pElementDataLen = pAuthTokenParse->elementDataProcessed + 1;
      }
      else
      {
         DbgTrace(0, "-ConsumeElementData- Buffer allocation failure\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_AUTHTOKEN,
                                     CASA_STATUS_INSUFFICIENT_RESOURCES);
      }
   }
   else
   {
      char  *pNewBuf;

      // We have already received token data, append this data to it.
      pNewBuf = (char*) malloc(pAuthTokenParse->elementDataProcessed + len + 1);
      if (pNewBuf)
      {
         memset(pNewBuf,
                0,
                pAuthTokenParse->elementDataProcessed + len + 1);
         memcpy(pNewBuf,
                *ppElementData,
                pAuthTokenParse->elementDataProcessed);
         memcpy(pNewBuf + pAuthTokenParse->elementDataProcessed, s, len);
         pAuthTokenParse->elementDataProcessed += len;

         // Swap the buffers
         free(*ppElementData);
         *ppElementData = pNewBuf;

         // Return the length of the element data buffer
         *pElementDataLen = pAuthTokenParse->elementDataProcessed + 1;
      }
      else
      {
         DbgTrace(0, "-ConsumeElementData- Buffer allocation failure\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_AUTHTOKEN,
                                     CASA_STATUS_INSUFFICIENT_RESOURCES);
      }
   }

   DbgTrace(3, "-ConsumeElementData- End, retStatus = %08X\n", retStatus);

   return retStatus;
}


//++=======================================================================
static
void XMLCALL
AuthTokenCharDataHandler(
   IN    void *pUserData,
   IN    const XML_Char *s,
   IN    int len)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   AuthTokenParse    *pAuthTokenParse = (AuthTokenParse*) pUserData;

   DbgTrace(2, "-AuthTokenCharDataHandler- Start\n", 0);

   // Just exit if being called to process LF and CR characters
   if (len == 1
       && ((*s == '\n') || (*s == '\r')))
   {
      goto exit;
   }

   // Proceed based on the state
   switch (pAuthTokenParse->state)
   {
      case AWAITING_SIGNATURE_DATA:
      case AWAITING_SIGNATURE_ELEMENT_END:

         pAuthTokenParse->status = ConsumeElementData(pAuthTokenParse,
                                                      s,
                                                      len,
                                                      &pAuthTokenParse->pAuthToken->pSignature,
                                                      &pAuthTokenParse->pAuthToken->signatureLen);
         if (CASA_SUCCESS(pAuthTokenParse->status))
         {
            // Advanced to the next state
            pAuthTokenParse->state = AWAITING_SIGNATURE_ELEMENT_END;
         }
         else
         {
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;

      case AWAITING_LIFETIME_DATA:
      case AWAITING_LIFETIME_ELEMENT_END:
   
         // Convert the lifetime string to a numeric value
         pAuthTokenParse->pAuthToken->tokenLifetime = dtoul((char*) s, len);
   
         // Advanced to the next state
         pAuthTokenParse->state = AWAITING_LIFETIME_ELEMENT_END;
         break;

      case AWAITING_TYPE_DATA:
      case AWAITING_TYPE_ELEMENT_END:

         pAuthTokenParse->status = ConsumeElementData(pAuthTokenParse,
                                                      s,
                                                      len,
                                                      &pAuthTokenParse->pAuthToken->pIdenTokenType,
                                                      &pAuthTokenParse->pAuthToken->idenTokenTypeLen);
         if (CASA_SUCCESS(pAuthTokenParse->status))
         {
            // Advanced to the next state
            pAuthTokenParse->state = AWAITING_TYPE_ELEMENT_END;
         }
         else
         {
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;

      case AWAITING_IDENT_TOKEN_DATA:
      case AWAITING_IDENT_TOKEN_ELEMENT_END:

         pAuthTokenParse->status = ConsumeElementData(pAuthTokenParse,
                                                      s,
                                                      len,
                                                      &pAuthTokenParse->pAuthToken->pIdenToken,
                                                      &pAuthTokenParse->pAuthToken->idenTokenLen);
         if (CASA_SUCCESS(pAuthTokenParse->status))
         {
            // Advanced to the next state
            pAuthTokenParse->state = AWAITING_IDENT_TOKEN_ELEMENT_END;
         }
         else
         {
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;

      default:
         DbgTrace(0, "-AuthTokenCharDataHandler- Un-expected state = %d\n", pAuthTokenParse->state);
         XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         break;
   }

exit:

   DbgTrace(2, "-AuthTokenCharDataHandler- End\n", 0);
}


//++=======================================================================
static
void XMLCALL
AuthTokenEndElementHandler(
   IN    void *pUserData,
   IN    const XML_Char *name)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   AuthTokenParse    *pAuthTokenParse = (AuthTokenParse*) pUserData;

   DbgTrace(2, "-AuthTokenEndElementHandler- Start\n", 0);

   // Proceed based on the state
   switch (pAuthTokenParse->state)
   {
      case AWAITING_ROOT_ELEMENT_END:

         // In this state, we are only expecting the Authentication
         // Token Element.
         if (strcmp(name, AUTH_TOKEN_ELEMENT_NAME) == 0)
         {
            // Done.
            pAuthTokenParse->state = DONE_PARSING;
         }
         else
         {
            DbgTrace(0, "-AuthTokenEndHandler- Un-expected end element\n", 0);
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;

      case AWAITING_SIGNATURE_ELEMENT_END:
   
         // In this state, we are only expecting the Signature Element.
         if (strcmp(name, SIGNATURE_ELEMENT_NAME) == 0)
         {
            // Good, advance to the next state.
            pAuthTokenParse->state = AWAITING_LIFETIME_ELEMENT_START;
         }
         else
         {
            DbgTrace(0, "-AuthTokenEndElementHandler- Un-expected end element\n", 0);
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;

      case AWAITING_LIFETIME_ELEMENT_END:
   
         // In this state, we are only expecting the Lifetime Element.
         if (strcmp(name, LIFETIME_ELEMENT_NAME) == 0)
         {
            // Good, advance to the next state.
            pAuthTokenParse->state = AWAITING_IDENT_TOKEN_ELEMENT_START;
         }
         else
         {
            DbgTrace(0, "-AuthTokenEndElementHandler- Un-expected end element\n", 0);
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;
   
      case AWAITING_TYPE_ELEMENT_END:
   
         // In this state, we are only expecting the Type Element.
         if (strcmp(name, TYPE_ELEMENT_NAME) == 0)
         {
            // Good, advance to the next state.
            pAuthTokenParse->state = AWAITING_IDENT_TOKEN_DATA;
         }
         else
         {
            DbgTrace(0, "-AuthTokenEndElementHandler- Un-expected end element\n", 0);
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;
   
      case AWAITING_IDENT_TOKEN_ELEMENT_END:
   
         // In this state, we are only expecting the Identity Token Element.
         if (strcmp(name, IDENTITY_TOKEN_ELEMENT_NAME) == 0)
         {
            // Good, advance to the next state.
            pAuthTokenParse->state = AWAITING_ROOT_ELEMENT_END;
         }
         else
         {
            DbgTrace(0, "-AuthTokenEndElementHandler- Un-expected end element\n", 0);
            XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         }
         break;
   
      default:
         DbgTrace(0, "-AuthTokenEndElementHandler- Un-expected state = %d\n", pAuthTokenParse->state);
         XML_StopParser(pAuthTokenParse->p, XML_FALSE);
         break;
   }

   DbgTrace(2, "-AuthTokenEndElementHandler- End\n", 0);
}


//++=======================================================================
CasaStatus
CreateAuthToken(
   IN    char *pTokenBuf,
   IN    int tokenBufLen,
   INOUT AuthToken **ppAuthToken)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus        retStatus = CASA_STATUS_SUCCESS;
   AuthTokenParse    authTokenParse = {0};
   AuthToken         *pAuthToken;

   DbgTrace(1, "-CreateAuthToken- Start\n", 0);

   /*
   * Authentication tokens have the following format:
   * 
   * <?xml version="1.0" encoding="ISO-8859-1"?>
   * <auth_token>
   * <signature>signature value</signature>
   * <lifetime>lifetime value</lifetime>
   * <ident_token><type>identity token type</type>identity token data</ident_token>
   * </auth_token>
   *
   */

   // Allocate AuthToken object
   pAuthToken = malloc(sizeof(*pAuthToken));
   if (pAuthToken)
   {
      XML_Parser  p;

      // Initialize the AuthToken object and set it in the
      // authentication response parse object.
      memset(pAuthToken, 0, sizeof(*pAuthToken));
      authTokenParse.pAuthToken = pAuthToken;

      // Create parser
      p = XML_ParserCreate(NULL);
      if (p)
      {
         // Keep track of the parser in our parse object
         authTokenParse.p = p;

         // Initialize the status within the parse object
         authTokenParse.status = CASA_STATUS_SUCCESS;

         // Set the start and end element handlers
         XML_SetElementHandler(p,
                               AuthTokenStartElementHandler,
                               AuthTokenEndElementHandler);

         // Set the character data handler
         XML_SetCharacterDataHandler(p, AuthTokenCharDataHandler);


         // Set our user data
         XML_SetUserData(p, &authTokenParse);

         // Parse the document
         if (XML_Parse(p, pTokenBuf, tokenBufLen, 1) == XML_STATUS_OK)
         {
            // Verify that the parse operation completed successfully
            if (authTokenParse.state == DONE_PARSING)
            {
               // The parse operation succeded.
               retStatus = CASA_STATUS_SUCCESS;
            }
            else
            {
               DbgTrace(0, "-CreateAuthToken- Parse operation did not complete\n", 0);

               // Check if a status has been recorded
               if (authTokenParse.status != CASA_STATUS_SUCCESS)
               {
                  retStatus = authTokenParse.status;
               }
               else
               {
                  retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                              CASA_FACILITY_AUTHTOKEN,
                                              CASA_STATUS_PROTOCOL_ERROR);
               }
            }
         }
         else
         {
            DbgTrace(0, "-CreateAuthToken- Parse error %d\n", XML_GetErrorCode(p));
            retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                        CASA_FACILITY_AUTHTOKEN,
                                        CASA_STATUS_PROTOCOL_ERROR);
         }

         // Free the parser
         XML_ParserFree(p);
      }
      else
      {
         DbgTrace(0, "-CreateAuthToken- Parser creation error\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_AUTHTOKEN,
                                     CASA_STATUS_INSUFFICIENT_RESOURCES);
      }

      // Return the AuthenticationResp object to the caller if necessary
      if (CASA_SUCCESS(retStatus))
      {
         *ppAuthToken = pAuthToken;
      }
      else
      {
         free(pAuthToken);
      }
   }
   else
   {
      DbgTrace(0, "-CreateAuthToken- Memory allocation error\n", 0);
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_AUTHTOKEN,
                                  CASA_STATUS_INSUFFICIENT_RESOURCES);
   }

   DbgTrace(1, "-CreateAuthToken- End, retStatus = %08X\n", retStatus);

   return retStatus;
}


//++=======================================================================
void
RelAuthToken(
   IN    AuthToken *pAuthToken)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(1, "-RelAuthToken- Start\n", 0);

   // Free the resources associated with the object
   if (pAuthToken->pSignature)
      free(pAuthToken->pSignature);

   if (pAuthToken->pIdenTokenType)
      free(pAuthToken->pIdenTokenType);

   if (pAuthToken->pIdenToken)
      free(pAuthToken->pIdenToken);

   free(pAuthToken);

   DbgTrace(1, "-RelAuthToken- End\n", 0);
}


//++=======================================================================
CasaStatus
CheckAuthToken(
   IN    AuthToken *pAuthToken,
   IN    const char *pServiceName)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L0
//=======================================================================--
{
   CasaStatus  retStatus = CASA_STATUS_SUCCESS;

   DbgTrace(1, "-CheckuthToken- Start\n", 0);

   // tbd

   DbgTrace(1, "-CheckAuthToken- End, retStatus = %08X\n", retStatus);

   return retStatus;
}