/** @file
  The Implementations for Information Exchange.

  Copyright (c) 2010, Intel Corporation. All rights reserved.<BR>

  This program and the accompanying materials
  are licensed and made available under the terms and conditions of the BSD License
  which accompanies this distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php.

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
  
**/

#include "Utility.h"
#include "IpSecDebug.h"
#include "IpSecConfigImpl.h"

/**
  Generate Information Packet.

  The information Packet may contain one Delete Payload, or Notify Payload, which 
  dependes on the Context's parameters.

  @param[in]  SaSession   Pointer to IKE SA Session or Child SA Session which is 
                          related to the information Exchange.
  @param[in]  Context     The Data passed from the caller. If the Context is not NULL
                          it should contain the information for Notification Data.
                          
  @retval     Pointer of IKE_PACKET generated.

**/
IKE_PACKET *
Ikev2InfoGenerator (
  IN UINT8                         *SaSession,
  IN VOID                          *Context
  )
{
  IKEV2_SA_SESSION            *IkeSaSession;
  IKEV2_CHILD_SA_SESSION      *ChildSaSession;
  IKE_PACKET                  *IkePacket;
  IKE_PAYLOAD                 *IkePayload;
  IKEV2_INFO_EXCHANGE_CONTEXT *InfoContext;

  InfoContext  = NULL;
  IkeSaSession = (IKEV2_SA_SESSION *) SaSession;
  IkePacket    = IkePacketAlloc ();
  ASSERT (IkePacket != NULL);

  //
  // Fill IkePacket Header.
  //
  IkePacket->Header->ExchangeType    = IKEV2_EXCHANGE_TYPE_INFO;
  IkePacket->Header->Version         = (UINT8) (2 << 4); 

  if (Context != NULL) {
    InfoContext = (IKEV2_INFO_EXCHANGE_CONTEXT *) Context;
  }

  //
  // For Liveness Check
  //
  if (InfoContext != NULL && 
      (InfoContext->InfoType == Ikev2InfoLiveCheck || InfoContext->InfoType == Ikev2InfoNotify) 
    ) {
    IkePacket->Header->MessageId       = InfoContext->MessageId;
    IkePacket->Header->InitiatorCookie = IkeSaSession->InitiatorCookie;
    IkePacket->Header->ResponderCookie = IkeSaSession->ResponderCookie;
    IkePacket->Header->NextPayload     = IKEV2_PAYLOAD_TYPE_NONE;
    IkePacket->Header->Flags           = IKE_HEADER_FLAGS_RESPOND;
    //
    // TODO: add Notify Payload for Notification Information.
    //
    return IkePacket;
  }
  
  //
  // For delete SAs
  //  
  if (IkeSaSession->SessionCommon.IkeSessionType == IkeSessionTypeIkeSa) {

    IkePacket->Header->InitiatorCookie = IkeSaSession->InitiatorCookie;
    IkePacket->Header->ResponderCookie = IkeSaSession->ResponderCookie;

    //
    // If the information message is response message,the MessageId should
    // be same as the request MessageId which passed through the Context.
    //
    if (InfoContext != NULL) {
      IkePacket->Header->MessageId     = InfoContext->MessageId;
    } else {
      IkePacket->Header->MessageId     = IkeSaSession->MessageId;
      Ikev2SaSessionIncreaseMessageId (IkeSaSession);
    }
    //
    // If the state is on deleting generate a Delete Payload for it.
    //
    if (IkeSaSession->SessionCommon.State == IkeStateSaDeleting ) {
      IkePayload = Ikev2GenerateDeletePayload (
                     IkeSaSession, 
                     IKEV2_PAYLOAD_TYPE_NONE, 
                     0, 
                     0, 
                     NULL
                     );  
      if (IkePayload == NULL) {
        goto ERROR_EXIT;
      }
      //
      // Fill the next payload in IkePacket's Header.
      //
      IkePacket->Header->NextPayload     = IKEV2_PAYLOAD_TYPE_DELETE;
      IKE_PACKET_APPEND_PAYLOAD (IkePacket, IkePayload);
      IkePacket->Private           = IkeSaSession->SessionCommon.Private;
      IkePacket->Spi               = 0;
      IkePacket->IsDeleteInfo      = TRUE;
            
    } else if (Context != NULL) {
      //
      // TODO: If contest is not NULL Generate a Notify Payload.
      //
    } else {
      //
      // The input parameter is not correct.
      //
      goto ERROR_EXIT;
    } 
  } else {
    //
    // Delete the Child SA Information Exchagne
    //
    ChildSaSession                     = (IKEV2_CHILD_SA_SESSION *) SaSession;
    IkeSaSession                       = ChildSaSession->IkeSaSession;
    IkePacket->Header->InitiatorCookie = ChildSaSession->IkeSaSession->InitiatorCookie;
    IkePacket->Header->ResponderCookie = ChildSaSession->IkeSaSession->ResponderCookie;

    //
    // If the information message is response message,the MessageId should
    // be same as the request MessageId which passed through the Context.
    //
    if (InfoContext != NULL && InfoContext->MessageId != 0) {
      IkePacket->Header->MessageId     = InfoContext->MessageId;
    } else {
      IkePacket->Header->MessageId     = ChildSaSession->IkeSaSession->MessageId;
      Ikev2SaSessionIncreaseMessageId (IkeSaSession);
    }
    
    IkePayload     = Ikev2GenerateDeletePayload (
                       ChildSaSession->IkeSaSession,
                       IKEV2_PAYLOAD_TYPE_DELETE,
                       4,
                       1,
                       (UINT8 *)&ChildSaSession->LocalPeerSpi
                       );
    if (IkePayload == NULL) {
      goto ERROR_EXIT;
    }
    //
    // Fill the Next Payload in IkePacket's Header.
    //
    IkePacket->Header->NextPayload     = IKEV2_PAYLOAD_TYPE_DELETE;
    IKE_PACKET_APPEND_PAYLOAD (IkePacket, IkePayload);

    IkePacket->Private      = IkeSaSession->SessionCommon.Private;
    IkePacket->Spi          = ChildSaSession->LocalPeerSpi;
    IkePacket->IsDeleteInfo = TRUE;

    if (!ChildSaSession->SessionCommon.IsInitiator) {
      //
      // If responder, use the MessageId fromt the initiator.
      //
      IkePacket->Header->MessageId = ChildSaSession->MessageId;
    }

    //
    // Change the IsOnDeleting Flag
    //
    ChildSaSession->SessionCommon.IsOnDeleting = TRUE;
  }

  if (InfoContext == NULL) {
    IkePacket->Header->Flags = IKE_HEADER_FLAGS_INIT;
  } else {
    IkePacket->Header->Flags = IKE_HEADER_FLAGS_RESPOND;
  }
  return IkePacket;

ERROR_EXIT:
   if (IkePacket != NULL) {
     FreePool (IkePacket);
   }
   return NULL;

}

