/** @file
  The implementation of IPsec.

  Copyright (c) 2009 - 2011, 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 "IpSecImpl.h"
#include "IkeService.h"
#include "IpSecDebug.h"
#include "IpSecCryptIo.h"
#include "IpSecConfigImpl.h"

/**
  Check if the specified Address is the Valid Address Range.

  This function checks if the bytes after prefixed length are all Zero in this
  Address. This Address is supposed to point to a range address. That means it
  should gives the correct prefixed address and the bytes outside the prefixed are
  zero.

  @param[in]  IpVersion         The IP version.
  @param[in]  Address           Points to EFI_IP_ADDRESS to be checked.
  @param[in]  PrefixLength      The PrefixeLength of this address.

  @retval     TRUE      The address is a vaild address range.
  @retval     FALSE     The address is not a vaild address range.

**/
BOOLEAN
IpSecValidAddressRange (
  IN UINT8                     IpVersion,
  IN EFI_IP_ADDRESS            *Address,
  IN UINT8                     PrefixLength
  )
{
  UINT8           Div;
  UINT8           Mod;
  UINT8           Mask;
  UINT8           AddrLen;
  UINT8           *Addr;
  EFI_IP_ADDRESS  ZeroAddr;

  if (PrefixLength == 0) {
    return TRUE;
  }

  AddrLen = (UINT8) ((IpVersion == IP_VERSION_4) ? 32 : 128);

  if (AddrLen <= PrefixLength) {
    return FALSE;
  }

  Div   = (UINT8) (PrefixLength / 8);
  Mod   = (UINT8) (PrefixLength % 8);
  Addr  = (UINT8 *) Address;
  ZeroMem (&ZeroAddr, sizeof (EFI_IP_ADDRESS));

  //
  // Check whether the mod part of host scope is zero or not.
  //
  if (Mod > 0) {
    Mask = (UINT8) (0xFF << (8 - Mod));

    if ((Addr[Div] | Mask) != Mask) {
      return FALSE;
    }

    Div++;
  }
  //
  // Check whether the div part of host scope is zero or not.
  //
  if (CompareMem (
        &Addr[Div],
        &ZeroAddr,
        sizeof (EFI_IP_ADDRESS) - Div
        ) != 0) {
    return FALSE;
  }

  return TRUE;
}

/**
  Extrct the Address Range from a Address.

  This function keep the prefix address and zero other part address.

  @param[in]  Address           Point to a specified address.
  @param[in]  PrefixLength      The prefix length.
  @param[out] Range             Contain the return Address Range.

**/
VOID
IpSecExtractAddressRange (
  IN EFI_IP_ADDRESS            *Address,
  IN UINT8                     PrefixLength,
  OUT EFI_IP_ADDRESS           *Range
  )
{
  UINT8 Div;
  UINT8 Mod;
  UINT8 Mask;
  UINT8 *Addr;

  if (PrefixLength == 0) {
    return ;
  }

  Div   = (UINT8) (PrefixLength / 8);
  Mod   = (UINT8) (PrefixLength % 8);
  Addr  = (UINT8 *) Range;

  CopyMem (Range, Address, sizeof (EFI_IP_ADDRESS));

  //
  // Zero the mod part of host scope.
  //
  if (Mod > 0) {
    Mask      = (UINT8) (0xFF << (8 - Mod));
    Addr[Div] = (UINT8) (Addr[Div] & Mask);
    Div++;
  }
  //
  // Zero the div part of host scope.
  //
  ZeroMem (&Addr[Div], sizeof (EFI_IP_ADDRESS) - Div);

}

/**
  Checks if the IP Address in the address range of AddressInfos specified.

  @param[in]  IpVersion         The IP version.
  @param[in]  IpAddr            Point to EFI_IP_ADDRESS to be check.
  @param[in]  AddressInfo       A list of EFI_IP_ADDRESS_INFO that is used to check
                                the IP Address is matched.
  @param[in]  AddressCount      The total numbers of the AddressInfo.

  @retval   TRUE    If the Specified IP Address is in the range of the AddressInfos specified.
  @retval   FALSE   If the Specified IP Address is not in the range of the AddressInfos specified.

**/
BOOLEAN
IpSecMatchIpAddress (
  IN UINT8                     IpVersion,
  IN EFI_IP_ADDRESS            *IpAddr,
  IN EFI_IP_ADDRESS_INFO       *AddressInfo,
  IN UINT32                    AddressCount
  )
{
  EFI_IP_ADDRESS  Range;
  UINT32          Index;
  BOOLEAN         IsMatch;

  IsMatch = FALSE;

  for (Index = 0; Index < AddressCount; Index++) {
    //
    // Check whether the target address is in the address range
    // if it's a valid range of address.
    //
    if (IpSecValidAddressRange (
          IpVersion,
          &AddressInfo[Index].Address,
          AddressInfo[Index].PrefixLength
          )) {
      //
      // Get the range of the target address belongs to.
      //
      ZeroMem (&Range, sizeof (EFI_IP_ADDRESS));
      IpSecExtractAddressRange (
        IpAddr,
        AddressInfo[Index].PrefixLength,
        &Range
        );

      if (CompareMem (
            &Range,
            &AddressInfo[Index].Address,
            sizeof (EFI_IP_ADDRESS)
            ) == 0) {
        //
        // The target address is in the address range.
        //
        IsMatch = TRUE;
        break;
      }
    }

    if (CompareMem (
          IpAddr,
          &AddressInfo[Index].Address,
          sizeof (EFI_IP_ADDRESS)
          ) == 0) {
      //
      // The target address is exact same as the address.
      //
      IsMatch = TRUE;
      break;
    }
  }
  return IsMatch;
}

/**
  Check if the specified Protocol and Prot is supported by the specified SPD Entry.

  This function is the subfunction of IPsecLookUpSpdEntry() that is used to
  check if the sent/received IKE packet has the related SPD entry support.

  @param[in]  Protocol          The Protocol to be checked.
  @param[in]  IpPayload         Point to IP Payload to be check.
  @param[in]  SpdProtocol       The Protocol supported by SPD.
  @param[in]  SpdLocalPort      The Local Port in SPD.
  @param[in]  SpdRemotePort     The Remote Port in SPD.
  @param[in]  IsOutbound        Flag to indicate the is for IKE Packet sending or recieving.

  @retval     TRUE      The Protocol and Port are supported by the SPD Entry.
  @retval     FALSE     The Protocol and Port are not supported by the SPD Entry.

**/
BOOLEAN
IpSecMatchNextLayerProtocol (
  IN UINT8                     Protocol,
  IN UINT8                     *IpPayload,
  IN UINT16                    SpdProtocol,
  IN UINT16                    SpdLocalPort,
  IN UINT16                    SpdRemotePort,
  IN BOOLEAN                   IsOutbound
  )
{
  BOOLEAN IsMatch;

  if (SpdProtocol == EFI_IPSEC_ANY_PROTOCOL) {
    return TRUE;
  }

  IsMatch = FALSE;

  if (SpdProtocol == Protocol) {
    switch (Protocol) {
    case EFI_IP_PROTO_UDP:
    case EFI_IP_PROTO_TCP:
      //
      // For udp and tcp, (0, 0) means no need to check local and remote
      // port. The payload is passed from upper level, which means it should
      // be in network order.
      //
      IsMatch = (BOOLEAN) (SpdLocalPort == 0 && SpdRemotePort == 0);
      IsMatch = (BOOLEAN) (IsMatch ||
                           (IsOutbound &&
                           (BOOLEAN)(
                              NTOHS (((EFI_UDP_HEADER *) IpPayload)->SrcPort) == SpdLocalPort &&
                              NTOHS (((EFI_UDP_HEADER *) IpPayload)->DstPort) == SpdRemotePort
                              )
                            ));

      IsMatch = (BOOLEAN) (IsMatch ||
                           (!IsOutbound &&
                           (BOOLEAN)(
                              NTOHS (((EFI_UDP_HEADER *) IpPayload)->DstPort) == SpdLocalPort &&
                              NTOHS (((EFI_UDP_HEADER *) IpPayload)->SrcPort) == SpdRemotePort
                              )
                           ));
      break;

    case EFI_IP_PROTO_ICMP:
      //
      // For icmpv4, type code is replaced with local port and remote port,
      // and (0, 0) means no need to check.
      //
      IsMatch = (BOOLEAN) (SpdLocalPort == 0 && SpdRemotePort == 0);
      IsMatch = (BOOLEAN) (IsMatch ||
                           (BOOLEAN) (((IP4_ICMP_HEAD *) IpPayload)->Type == SpdLocalPort &&
                                      ((IP4_ICMP_HEAD *) IpPayload)->Code == SpdRemotePort
                                      )
                           );
      break;

    case IP6_ICMP:
      //
      // For icmpv6, type code is replaced with local port and remote port,
      // and (0, 0) means no need to check.
      //
      IsMatch = (BOOLEAN) (SpdLocalPort == 0 && SpdRemotePort == 0);

      IsMatch = (BOOLEAN) (IsMatch ||
                           (BOOLEAN) (((IP6_ICMP_HEAD *) IpPayload)->Type == SpdLocalPort &&
                                      ((IP6_ICMP_HEAD *) IpPayload)->Code == SpdRemotePort
                                      )
                          );
      break;

    default:
      IsMatch = TRUE;
      break;
    }
  }

  return IsMatch;
}

/**
  Find the SAD through a specified SPD's SAD list.

  @param[in]  SadList           SAD list related to a specified SPD entry.
  @param[in]  DestAddress       The destination address used to find the SAD entry.
  @param[in]  IpVersion         The IP version. Ip4 or Ip6.

  @return  The pointer to a certain SAD entry.

**/
IPSEC_SAD_ENTRY *
IpSecLookupSadBySpd (
  IN LIST_ENTRY                 *SadList,
  IN EFI_IP_ADDRESS             *DestAddress,
  IN UINT8                      IpVersion
  )
{
  LIST_ENTRY      *Entry;
  IPSEC_SAD_ENTRY *SadEntry;

  NET_LIST_FOR_EACH (Entry, SadList) {

    SadEntry = IPSEC_SAD_ENTRY_FROM_SPD (Entry);
    //
    // Find the right SAD entry which contains the appointed dest address.
    //
    if (IpSecMatchIpAddress (
          IpVersion,
          DestAddress,
          SadEntry->Data->SpdSelector->RemoteAddress,
          SadEntry->Data->SpdSelector->RemoteAddressCount
          )){
      return SadEntry;
    }
  }

  return NULL;
}

/**
  Find the SAD through whole SAD list.

  @param[in]  Spi               The SPI used to search the SAD entry.
  @param[in]  DestAddress       The destination used to search the SAD entry.
  @param[in]  IpVersion         The IP version. Ip4 or Ip6.

  @return  the pointer to a certain SAD entry.

**/
IPSEC_SAD_ENTRY *
IpSecLookupSadBySpi (
  IN UINT32                   Spi,
  IN EFI_IP_ADDRESS           *DestAddress,
  IN UINT8                    IpVersion
  )
{
  LIST_ENTRY      *Entry;
  LIST_ENTRY      *SadList;
  IPSEC_SAD_ENTRY *SadEntry;

  SadList = &mConfigData[IPsecConfigDataTypeSad];

  NET_LIST_FOR_EACH (Entry, SadList) {

    SadEntry = IPSEC_SAD_ENTRY_FROM_LIST (Entry);

    //
    // Find the right SAD entry which contain the appointed spi and dest addr.
    //
    if (SadEntry->Id->Spi == Spi) {
      if (SadEntry->Data->Mode == EfiIPsecTunnel) {
        if (CompareMem (
              &DestAddress,
              &SadEntry->Data->TunnelDestAddress,
              sizeof (EFI_IP_ADDRESS)
              )) {
          return SadEntry;
        }
      } else {
        if (SadEntry->Data->SpdSelector != NULL &&
            IpSecMatchIpAddress (
              IpVersion,
              DestAddress,
              SadEntry->Data->SpdSelector->RemoteAddress,
              SadEntry->Data->SpdSelector->RemoteAddressCount
              )
            ) {
          return SadEntry;
        }
      }
    }
  }
  return NULL;
}

/**
  Look up if there is existing SAD entry for specified IP packet sending.

  This function is called by the IPsecProcess when there is some IP packet needed to
  send out. This function checks if there is an existing SAD entry that can be serviced
  to this IP packet sending. If no existing SAD entry could be used, this
  function will invoke an IPsec Key Exchange Negotiation.

  @param[in]  Private           Points to private data.
  @param[in]  NicHandle         Points to a NIC handle.
  @param[in]  IpVersion         The version of IP.
  @param[in]  IpHead            The IP Header of packet to be sent out.
  @param[in]  IpPayload         The IP Payload to be sent out.
  @param[in]  OldLastHead       The Last protocol of the IP packet.
  @param[in]  SpdEntry          Points to a related SPD entry.
  @param[out] SadEntry          Contains the Point of a related SAD entry.

  @retval EFI_DEVICE_ERROR  One of following conditions is TRUE:
                            - If don't find related UDP service.
                            - Sequence Number is used up.
                            - Extension Sequence Number is used up.
  @retval EFI_NOT_READY     No existing SAD entry could be used.
  @retval EFI_SUCCESS       Find the related SAD entry.

**/
EFI_STATUS
IpSecLookupSadEntry (
  IN IPSEC_PRIVATE_DATA      *Private,
  IN EFI_HANDLE              NicHandle,
  IN UINT8                   IpVersion,
  IN VOID                    *IpHead,
  IN UINT8                   *IpPayload,
  IN UINT8                   OldLastHead,
  IN IPSEC_SPD_ENTRY         *SpdEntry,
  OUT IPSEC_SAD_ENTRY        **SadEntry
  )
{
  IKE_UDP_SERVICE *UdpService;
  IPSEC_SAD_ENTRY *Entry;
  IPSEC_SAD_DATA  *Data;
  EFI_IP_ADDRESS  DestIp;
  UINT32          SeqNum32;

  *SadEntry   = NULL;
  UdpService  = IkeLookupUdp (Private, NicHandle, IpVersion);

  if (UdpService == NULL) {
    return EFI_DEVICE_ERROR;
  }
  //
  // Parse the destination address from ip header.
  //
  ZeroMem (&DestIp, sizeof (EFI_IP_ADDRESS));
  if (IpVersion == IP_VERSION_4) {
    CopyMem (
      &DestIp,
      &((IP4_HEAD *) IpHead)->Dst,
      sizeof (IP4_ADDR)
      );
  } else {
    CopyMem (
      &DestIp,
      &((EFI_IP6_HEADER *) IpHead)->DestinationAddress,
      sizeof (EFI_IP_ADDRESS)
      );
  }

  //
  // Find the SAD entry in the spd.sas list according to the dest address.
  //
  Entry = IpSecLookupSadBySpd (&SpdEntry->Data->Sas, &DestIp, IpVersion);

  if (Entry == NULL) {
    if (OldLastHead != IP6_ICMP ||
        (OldLastHead == IP6_ICMP && *IpPayload == ICMP_V6_ECHO_REQUEST)
        ) {
      //
      // Start ike negotiation process except the request packet of ping.
      //
      if (SpdEntry->Data->ProcessingPolicy->Mode == EfiIPsecTunnel) {
        IkeNegotiate (
          UdpService,
          SpdEntry,
          &SpdEntry->Data->ProcessingPolicy->TunnelOption->RemoteTunnelAddress
          );
      } else {
        IkeNegotiate (
          UdpService,
          SpdEntry,
          &DestIp
        );
      }

    }

    return EFI_NOT_READY;
  }

  Data = Entry->Data;

  if (!Data->ManualSet) {
    if (Data->ESNEnabled) {
      //
      // Validate the 64bit sn number if 64bit sn enabled.
      //
      if ((UINT64) (Data->SequenceNumber + 1) == 0) {
        //
        // TODO: Re-negotiate SA
        //
        return EFI_DEVICE_ERROR;
      }
    } else {
      //
      // Validate the 32bit sn number if 64bit sn disabled.
      //
      SeqNum32 = (UINT32) Data->SequenceNumber;
      if ((UINT32) (SeqNum32 + 1) == 0) {
        //
        // TODO: Re-negotiate SA
        //
        return EFI_DEVICE_ERROR;
      }
    }
  }

  *SadEntry = Entry;

  return EFI_SUCCESS;
}

/**
  Find a PAD entry according to a remote IP address.

  @param[in]  IpVersion         The version of IP.
  @param[in]  IpAddr            Points to remote IP address.

  @return the pointer of related PAD entry.

**/
IPSEC_PAD_ENTRY *
IpSecLookupPadEntry (
  IN UINT8                   IpVersion,
  IN EFI_IP_ADDRESS          *IpAddr
  )
{
  LIST_ENTRY          *PadList;
  LIST_ENTRY          *Entry;
  EFI_IP_ADDRESS_INFO *IpAddrInfo;
  IPSEC_PAD_ENTRY     *PadEntry;

  PadList = &mConfigData[IPsecConfigDataTypePad];

  for (Entry = PadList->ForwardLink; Entry != PadList; Entry = Entry->ForwardLink) {

    PadEntry    = IPSEC_PAD_ENTRY_FROM_LIST (Entry);
    IpAddrInfo  = &PadEntry->Id->Id.IpAddress;
    //
    // Find the right pad entry which contain the appointed dest addr.
    //
    if (IpSecMatchIpAddress (IpVersion, IpAddr, IpAddrInfo, 1)) {
      return PadEntry;
    }
  }

  return NULL;
}