/**
  Parse the Info Exchange.

  @param[in]  SaSession   Pointer to IKEV2_SA_SESSION.
  @param[in]  IkePacket   Pointer to IkePacket related to the Information Exchange.

  @retval  EFI_SUCCESS    The operation finised successed.

**/
EFI_STATUS
Ikev2InfoParser (
  IN UINT8                         *SaSession,
  IN IKE_PACKET                    *IkePacket
  )
{
  IKEV2_CHILD_SA_SESSION *ChildSaSession;
  IKEV2_SA_SESSION       *IkeSaSession;
  IKE_PAYLOAD            *NotifyPayload;
  IKE_PAYLOAD            *DeletePayload;
  IKE_PAYLOAD            *IkePayload;
  IKEV2_DELETE           *Delete;
  LIST_ENTRY             *Entry;
  LIST_ENTRY             *ListEntry;
  UINT8                  Index;
  UINT32                 Spi;
  UINT8                  *SpiBuffer;
  IPSEC_PRIVATE_DATA     *Private;
  UINT8                  Value;
  EFI_STATUS             Status;
  IKE_PACKET             *RespondPacket;
  
  IKEV2_INFO_EXCHANGE_CONTEXT Context;
  
  IkeSaSession   = (IKEV2_SA_SESSION *) SaSession;

  NotifyPayload  = NULL;
  DeletePayload  = NULL;
  Private        = NULL;
  RespondPacket  = NULL;
  Status         = EFI_SUCCESS;
  
  //
  // For Liveness Check
  //
  if (IkePacket->Header->NextPayload == IKEV2_PAYLOAD_TYPE_NONE &&
      (IkePacket->PayloadTotalSize == 0)
      ) {
    if (IkePacket->Header->Flags == IKE_HEADER_FLAGS_INIT) {
      //
      // If it is Liveness check request, reply it.
      //
      Context.InfoType  = Ikev2InfoLiveCheck;
      Context.MessageId = IkePacket->Header->MessageId;
      RespondPacket     = Ikev2InfoGenerator ((UINT8 *)IkeSaSession, &Context);

      if (RespondPacket == NULL) {
        Status = EFI_INVALID_PARAMETER;
        return Status;
      }
      Status = Ikev2SendIkePacket (
                 IkeSaSession->SessionCommon.UdpService,
                 (UINT8 *)(&IkeSaSession->SessionCommon),
                 RespondPacket,
                 0
                 );

    } else {
      //
      // Todo: verify the liveness check response packet.
      //
    }
    return Status;
  }

  //
  // For SA Delete
  //
  NET_LIST_FOR_EACH (Entry, &(IkePacket)->PayloadList) {   

  //
  // Iterate payloads to find the Delete/Notify Payload.
  //
    IkePayload  = IKE_PAYLOAD_BY_PACKET (Entry);
    
    if (IkePayload->PayloadType == IKEV2_PAYLOAD_TYPE_DELETE) {
      DeletePayload = IkePayload;
      Delete = (IKEV2_DELETE *)DeletePayload->PayloadBuf;

      if (Delete->SpiSize == 0) {
        //
        // Delete IKE SA.
        //
        if (IkeSaSession->SessionCommon.State == IkeStateSaDeleting) {
          RemoveEntryList (&IkeSaSession->BySessionTable);
          Ikev2SaSessionFree (IkeSaSession);
          //
          // Checking the Private status.
          //
          //
          // when all IKE SAs were disabled by calling "IPsecConfig -disable", the IPsec
          // status should be changed.
          //
          Private = IkeSaSession->SessionCommon.Private;
          if (Private != NULL && Private->IsIPsecDisabling) {
            //
            // After all IKE SAs were deleted, set the IPSEC_STATUS_DISABLED value in
            // IPsec status variable.
            //
            if (IsListEmpty (&Private->Ikev1EstablishedList) && 
                (IsListEmpty (&Private->Ikev2EstablishedList))
               ) {
              Value  = IPSEC_STATUS_DISABLED;
              Status = gRT->SetVariable (
                         IPSECCONFIG_STATUS_NAME,
                         &gEfiIpSecConfigProtocolGuid,
                         EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                         sizeof (Value),
                         &Value
                         );
              if (!EFI_ERROR (Status)) {
                //
                // Set the DisabledFlag in Private data.
                //
                Private->IpSec.DisabledFlag = TRUE;
                Private->IsIPsecDisabling   = FALSE;
              }
            }
          }
        } else {
          IkeSaSession->SessionCommon.State = IkeStateSaDeleting;
          Context.InfoType                  = Ikev2InfoDelete;
          Context.MessageId                 = IkePacket->Header->MessageId;

          RespondPacket = Ikev2InfoGenerator ((UINT8 *)IkeSaSession, &Context);
          if (RespondPacket == NULL) {
            Status = EFI_INVALID_PARAMETER;
            return Status;
          }
          Status = Ikev2SendIkePacket (
                     IkeSaSession->SessionCommon.UdpService, 
                     (UINT8 *)(&IkeSaSession->SessionCommon), 
                     RespondPacket, 
                     0
                     );
        }
      } else if (Delete->SpiSize == 4) {
        //
        // Move the Child SAs to DeleteList
        //
        SpiBuffer = (UINT8 *)(Delete + 1);
        for (Index = 0; Index < Delete->NumSpis; Index++) {
          Spi = ReadUnaligned32 ((UINT32 *)SpiBuffer);
          for (ListEntry = IkeSaSession->ChildSaEstablishSessionList.ForwardLink;
               ListEntry != &IkeSaSession->ChildSaEstablishSessionList;
          ) {
            ChildSaSession = IKEV2_CHILD_SA_SESSION_BY_IKE_SA (ListEntry);
            ListEntry = ListEntry->ForwardLink;

            if (ChildSaSession->RemotePeerSpi == HTONL(Spi)) {
              if (ChildSaSession->SessionCommon.State != IkeStateSaDeleting) {

                //
                // Insert the ChildSa Session into Delete List.
                //
                InsertTailList (&IkeSaSession->DeleteSaList, &ChildSaSession->ByDelete);
                ChildSaSession->SessionCommon.State       = IkeStateSaDeleting;
                ChildSaSession->SessionCommon.IsInitiator = FALSE;
                ChildSaSession->MessageId                 = IkePacket->Header->MessageId;

                Context.InfoType = Ikev2InfoDelete;
                Context.MessageId = IkePacket->Header->MessageId;
          
                RespondPacket = Ikev2InfoGenerator ((UINT8 *)ChildSaSession, &Context);
                if (RespondPacket == NULL) {
                  Status = EFI_INVALID_PARAMETER;
                  return Status;
                }
                Status = Ikev2SendIkePacket (
                           ChildSaSession->SessionCommon.UdpService,
                           (UINT8 *)(&ChildSaSession->SessionCommon),
                           RespondPacket, 
                           0
                           );
              } else {
                //
                // Delete the Child SA.
                //
                Ikev2ChildSaSilentDelete (IkeSaSession, Spi);
                RemoveEntryList (&ChildSaSession->ByDelete);
              }
            }
          }
          SpiBuffer = SpiBuffer + sizeof (Spi);
        }
      }
    }
  }
  
  return Status;
}

GLOBAL_REMOVE_IF_UNREFERENCED IKEV2_PACKET_HANDLER  mIkev2Info = {
  Ikev2InfoParser,
  Ikev2InfoGenerator
};