/**
  Check if the specified IP packet can be serviced by this SPD entry.

  @param[in]  SpdEntry          Point to SPD entry.
  @param[in]  IpVersion         Version of IP.
  @param[in]  IpHead            Point to IP header.
  @param[in]  IpPayload         Point to IP payload.
  @param[in]  Protocol          The Last protocol of IP packet.
  @param[in]  IsOutbound        Traffic direction.
  @param[out] Action            The support action of SPD entry.

  @retval EFI_SUCCESS       Find the related SPD.
  @retval EFI_NOT_FOUND     Not find the related SPD entry;

**/
EFI_STATUS
IpSecLookupSpdEntry (
  IN     IPSEC_SPD_ENTRY         *SpdEntry,
  IN     UINT8                   IpVersion,
  IN     VOID                    *IpHead,
  IN     UINT8                   *IpPayload,
  IN     UINT8                   Protocol,
  IN     BOOLEAN                 IsOutbound,
     OUT EFI_IPSEC_ACTION        *Action
  )
{
  EFI_IPSEC_SPD_SELECTOR  *SpdSel;
  IP4_HEAD                *Ip4;
  EFI_IP6_HEADER          *Ip6;
  EFI_IP_ADDRESS          SrcAddr;
  EFI_IP_ADDRESS          DstAddr;
  BOOLEAN                 SpdMatch;

  ASSERT (SpdEntry != NULL);
  SpdSel  = SpdEntry->Selector;
  Ip4     = (IP4_HEAD *) IpHead;
  Ip6     = (EFI_IP6_HEADER *) IpHead;

  ZeroMem (&SrcAddr, sizeof (EFI_IP_ADDRESS));
  ZeroMem (&DstAddr, sizeof (EFI_IP_ADDRESS));

  //
  // Parse the source and destination address from ip header.
  //
  if (IpVersion == IP_VERSION_4) {
    CopyMem (&SrcAddr, &Ip4->Src, sizeof (IP4_ADDR));
    CopyMem (&DstAddr, &Ip4->Dst, sizeof (IP4_ADDR));
  } else {
    CopyMem (&SrcAddr, &Ip6->SourceAddress, sizeof (EFI_IPv6_ADDRESS));
    CopyMem (&DstAddr, &Ip6->DestinationAddress, sizeof (EFI_IPv6_ADDRESS));
  }
  //
  // Check the local and remote addresses for outbound traffic
  //
  SpdMatch = (BOOLEAN)(IsOutbound &&
                       IpSecMatchIpAddress (
                         IpVersion,
                         &SrcAddr,
                         SpdSel->LocalAddress,
                         SpdSel->LocalAddressCount
                         ) &&
                       IpSecMatchIpAddress (
                         IpVersion,
                         &DstAddr,
                         SpdSel->RemoteAddress,
                         SpdSel->RemoteAddressCount
                         )
                       );

  //
  // Check the local and remote addresses for inbound traffic
  //
  SpdMatch = (BOOLEAN) (SpdMatch ||
                        (!IsOutbound &&
                        IpSecMatchIpAddress (
                          IpVersion,
                          &DstAddr,
                          SpdSel->LocalAddress,
                          SpdSel->LocalAddressCount
                          ) &&
                        IpSecMatchIpAddress (
                          IpVersion,
                          &SrcAddr,
                          SpdSel->RemoteAddress,
                          SpdSel->RemoteAddressCount
                          )
                        ));

  //
  // Check the next layer protocol and local and remote ports.
  //
  SpdMatch = (BOOLEAN) (SpdMatch &&
                        IpSecMatchNextLayerProtocol (
                          Protocol,
                          IpPayload,
                          SpdSel->NextLayerProtocol,
                          SpdSel->LocalPort,
                          SpdSel->RemotePort,
                          IsOutbound
                          )
                        );

  if (SpdMatch) {
    //
    // Find the right SPD entry if match the 5 key elements.
    //
    *Action = SpdEntry->Data->Action;
    return EFI_SUCCESS;
  }

  return EFI_NOT_FOUND;
}

/**
  The call back function of NetbufFromExt.

  @param[in]  Arg            The argument passed from the caller.

**/
VOID
EFIAPI
IpSecOnRecyclePacket (
  IN VOID                            *Arg
  )
{
}

/**
  This is a Notification function. It is called when the related IP6_TXTOKEN_WRAP
  is released.

  @param[in]  Event              The related event.
  @param[in]  Context            The data passed by the caller.

**/
VOID
EFIAPI
IpSecRecycleCallback (
  IN EFI_EVENT                       Event,
  IN VOID                            *Context
  )
{
  IPSEC_RECYCLE_CONTEXT *RecycleContext;

  RecycleContext = (IPSEC_RECYCLE_CONTEXT *) Context;

  if (RecycleContext->FragmentTable != NULL) {
    FreePool (RecycleContext->FragmentTable);
  }

  if (RecycleContext->PayloadBuffer != NULL) {
    FreePool (RecycleContext->PayloadBuffer);
  }

  FreePool (RecycleContext);
  gBS->CloseEvent (Event);

}

/**
  Calculate the extension hader of IP. The return length only doesn't contain
  the fixed IP header length.

  @param[in]  IpHead             Points to an IP head to be calculated.
  @param[in]  LastHead           Points to the last header of the IP header.

  @return The length of the extension header.

**/
UINT16
IpSecGetPlainExtHeadSize (
  IN VOID                             *IpHead,
  IN UINT8                            *LastHead
  )
{
  UINT16  Size;

  Size = (UINT16) (LastHead - (UINT8 *) IpHead);

  if (Size > sizeof (EFI_IP6_HEADER)) {
    //
    // * (LastHead+1) point the last header's length but not include the first
    // 8 octers, so this formluation add 8 at the end.
    //
    Size = (UINT16) (Size - sizeof (EFI_IP6_HEADER) + *(LastHead + 1) + 8);
  } else {
    Size = 0;
  }

  return Size;
}

/**
  Verify if the Authentication payload is correct.

  @param[in]  EspBuffer          Points to the ESP wrapped buffer.
  @param[in]  EspSize            The size of the ESP wrapped buffer.
  @param[in]  SadEntry           The related SAD entry to store the authentication
                                 algorithm key.
  @param[in]  IcvSize            The length of ICV.

  @retval EFI_SUCCESS        The authentication data is correct.
  @retval EFI_ACCESS_DENIED  The authentication data is not correct.

**/
EFI_STATUS
IpSecEspAuthVerifyPayload (
  IN UINT8                           *EspBuffer,
  IN UINTN                           EspSize,
  IN IPSEC_SAD_ENTRY                 *SadEntry,
  IN UINTN                           IcvSize
  )
{
  EFI_STATUS           Status;
  UINTN                AuthSize;
  UINT8                IcvBuffer[12];
  HASH_DATA_FRAGMENT   HashFragment[1];

  //
  // Calculate the size of authentication payload.
  //
  AuthSize  = EspSize - IcvSize;

  //
  // Calculate the icv buffer and size of the payload.
  //
  HashFragment[0].Data     = EspBuffer;
  HashFragment[0].DataSize = AuthSize;

  Status = IpSecCryptoIoHmac (
             SadEntry->Data->AlgoInfo.EspAlgoInfo.AuthAlgoId,
             SadEntry->Data->AlgoInfo.EspAlgoInfo.AuthKey,
             SadEntry->Data->AlgoInfo.EspAlgoInfo.AuthKeyLength,
             HashFragment,
             1,
             IcvBuffer,
             IcvSize
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Compare the calculated icv and the appended original icv.
  //
  if (CompareMem (EspBuffer + AuthSize, IcvBuffer, IcvSize) == 0) {
    return EFI_SUCCESS;
  }

  DEBUG ((DEBUG_ERROR, "Error auth verify payload\n"));
  return EFI_ACCESS_DENIED;
}

/**
  Search the related SAD entry by the input .

  @param[in]  IpHead       The pointer to IP header.
  @param[in]  IpVersion    The version of IP (IP4 or IP6).
  @param[in]  Spi          The SPI used to search the related SAD entry.


  @retval     NULL             Not find the related SAD entry.
  @retval     IPSEC_SAD_ENTRY  Return the related SAD entry.

**/
IPSEC_SAD_ENTRY *
IpSecFoundSadFromInboundPacket (
   UINT8   *IpHead,
   UINT8   IpVersion,
   UINT32  Spi
   )
{
  EFI_IP_ADDRESS   DestIp;

  //
  // Parse destination address from ip header.
  //
  ZeroMem (&DestIp, sizeof (EFI_IP_ADDRESS));
  if (IpVersion == IP_VERSION_4) {
    CopyMem (
      &DestIp,
      &((IP4_HEAD *) IpHead)->Dst,
      sizeof (IP4_ADDR)
      );
  } else {
    CopyMem (
      &DestIp,
      &((EFI_IP6_HEADER *) IpHead)->DestinationAddress,
      sizeof (EFI_IPv6_ADDRESS)
      );
  }

  //
  // Lookup SAD entry according to the spi and dest address.
  //
  return IpSecLookupSadBySpi (Spi, &DestIp, IpVersion);
}

/**
  Validate the IP6 extension header format for both the packets we received
  and that we will transmit.

  @param[in]  NextHeader    The next header field in IPv6 basic header.
  @param[in]  ExtHdrs       The first bye of the option.
  @param[in]  ExtHdrsLen    The length of the whole option.
  @param[out] LastHeader    The pointer of NextHeader of the last extension
                            header processed by IP6.
  @param[out] RealExtsLen   The length of extension headers processed by IP6 layer.
                            This is an optional parameter that may be NULL.

  @retval     TRUE          The option is properly formated.
  @retval     FALSE         The option is malformated.

**/
BOOLEAN
IpSecIsIp6ExtsValid (
  IN UINT8                  *NextHeader,
  IN UINT8                  *ExtHdrs,
  IN UINT32                 ExtHdrsLen,
  OUT UINT8                 **LastHeader,
  OUT UINT32                *RealExtsLen    OPTIONAL
  )
{
  UINT32                     Pointer;
  UINT8                      *Option;
  UINT8                      OptionLen;
  BOOLEAN                    Flag;
  UINT8                      CountD;
  UINT8                      CountF;
  UINT8                      CountA;

  if (RealExtsLen != NULL) {
    *RealExtsLen = 0;
  }

  *LastHeader = NextHeader;

  if (ExtHdrs == NULL && ExtHdrsLen == 0) {
    return TRUE;
  }

  if ((ExtHdrs == NULL && ExtHdrsLen != 0) || (ExtHdrs != NULL && ExtHdrsLen == 0)) {
    return FALSE;
  }

  Pointer = 0;
  Flag    = FALSE;
  CountD  = 0;
  CountF  = 0;
  CountA  = 0;

  while (Pointer <= ExtHdrsLen) {

    switch (*NextHeader) {
    case IP6_HOP_BY_HOP:
      if (Pointer != 0) {
        return FALSE;
      }

      Flag = TRUE;

    //
    // Fall through
    //
    case IP6_DESTINATION:
      if (*NextHeader == IP6_DESTINATION) {
        CountD++;
      }

      if (CountD > 2) {
        return FALSE;
      }

      NextHeader = ExtHdrs + Pointer;

      Pointer++;
      Option     = ExtHdrs + Pointer;
      OptionLen  = (UINT8) ((*Option + 1) * 8 - 2);
      Option++;
      Pointer++;

      Pointer = Pointer + OptionLen;
      break;

    case IP6_FRAGMENT:
      if (++CountF > 1) {
        return FALSE;
      }
      //
      // RFC2402, AH header should after fragment header.
      //
      if (CountA > 1) {
        return FALSE;
      }

      NextHeader = ExtHdrs + Pointer;
      Pointer    = Pointer + 8;
      break;

    case IP6_AH:
      if (++CountA > 1) {
        return FALSE;
      }

      Option     = ExtHdrs + Pointer;
      NextHeader = Option;
      Option++;
      //
      // RFC2402, Payload length is specified in 32-bit words, minus "2".
      //
      OptionLen  = (UINT8) ((*Option + 2) * 4);
      Pointer    = Pointer + OptionLen;
      break;

    default:
      *LastHeader = NextHeader;
       if (RealExtsLen != NULL) {
         *RealExtsLen = Pointer;
       }

       return TRUE;
    }
  }

  *LastHeader = NextHeader;

  if (RealExtsLen != NULL) {
    *RealExtsLen = Pointer;
  }

  return TRUE;
}

/**
  The actual entry to process the tunnel header and inner header for tunnel mode
  outbound traffic.

  This function is the subfunction of IpSecEspInboundPacket(). It change the destination
  Ip address to the station address and recalculate the uplayyer's checksum.


  @param[in, out] IpHead             Points to the IP header containing the ESP header
                                     to be trimed on input, and without ESP header
                                     on return.
  @param[in]      IpPayload          The decrypted Ip payload. It start from the inner
                                     header.
  @param[in]      IpVersion          The version of IP.
  @param[in]      SadData            Pointer of the relevant SAD.
  @param[in, out] LastHead           The Last Header in IP header on return.

**/
VOID
IpSecTunnelInboundPacket (
  IN OUT UINT8           *IpHead,
  IN     UINT8           *IpPayload,
  IN     UINT8           IpVersion,
  IN     IPSEC_SAD_DATA  *SadData,
  IN OUT UINT8           *LastHead
  )
{
  EFI_UDP_HEADER   *UdpHeader;
  TCP_HEAD         *TcpHeader;
  UINT16            *Checksum;
  UINT16           PseudoChecksum;
  UINT16           PacketChecksum;
  UINT32           OptionLen;
  IP6_ICMP_HEAD    *Icmp6Head;

  Checksum = NULL;

  if (IpVersion == IP_VERSION_4) {
    //
    // Zero OutIP header use this to indicate the input packet is under
    // IPsec Tunnel protected.
    //
    ZeroMem (
      (IP4_HEAD *)IpHead,
      sizeof (IP4_HEAD)
      );
    CopyMem (
      &((IP4_HEAD *)IpPayload)->Dst,
      &SadData->TunnelDestAddress.v4,
      sizeof (EFI_IPv4_ADDRESS)
      );

    //
    // Recalculate IpHeader Checksum
    //
    if (((IP4_HEAD *)(IpPayload))->Checksum != 0 ) {
      ((IP4_HEAD *)(IpPayload))->Checksum = 0;
      ((IP4_HEAD *)(IpPayload))->Checksum = (UINT16) (~NetblockChecksum (
                                                        (UINT8 *)IpPayload,
                                                        ((IP4_HEAD *)IpPayload)->HeadLen << 2
                                                        ));


    }

    //
    // Recalcualte PseudoChecksum
    //
    switch (((IP4_HEAD *)IpPayload)->Protocol) {
    case EFI_IP_PROTO_UDP :
      UdpHeader = (EFI_UDP_HEADER *)((UINT8 *)IpPayload + (((IP4_HEAD *)IpPayload)->HeadLen << 2));
      Checksum  = & UdpHeader->Checksum;
      *Checksum = 0;
      break;

    case EFI_IP_PROTO_TCP:
      TcpHeader = (TCP_HEAD *) ((UINT8 *)IpPayload + (((IP4_HEAD *)IpPayload)->HeadLen << 2));
      Checksum  = &TcpHeader->Checksum;
      *Checksum = 0;
      break;

    default:
      break;
      }
    PacketChecksum = NetblockChecksum (
                       (UINT8 *)IpPayload + (((IP4_HEAD *)IpPayload)->HeadLen << 2),
                       NTOHS (((IP4_HEAD *)IpPayload)->TotalLen) - (((IP4_HEAD *)IpPayload)->HeadLen << 2)
                       );
    PseudoChecksum = NetPseudoHeadChecksum (
                       ((IP4_HEAD *)IpPayload)->Src,
                       ((IP4_HEAD *)IpPayload)->Dst,
                       ((IP4_HEAD *)IpPayload)->Protocol,
                       0
                       );

      if (Checksum != NULL) {
        *Checksum = NetAddChecksum (PacketChecksum, PseudoChecksum);
        *Checksum = (UINT16) ~(NetAddChecksum (*Checksum, HTONS((UINT16)(NTOHS (((IP4_HEAD *)IpPayload)->TotalLen) - (((IP4_HEAD *)IpPayload)->HeadLen << 2)))));
      }
    }else {
      //
      //  Zero OutIP header use this to indicate the input packet is under
      //  IPsec Tunnel protected.
      //
      ZeroMem (
        IpHead,
        sizeof (EFI_IP6_HEADER)
        );
      CopyMem (
        &((EFI_IP6_HEADER*)IpPayload)->DestinationAddress,
        &SadData->TunnelDestAddress.v6,
        sizeof (EFI_IPv6_ADDRESS)
        );

      //
      // Get the Extension Header and Header length.
      //
      IpSecIsIp6ExtsValid (
        &((EFI_IP6_HEADER *)IpPayload)->NextHeader,
        IpPayload + sizeof (EFI_IP6_HEADER),
        ((EFI_IP6_HEADER *)IpPayload)->PayloadLength,
        &LastHead,
        &OptionLen
        );

      //
      // Recalcualte PseudoChecksum
      //
      switch (*LastHead) {
      case EFI_IP_PROTO_UDP:
        UdpHeader = (EFI_UDP_HEADER *)((UINT8 *)IpPayload + sizeof (EFI_IP6_HEADER) + OptionLen);
        Checksum  = &UdpHeader->Checksum;
        *Checksum = 0;
        break;

      case EFI_IP_PROTO_TCP:
        TcpHeader = (TCP_HEAD *)(IpPayload + sizeof (EFI_IP6_HEADER) + OptionLen);
        Checksum  = &TcpHeader->Checksum;
        *Checksum = 0;
        break;

      case IP6_ICMP:
        Icmp6Head  = (IP6_ICMP_HEAD *) (IpPayload + sizeof (EFI_IP6_HEADER) + OptionLen);
        Checksum   = &Icmp6Head->Checksum;
        *Checksum  = 0;
        break;
      }
      PacketChecksum = NetblockChecksum (
                         IpPayload + sizeof (EFI_IP6_HEADER) + OptionLen,
                         NTOHS(((EFI_IP6_HEADER *)IpPayload)->PayloadLength) - OptionLen
                         );
      PseudoChecksum = NetIp6PseudoHeadChecksum (
                         &((EFI_IP6_HEADER *)IpPayload)->SourceAddress,
                         &((EFI_IP6_HEADER *)IpPayload)->DestinationAddress,
                         *LastHead,
                         0
                         );

    if (Checksum != NULL) {
      *Checksum = NetAddChecksum (PacketChecksum, PseudoChecksum);
      *Checksum = (UINT16) ~(NetAddChecksum (
                               *Checksum,
                               HTONS ((UINT16)((NTOHS (((EFI_IP6_HEADER *)(IpPayload))->PayloadLength)) - OptionLen))
                               ));
    }
  }
}

/**
  The actual entry to create inner header for tunnel mode inbound traffic.

  This function is the subfunction of IpSecEspOutboundPacket(). It create
  the sending packet by encrypting its payload and inserting ESP header in the orginal
  IP header, then return the IpHeader and IPsec protected Fragmentable.

  @param[in, out] IpHead             Points to IP header containing the orginal IP header
                                     to be processed on input, and inserted ESP header
                                     on return.
  @param[in]      IpVersion          The version of IP.
  @param[in]      SadData            The related SAD data.
  @param[in, out] LastHead           The Last Header in IP header.
  @param[in]      OptionsBuffer      Pointer to the options buffer.
  @param[in]      OptionsLength      Length of the options buffer.
  @param[in, out] FragmentTable      Pointer to a list of fragments to be protected by
                                     IPsec on input, and with IPsec protected
                                     on return.
  @param[in]      FragmentCount      The number of fragments.

  @retval EFI_SUCCESS              The operation was successful.
  @retval EFI_OUT_OF_RESOURCES     The required system resources can't be allocated.

**/
UINT8 *
IpSecTunnelOutboundPacket (
  IN OUT UINT8                   *IpHead,
  IN     UINT8                   IpVersion,
  IN     IPSEC_SAD_DATA          *SadData,
  IN OUT UINT8                   *LastHead,
  IN     VOID                    **OptionsBuffer,
  IN     UINT32                  *OptionsLength,
  IN OUT EFI_IPSEC_FRAGMENT_DATA **FragmentTable,
  IN     UINT32                  *FragmentCount
  )
{
  UINT8         *InnerHead;
  NET_BUF       *Packet;
  UINT16        PacketChecksum;
  UINT16        *Checksum;
  UINT16        PseudoChecksum;
  IP6_ICMP_HEAD *IcmpHead;

  Checksum = NULL;
  if (OptionsLength == NULL) {
    return NULL;
  }

  if (IpVersion == IP_VERSION_4) {
    InnerHead = AllocateZeroPool (sizeof (IP4_HEAD) + *OptionsLength);
    ASSERT (InnerHead != NULL);
    CopyMem (
      InnerHead,
      IpHead,
      sizeof (IP4_HEAD)
      );
    CopyMem (
      InnerHead + sizeof (IP4_HEAD),
      *OptionsBuffer,
      *OptionsLength
      );
  } else {
    InnerHead = AllocateZeroPool (sizeof (EFI_IP6_HEADER) + *OptionsLength);
    ASSERT (InnerHead != NULL);
    CopyMem (
      InnerHead,
      IpHead,
      sizeof (EFI_IP6_HEADER)
      );
    CopyMem (
      InnerHead + sizeof (EFI_IP6_HEADER),
      *OptionsBuffer,
      *OptionsLength
      );
  }
  if (OptionsBuffer != NULL) {
    if (*OptionsLength != 0) {

      *OptionsBuffer = NULL;
      *OptionsLength = 0;
    }
  }

  //
  // 2. Reassamlbe Fragment into Packet
  //
  Packet = NetbufFromExt (
             (NET_FRAGMENT *)(*FragmentTable),
             *FragmentCount,
             0,
             0,
             IpSecOnRecyclePacket,
             NULL
             );
  ASSERT (Packet != NULL);
  //
  // 3. Check the Last Header, if it is TCP, UDP or ICMP recalcualate its pesudo
  //    CheckSum.
  //
  switch (*LastHead) {
  case EFI_IP_PROTO_UDP:
    Packet->Udp = (EFI_UDP_HEADER *) NetbufGetByte (Packet, 0, 0);
    ASSERT (Packet->Udp != NULL);
    Checksum = &Packet->Udp->Checksum;
    *Checksum = 0;
    break;

  case EFI_IP_PROTO_TCP:
    Packet->Tcp = (TCP_HEAD *) NetbufGetByte (Packet, 0, 0);
    ASSERT (Packet->Tcp != NULL);
    Checksum = &Packet->Tcp->Checksum;
    *Checksum = 0;
    break;

  case IP6_ICMP:
    IcmpHead = (IP6_ICMP_HEAD *) NetbufGetByte (Packet, 0, NULL);
    ASSERT (IcmpHead != NULL);
    Checksum = &IcmpHead->Checksum;
    *Checksum = 0;
    break;

  default:
    break;
  }

  PacketChecksum = NetbufChecksum (Packet);

  if (IpVersion == IP_VERSION_4) {
    //
    // Replace the source address of Inner Header.
    //
    CopyMem (
      &((IP4_HEAD *)InnerHead)->Src,
      &SadData->SpdSelector->LocalAddress[0].Address.v4,
      sizeof (EFI_IPv4_ADDRESS)
      );

    PacketChecksum = NetbufChecksum (Packet);
    PseudoChecksum = NetPseudoHeadChecksum (
                       ((IP4_HEAD *)InnerHead)->Src,
                       ((IP4_HEAD *)InnerHead)->Dst,
                       *LastHead,
                       0
                       );

   } else {
     //
     // Replace the source address of Inner Header.
     //
     CopyMem (
       &((EFI_IP6_HEADER *)InnerHead)->SourceAddress,
       &(SadData->SpdSelector->LocalAddress[0].Address.v6),
       sizeof (EFI_IPv6_ADDRESS)
       );
     PacketChecksum = NetbufChecksum (Packet);
     PseudoChecksum = NetIp6PseudoHeadChecksum (
                      &((EFI_IP6_HEADER *)InnerHead)->SourceAddress,
                      &((EFI_IP6_HEADER *)InnerHead)->DestinationAddress,
                      *LastHead,
                      0
                      );

   }
   if (Checksum != NULL) {
     *Checksum = NetAddChecksum (PacketChecksum, PseudoChecksum);
     *Checksum = (UINT16) ~(NetAddChecksum ((UINT16)*Checksum, HTONS ((UINT16) Packet->TotalSize)));
   }

  if (Packet != NULL) {
    NetbufFree (Packet);
  }
  return InnerHead;
}

/**
  The actual entry to relative function processes the inbound traffic of ESP header.

  This function is the subfunction of IpSecProtectInboundPacket(). It checks the
  received packet security property and trim the ESP header and then returns without
  an IPsec protected IP Header and FramgmentTable.

  @param[in]      IpVersion          The version of IP.
  @param[in, out] IpHead             Points to the IP header containing the ESP header
                                     to be trimed on input, and without ESP header
                                     on return.
  @param[out]     LastHead           The Last Header in IP header on return.
  @param[in, out] OptionsBuffer      Pointer to the options buffer.
  @param[in, out] OptionsLength      Length of the options buffer.
  @param[in, out] FragmentTable      Pointer to a list of fragments in the form of IPsec
                                     protected on input, and without IPsec protected
                                     on return.
  @param[in, out] FragmentCount      The number of fragments.
  @param[out]     SpdSelector        Pointer to contain the address of SPD selector on return.
  @param[out]     RecycleEvent       The event for recycling of resources.

  @retval EFI_SUCCESS              The operation was successful.
  @retval EFI_ACCESS_DENIED        One or more following conditions is TRUE:
                                   - ESP header was not found or mal-format.
                                   - The related SAD entry was not found.
                                   - The related SAD entry does not support the ESP protocol.
  @retval EFI_OUT_OF_RESOURCES     The required system resource can't be allocated.

**/
EFI_STATUS
IpSecEspInboundPacket (
  IN     UINT8                       IpVersion,
  IN OUT VOID                        *IpHead,
     OUT UINT8                       *LastHead,
  IN OUT VOID                        **OptionsBuffer,
  IN OUT UINT32                      *OptionsLength,
  IN OUT EFI_IPSEC_FRAGMENT_DATA     **FragmentTable,
  IN OUT UINT32                      *FragmentCount,
     OUT EFI_IPSEC_SPD_SELECTOR      **SpdSelector,
     OUT EFI_EVENT                   *RecycleEvent
  )
{
  EFI_STATUS            Status;
  NET_BUF               *Payload;
  UINTN                 EspSize;
  UINTN                 IvSize;
  UINTN                 BlockSize;
  UINTN                 MiscSize;
  UINTN                 PlainPayloadSize;
  UINTN                 PaddingSize;
  UINTN                 IcvSize;
  UINT8                 *ProcessBuffer;
  EFI_ESP_HEADER        *EspHeader;
  EFI_ESP_TAIL          *EspTail;
  EFI_IPSEC_SA_ID       *SaId;
  IPSEC_SAD_DATA        *SadData;
  IPSEC_SAD_ENTRY       *SadEntry;
  IPSEC_RECYCLE_CONTEXT *RecycleContext;
  UINT8                 NextHeader;
  UINT16                IpSecHeadSize;
  UINT8                 *InnerHead;

  Status            = EFI_SUCCESS;
  Payload           = NULL;
  ProcessBuffer     = NULL;
  RecycleContext    = NULL;
  *RecycleEvent     = NULL;
  PlainPayloadSize  = 0;
  NextHeader        = 0;

  //
  // Build netbuf from fragment table first.
  //
  Payload = NetbufFromExt (
              (NET_FRAGMENT *) *FragmentTable,
              *FragmentCount,
              0,
              sizeof (EFI_ESP_HEADER),
              IpSecOnRecyclePacket,
              NULL
              );
  if (Payload == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  //
  // Get the esp size and esp header from netbuf.
  //
  EspSize   = Payload->TotalSize;
  EspHeader = (EFI_ESP_HEADER *) NetbufGetByte (Payload, 0, NULL);

  if (EspHeader == NULL) {
    Status = EFI_ACCESS_DENIED;
    goto ON_EXIT;
  }

  //
  // Parse destination address from ip header and found the related SAD Entry.
  //
  SadEntry = IpSecFoundSadFromInboundPacket (
               IpHead,
               IpVersion,
               NTOHL (EspHeader->Spi)
               );

  if (SadEntry == NULL) {
    Status = EFI_ACCESS_DENIED;
    goto ON_EXIT;
  }

  SaId    = SadEntry->Id;
  SadData = SadEntry->Data;

  //
  // Only support esp protocol currently.
  //
  if (SaId->Proto != EfiIPsecESP) {
    Status = EFI_ACCESS_DENIED;
    goto ON_EXIT;
  }

  if (!SadData->ManualSet) {
    //
    // TODO: Check SA lifetime and sequence number
    //
  }

  //
  // Allocate buffer for decryption and authentication.
  //
  ProcessBuffer = AllocateZeroPool (EspSize);
  if (ProcessBuffer == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  NetbufCopy (Payload, 0, (UINT32) EspSize, ProcessBuffer);

  //
  // Get the IcvSize for authentication and BlockSize/IvSize for Decryption.
  //
  IcvSize   = IpSecGetIcvLength (SadEntry->Data->AlgoInfo.EspAlgoInfo.AuthAlgoId);
  IvSize    = IpSecGetEncryptIvLength (SadEntry->Data->AlgoInfo.EspAlgoInfo.EncAlgoId);
  BlockSize = IpSecGetEncryptBlockSize (SadEntry->Data->AlgoInfo.EspAlgoInfo.EncAlgoId);

  //
  // Make sure the ESP packet is not mal-formt.
  // 1. Check whether the Espsize is larger than ESP header + IvSize + EspTail + IcvSize.
  // 2. Check whether the left payload size is multiple of IvSize.
  //
  MiscSize = sizeof (EFI_ESP_HEADER) + IvSize + IcvSize;
  if (EspSize <= (MiscSize + sizeof (EFI_ESP_TAIL))) {
    Status = EFI_ACCESS_DENIED;
    goto ON_EXIT;
  }
  if ((EspSize - MiscSize) % BlockSize != 0) {
    Status = EFI_ACCESS_DENIED;
    goto ON_EXIT;
  }

  //
  // Authenticate the ESP packet.
  //
  if (SadData->AlgoInfo.EspAlgoInfo.AuthKey != NULL) {
    Status = IpSecEspAuthVerifyPayload (
               ProcessBuffer,
               EspSize,
               SadEntry,
               IcvSize
               );
    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }
  }
  //
  // Decrypt the payload by the SAD entry if it has decrypt key.
  //
  if (SadData->AlgoInfo.EspAlgoInfo.EncKey != NULL) {
    Status = IpSecCryptoIoDecrypt (
               SadEntry->Data->AlgoInfo.EspAlgoInfo.EncAlgoId,
               SadEntry->Data->AlgoInfo.EspAlgoInfo.EncKey,
               SadEntry->Data->AlgoInfo.EspAlgoInfo.EncKeyLength << 3,
               ProcessBuffer + sizeof (EFI_ESP_HEADER),
               ProcessBuffer + sizeof (EFI_ESP_HEADER) + IvSize,
               EspSize - sizeof (EFI_ESP_HEADER) - IvSize - IcvSize,
               ProcessBuffer + sizeof (EFI_ESP_HEADER) + IvSize
               );
    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }
  }

  //
  // Parse EspTail and compute the plain payload size.
  //
  EspTail           = (EFI_ESP_TAIL *) (ProcessBuffer + EspSize - IcvSize - sizeof (EFI_ESP_TAIL));
  PaddingSize       = EspTail->PaddingLength;
  NextHeader        = EspTail->NextHeader;

  if (EspSize <= (MiscSize + sizeof (EFI_ESP_TAIL) + PaddingSize)) {
    Status = EFI_ACCESS_DENIED;
    goto ON_EXIT;
  }
  PlainPayloadSize  = EspSize - MiscSize - sizeof (EFI_ESP_TAIL) - PaddingSize;

  //
  // TODO: handle anti-replay window
  //
  //
  // Decryption and authentication with esp has been done, so it's time to
  // reload the new packet, create recycle event and fixup ip header.
  //
  RecycleContext = AllocateZeroPool (sizeof (IPSEC_RECYCLE_CONTEXT));
  if (RecycleContext == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  IpSecRecycleCallback,
                  RecycleContext,
                  RecycleEvent
                  );
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  //
  // The caller will take responsible to handle the original fragment table
  //
  *FragmentTable = AllocateZeroPool (sizeof (EFI_IPSEC_FRAGMENT_DATA));
  if (*FragmentTable == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  RecycleContext->PayloadBuffer       = ProcessBuffer;
  RecycleContext->FragmentTable       = *FragmentTable;

  //
  // If Tunnel, recalculate upper-layyer PesudoCheckSum and trim the out
  //
  if (SadData->Mode == EfiIPsecTunnel) {
    InnerHead = ProcessBuffer + sizeof (EFI_ESP_HEADER) + IvSize;
    IpSecTunnelInboundPacket (
      IpHead,
      InnerHead,
      IpVersion,
      SadData,
      LastHead
      );

    if (IpVersion == IP_VERSION_4) {
      (*FragmentTable)[0].FragmentBuffer  = InnerHead ;
      (*FragmentTable)[0].FragmentLength  = (UINT32) PlainPayloadSize;

    }else {
      (*FragmentTable)[0].FragmentBuffer  = InnerHead;
      (*FragmentTable)[0].FragmentLength  = (UINT32) PlainPayloadSize;
    }
  } else {
    (*FragmentTable)[0].FragmentBuffer  = ProcessBuffer + sizeof (EFI_ESP_HEADER) + IvSize;
    (*FragmentTable)[0].FragmentLength  = (UINT32) PlainPayloadSize;
  }

  *FragmentCount                      = 1;

  //
  // Update the total length field in ip header since processed by esp.
  //
  if (!SadData->Mode == EfiIPsecTunnel) {
    if (IpVersion == IP_VERSION_4) {
      ((IP4_HEAD *) IpHead)->TotalLen = HTONS ((UINT16) ((((IP4_HEAD *) IpHead)->HeadLen << 2) + PlainPayloadSize));
    } else {
      IpSecHeadSize                              = IpSecGetPlainExtHeadSize (IpHead, LastHead);
      ((EFI_IP6_HEADER *) IpHead)->PayloadLength = HTONS ((UINT16)(IpSecHeadSize + PlainPayloadSize));
    }
    //
    // Update the next layer field in ip header since esp header inserted.
    //
    *LastHead = NextHeader;
  }


  //
  // Update the SPD association of the SAD entry.
  //
  *SpdSelector = SadData->SpdSelector;

ON_EXIT:
  if (Payload != NULL) {
    NetbufFree (Payload);
  }

  if (EFI_ERROR (Status)) {
    if (ProcessBuffer != NULL) {
      FreePool (ProcessBuffer);
    }

    if (RecycleContext != NULL) {
      FreePool (RecycleContext);
    }

    if (*RecycleEvent != NULL) {
      gBS->CloseEvent (*RecycleEvent);
    }
  }

  return Status;
}

/**
  The actual entry to the relative function processes the output traffic using the ESP protocol.

  This function is the subfunction of IpSecProtectOutboundPacket(). It protected
  the sending packet by encrypting its payload and inserting ESP header in the orginal
  IP header, then return the IpHeader and IPsec protected Fragmentable.

  @param[in]      IpVersion          The version of IP.
  @param[in, out] IpHead             Points to IP header containing the orginal IP header
                                     to be processed on input, and inserted ESP header
                                     on return.
  @param[in, out] LastHead           The Last Header in IP header.
  @param[in, out] OptionsBuffer      Pointer to the options buffer.
  @param[in, out] OptionsLength      Length of the options buffer.
  @param[in, out] FragmentTable      Pointer to a list of fragments to be protected by
                                     IPsec on input, and with IPsec protected
                                     on return.
  @param[in, out] FragmentCount      The number of fragments.
  @param[in]      SadEntry           The related SAD entry.
  @param[out]     RecycleEvent       The event for recycling of resources.

  @retval EFI_SUCCESS              The operation was successful.
  @retval EFI_OUT_OF_RESOURCES     The required system resources can't be allocated.

**/
EFI_STATUS
IpSecEspOutboundPacket (
  IN UINT8                           IpVersion,
  IN OUT VOID                        *IpHead,
  IN OUT UINT8                       *LastHead,
  IN OUT VOID                        **OptionsBuffer,
  IN OUT UINT32                      *OptionsLength,
  IN OUT EFI_IPSEC_FRAGMENT_DATA     **FragmentTable,
  IN OUT UINT32                      *FragmentCount,
  IN     IPSEC_SAD_ENTRY             *SadEntry,
     OUT EFI_EVENT                   *RecycleEvent
  )
{
  EFI_STATUS            Status;
  UINTN                 Index;
  EFI_IPSEC_SA_ID       *SaId;
  IPSEC_SAD_DATA        *SadData;
  IPSEC_RECYCLE_CONTEXT *RecycleContext;
  UINT8                 *ProcessBuffer;
  UINTN                 BytesCopied;
  INTN                  EncryptBlockSize;// Size of encryption block, 4 bytes aligned and >= 4
  UINTN                 EspSize;         // Total size of esp wrapped ip payload
  UINTN                 IvSize;          // Size of IV, optional, might be 0
  UINTN                 PlainPayloadSize;// Original IP payload size
  UINTN                 PaddingSize;     // Size of padding
  UINTN                 EncryptSize;     // Size of data to be encrypted, start after IV and
                                         // stop before ICV
  UINTN                 IcvSize;         // Size of ICV, optional, might be 0
  UINT8                 *RestOfPayload;  // Start of Payload after IV
  UINT8                 *Padding;        // Start address of padding
  EFI_ESP_HEADER        *EspHeader;      // Start address of ESP frame
  EFI_ESP_TAIL          *EspTail;        // Address behind padding
  UINT8                 *InnerHead;
  HASH_DATA_FRAGMENT    HashFragment[1];

  Status          = EFI_ACCESS_DENIED;
  SaId            = SadEntry->Id;
  SadData         = SadEntry->Data;
  ProcessBuffer   = NULL;
  RecycleContext  = NULL;
  *RecycleEvent   = NULL;
  InnerHead       = NULL;

  if (!SadData->ManualSet &&
      SadData->AlgoInfo.EspAlgoInfo.EncKey == NULL &&
      SadData->AlgoInfo.EspAlgoInfo.AuthKey == NULL
      ) {
    //
    // Invalid manual SAD entry configuration.
    //
    goto ON_EXIT;
  }

  //
  // Create OutHeader according to Inner Header
  //
  if (SadData->Mode == EfiIPsecTunnel) {
    InnerHead = IpSecTunnelOutboundPacket (
                  IpHead,
                  IpVersion,
                  SadData,
                  LastHead,
                  OptionsBuffer,
                  OptionsLength,
                  FragmentTable,
                  FragmentCount
                  );

    if (InnerHead == NULL) {
      return EFI_INVALID_PARAMETER;
    }

  }

  //
  // Calculate enctrypt block size, need iv by default and 4 bytes alignment.
  //
  EncryptBlockSize  = 4;

  if (SadData->AlgoInfo.EspAlgoInfo.EncKey != NULL) {
    EncryptBlockSize  = IpSecGetEncryptBlockSize (SadEntry->Data->AlgoInfo.EspAlgoInfo.EncAlgoId);

    if (EncryptBlockSize < 0 || (EncryptBlockSize != 1 && EncryptBlockSize % 4 != 0)) {
      goto ON_EXIT;
    }
  }

  //
  // Calculate the plain payload size accroding to the fragment table.
  //
  PlainPayloadSize = 0;
  for (Index = 0; Index < *FragmentCount; Index++) {
    PlainPayloadSize += (*FragmentTable)[Index].FragmentLength;
  }

  //
  // Add IPHeader size for Tunnel Mode
  //
  if (SadData->Mode == EfiIPsecTunnel) {
    if (IpVersion == IP_VERSION_4) {
      PlainPayloadSize += sizeof (IP4_HEAD);
    } else {
      PlainPayloadSize += sizeof (EFI_IP6_HEADER);
    }
    //
    // OPtions should be encryption into it
    //
    PlainPayloadSize += *OptionsLength;
  }


  //
  // Calculate icv size, optional by default and 4 bytes alignment.
  //
  IcvSize = 0;
  if (SadData->AlgoInfo.EspAlgoInfo.AuthKey != NULL) {
    IcvSize = IpSecGetIcvLength (SadEntry->Data->AlgoInfo.EspAlgoInfo.AuthAlgoId);
    if (IcvSize % 4 != 0) {
      goto ON_EXIT;
    }
  }

  //
  // Calcuate the total size of esp wrapped ip payload.
  //
  IvSize        = IpSecGetEncryptIvLength (SadEntry->Data->AlgoInfo.EspAlgoInfo.EncAlgoId);
  EncryptSize   = (PlainPayloadSize + sizeof (EFI_ESP_TAIL) + EncryptBlockSize - 1) / EncryptBlockSize * EncryptBlockSize;
  PaddingSize   = EncryptSize - PlainPayloadSize - sizeof (EFI_ESP_TAIL);
  EspSize       = sizeof (EFI_ESP_HEADER) + IvSize + EncryptSize + IcvSize;

  ProcessBuffer = AllocateZeroPool (EspSize);
  if (ProcessBuffer == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  //
  // Calculate esp header and esp tail including header, payload and padding.
  //
  EspHeader     = (EFI_ESP_HEADER *) ProcessBuffer;
  RestOfPayload = (UINT8 *) (EspHeader + 1) + IvSize;
  Padding       = RestOfPayload + PlainPayloadSize;
  EspTail       = (EFI_ESP_TAIL *) (Padding + PaddingSize);

  //
  // Fill the sn and spi fields in esp header.
  //
  EspHeader->SequenceNumber = HTONL ((UINT32) SadData->SequenceNumber + 1);
  //EspHeader->SequenceNumber = HTONL ((UINT32) SadData->SequenceNumber);
  EspHeader->Spi            = HTONL (SaId->Spi);

  //
  // Copy the rest of payload (after iv) from the original fragment buffer.
  //
  BytesCopied = 0;

  //
  // For Tunnel Mode
  //
  if (SadData->Mode == EfiIPsecTunnel) {
    if (IpVersion == IP_VERSION_4) {
      //
      // HeadLen, Total Length
      //
      ((IP4_HEAD *)InnerHead)->HeadLen  = (UINT8) ((sizeof (IP4_HEAD) + *OptionsLength) >> 2);
      ((IP4_HEAD *)InnerHead)->TotalLen = HTONS ((UINT16) PlainPayloadSize);
      ((IP4_HEAD *)InnerHead)->Checksum = 0;
      ((IP4_HEAD *)InnerHead)->Checksum = (UINT16) (~NetblockChecksum (
                                                  (UINT8 *)InnerHead,
                                                  sizeof(IP4_HEAD)
                                                  ));
      CopyMem (
        RestOfPayload + BytesCopied,
        InnerHead,
        sizeof (IP4_HEAD) + *OptionsLength
        );
      BytesCopied += sizeof (IP4_HEAD) + *OptionsLength;

    } else {
    ((EFI_IP6_HEADER *)InnerHead)->PayloadLength = HTONS ((UINT16) (PlainPayloadSize - sizeof (EFI_IP6_HEADER)));
      CopyMem (
        RestOfPayload + BytesCopied,
        InnerHead,
        sizeof (EFI_IP6_HEADER) + *OptionsLength
        );
      BytesCopied += sizeof (EFI_IP6_HEADER) + *OptionsLength;
    }
  }

  for (Index = 0; Index < *FragmentCount; Index++) {
    CopyMem (
      (RestOfPayload + BytesCopied),
      (*FragmentTable)[Index].FragmentBuffer,
      (*FragmentTable)[Index].FragmentLength
      );
    BytesCopied += (*FragmentTable)[Index].FragmentLength;
  }
  //
  // Fill the padding buffer by natural number sequence.
  //
  for (Index = 0; Index < PaddingSize; Index++) {
    Padding[Index] = (UINT8) (Index + 1);
  }
  //
  // Fill the padding length and next header fields in esp tail.
  //
  EspTail->PaddingLength  = (UINT8) PaddingSize;
  EspTail->NextHeader     = *LastHead;

  //
  // Fill the next header for Tunnel mode.
  //
  if (SadData->Mode == EfiIPsecTunnel) {
    if (IpVersion == IP_VERSION_4) {
      EspTail->NextHeader = 4;
    } else {
      EspTail->NextHeader = 41;
    }
  }

  //
  // Generate iv at random by crypt library.
  //
  Status = IpSecGenerateIv (
             (UINT8 *) (EspHeader + 1),
             IvSize
             );


  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  //
  // Encryption the payload (after iv) by the SAD entry if has encrypt key.
  //
  if (SadData->AlgoInfo.EspAlgoInfo.EncKey != NULL) {
    Status = IpSecCryptoIoEncrypt (
               SadEntry->Data->AlgoInfo.EspAlgoInfo.EncAlgoId,
               SadEntry->Data->AlgoInfo.EspAlgoInfo.EncKey,
               SadEntry->Data->AlgoInfo.EspAlgoInfo.EncKeyLength << 3,
               (UINT8 *)(EspHeader + 1),
               RestOfPayload,
               EncryptSize,
               RestOfPayload
               );

    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }
  }

  //
  // Authenticate the esp wrapped buffer by the SAD entry if it has auth key.
  //
  if (SadData->AlgoInfo.EspAlgoInfo.AuthKey != NULL) {

    HashFragment[0].Data     = ProcessBuffer;
    HashFragment[0].DataSize = EspSize - IcvSize;
    Status = IpSecCryptoIoHmac (
               SadEntry->Data->AlgoInfo.EspAlgoInfo.AuthAlgoId,
               SadEntry->Data->AlgoInfo.EspAlgoInfo.AuthKey,
               SadEntry->Data->AlgoInfo.EspAlgoInfo.AuthKeyLength,
               HashFragment,
               1,
               ProcessBuffer + EspSize - IcvSize,
               IcvSize
               );
    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }
  }

  //
  // Encryption and authentication with esp has been done, so it's time to
  // reload the new packet, create recycle event and fixup ip header.
  //
  RecycleContext = AllocateZeroPool (sizeof (IPSEC_RECYCLE_CONTEXT));
  if (RecycleContext == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  IpSecRecycleCallback,
                  RecycleContext,
                  RecycleEvent
                  );
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }
  //
  // Caller take responsible to handle the original fragment table.
  //
  *FragmentTable = AllocateZeroPool (sizeof (EFI_IPSEC_FRAGMENT_DATA));
  if (*FragmentTable == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  RecycleContext->FragmentTable       = *FragmentTable;
  RecycleContext->PayloadBuffer       = ProcessBuffer;
  (*FragmentTable)[0].FragmentBuffer  = ProcessBuffer;
  (*FragmentTable)[0].FragmentLength  = (UINT32) EspSize;
  *FragmentCount                      = 1;

  //
  // Update the total length field in ip header since processed by esp.
  //
  if (IpVersion == IP_VERSION_4) {
    ((IP4_HEAD *) IpHead)->TotalLen = HTONS ((UINT16) ((((IP4_HEAD *) IpHead)->HeadLen << 2) + EspSize));
  } else {
    ((EFI_IP6_HEADER *) IpHead)->PayloadLength = (UINT16) (IpSecGetPlainExtHeadSize (IpHead, LastHead) + EspSize);
  }

  //
  // If tunnel mode, it should change the outer Ip header with tunnel source address
  // and destination tunnel address.
  //
  if (SadData->Mode == EfiIPsecTunnel) {
    if (IpVersion == IP_VERSION_4) {
      CopyMem (
        &((IP4_HEAD *) IpHead)->Src,
        &SadData->TunnelSourceAddress.v4,
        sizeof (EFI_IPv4_ADDRESS)
        );
      CopyMem (
        &((IP4_HEAD *) IpHead)->Dst,
        &SadData->TunnelDestAddress.v4,
        sizeof (EFI_IPv4_ADDRESS)
        );
    } else {
      CopyMem (
        &((EFI_IP6_HEADER *) IpHead)->SourceAddress,
        &SadData->TunnelSourceAddress.v6,
        sizeof (EFI_IPv6_ADDRESS)
        );
      CopyMem (
        &((EFI_IP6_HEADER *) IpHead)->DestinationAddress,
        &SadData->TunnelDestAddress.v6,
        sizeof (EFI_IPv6_ADDRESS)
        );
    }
  }

  //
  // Update the next layer field in ip header since esp header inserted.
  //
  *LastHead = IPSEC_ESP_PROTOCOL;

  //
  // Increase the sn number in SAD entry according to rfc4303.
  //
  SadData->SequenceNumber++;

ON_EXIT:
  if (EFI_ERROR (Status)) {
    if (ProcessBuffer != NULL) {
      FreePool (ProcessBuffer);
    }

    if (RecycleContext != NULL) {
      FreePool (RecycleContext);
    }

    if (*RecycleEvent != NULL) {
      gBS->CloseEvent (*RecycleEvent);
    }
  }

  return Status;
}

/**
  This function processes the inbound traffic with IPsec.

  It checks the received packet security property, trims the ESP/AH header, and then
  returns without an IPsec protected IP Header and FragmentTable.

  @param[in]      IpVersion          The version of IP.
  @param[in, out] IpHead             Points to IP header containing the ESP/AH header
                                     to be trimed on input, and without ESP/AH header
                                     on return.
  @param[in, out] LastHead           The Last Header in IP header on return.
  @param[in, out] OptionsBuffer      Pointer to the options buffer.
  @param[in, out] OptionsLength      Length of the options buffer.
  @param[in, out] FragmentTable      Pointer to a list of fragments in form of IPsec
                                     protected on input, and without IPsec protected
                                     on return.
  @param[in, out] FragmentCount      The number of fragments.
  @param[out]     SpdEntry           Pointer to contain the address of SPD entry on return.
  @param[out]     RecycleEvent       The event for recycling of resources.

  @retval EFI_SUCCESS              The operation was successful.
  @retval EFI_UNSUPPORTED          The IPSEC protocol is not supported.

**/
EFI_STATUS
IpSecProtectInboundPacket (
  IN     UINT8                       IpVersion,
  IN OUT VOID                        *IpHead,
  IN OUT UINT8                       *LastHead,
  IN OUT VOID                        **OptionsBuffer,
  IN OUT UINT32                      *OptionsLength,
  IN OUT EFI_IPSEC_FRAGMENT_DATA     **FragmentTable,
  IN OUT UINT32                      *FragmentCount,
     OUT EFI_IPSEC_SPD_SELECTOR      **SpdEntry,
     OUT EFI_EVENT                   *RecycleEvent
  )
{
  if (*LastHead == IPSEC_ESP_PROTOCOL) {
    //
    // Process the esp ipsec header of the inbound traffic.
    //
    return IpSecEspInboundPacket (
             IpVersion,
             IpHead,
             LastHead,
             OptionsBuffer,
             OptionsLength,
             FragmentTable,
             FragmentCount,
             SpdEntry,
             RecycleEvent
             );
  }
  //
  // The other protocols are not supported.
  //
  return EFI_UNSUPPORTED;
}

/**
  This fucntion processes the output traffic with IPsec.

  It protected the sending packet by encrypting it payload and inserting ESP/AH header
  in the orginal IP header, then return the IpHeader and IPsec protected Fragmentable.

  @param[in]      IpVersion          The version of IP.
  @param[in, out] IpHead             Point to IP header containing the orginal IP header
                                     to be processed on input, and inserted ESP/AH header
                                     on return.
  @param[in, out] LastHead           The Last Header in IP header.
  @param[in, out] OptionsBuffer      Pointer to the options buffer.
  @param[in, out] OptionsLength      Length of the options buffer.
  @param[in, out] FragmentTable      Pointer to a list of fragments to be protected by
                                     IPsec on input, and with IPsec protected
                                     on return.
  @param[in, out] FragmentCount      Number of fragments.
  @param[in]      SadEntry           Related SAD entry.
  @param[out]     RecycleEvent       Event for recycling of resources.

  @retval EFI_SUCCESS              The operation is successful.
  @retval EFI_UNSUPPORTED          If the IPSEC protocol is not supported.

**/
EFI_STATUS
IpSecProtectOutboundPacket (
  IN     UINT8                       IpVersion,
  IN OUT VOID                        *IpHead,
  IN OUT UINT8                       *LastHead,
  IN OUT VOID                        **OptionsBuffer,
  IN OUT UINT32                      *OptionsLength,
  IN OUT EFI_IPSEC_FRAGMENT_DATA     **FragmentTable,
  IN OUT UINT32                      *FragmentCount,
  IN     IPSEC_SAD_ENTRY             *SadEntry,
     OUT EFI_EVENT                   *RecycleEvent
  )
{
  if (SadEntry->Id->Proto == EfiIPsecESP) {
    //
    // Process the esp ipsec header of the outbound traffic.
    //
    return IpSecEspOutboundPacket (
             IpVersion,
             IpHead,
             LastHead,
             OptionsBuffer,
             OptionsLength,
             FragmentTable,
             FragmentCount,
             SadEntry,
             RecycleEvent
             );
  }
  //
  // The other protocols are not supported.
  //
  return EFI_UNSUPPORTED;
}
