/** @file
  The implementation for Ping shell command.

  (C) Copyright 2015 Hewlett-Packard Development Company, L.P.<BR>
  Copyright (c) 2009 - 2017, Intel Corporation. All rights reserved.<BR>
  (C) Copyright 2016 Hewlett Packard Enterprise Development LP<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 "UefiShellNetwork1CommandsLib.h"

#define PING_IP4_COPY_ADDRESS(Dest, Src) (CopyMem ((Dest), (Src), sizeof (EFI_IPv4_ADDRESS)))

UINT64          mCurrentTick = 0;

//
// Function templates to match the IPv4 and IPv6 commands that we use.
//
typedef 
EFI_STATUS
(EFIAPI *PING_IPX_POLL)(
  IN VOID          *This
  );  

typedef 
EFI_STATUS
(EFIAPI *PING_IPX_TRANSMIT)(
  IN VOID          *This,
  IN VOID          *Token
  );

typedef 
EFI_STATUS
(EFIAPI *PING_IPX_RECEIVE)(
  IN VOID          *This,
  IN VOID          *Token
  ); 

typedef
EFI_STATUS
(EFIAPI *PING_IPX_CANCEL)(
  IN VOID          *This,
  IN VOID          *Token OPTIONAL
  );

///
/// A set of pointers to either IPv6 or IPv4 functions.  
/// Unknown which one to the ping command.
///
typedef struct {
  PING_IPX_TRANSMIT             Transmit;
  PING_IPX_RECEIVE              Receive;
  PING_IPX_CANCEL               Cancel;
  PING_IPX_POLL                 Poll;
}PING_IPX_PROTOCOL;


typedef union {
  VOID                  *RxData;
  VOID                  *TxData;
} PING_PACKET;

//
// PING_IPX_COMPLETION_TOKEN
// structures are used for both transmit and receive operations. 
// This version is IP-unaware.
//
typedef struct {
  EFI_EVENT               Event;
  EFI_STATUS              Status;
  PING_PACKET             Packet;
} PING_IPX_COMPLETION_TOKEN;

#pragma pack(1)
typedef struct _ICMPX_ECHO_REQUEST_REPLY {
  UINT8                       Type;
  UINT8                       Code;
  UINT16                      Checksum;
  UINT16                      Identifier;
  UINT16                      SequenceNum;
  UINT32                      TimeStamp;
  UINT8                       Data[1];
} ICMPX_ECHO_REQUEST_REPLY;
#pragma pack()

typedef struct _PING_ICMP_TX_INFO {
  LIST_ENTRY                Link;
  UINT16                    SequenceNum;
  UINT32                    TimeStamp;
  PING_IPX_COMPLETION_TOKEN *Token;
} PING_ICMPX_TX_INFO;

#define DEFAULT_TIMEOUT       5000
#define MAX_SEND_NUMBER       10000
#define MAX_BUFFER_SIZE       32768
#define DEFAULT_TIMER_PERIOD  358049
#define ONE_SECOND            10000000
#define PING_IP_CHOICE_IP4    1
#define PING_IP_CHOICE_IP6    2
#define DEFAULT_SEND_COUNT    10
#define DEFAULT_BUFFER_SIZE   16
#define ICMP_V4_ECHO_REQUEST  0x8
#define ICMP_V4_ECHO_REPLY    0x0
#define STALL_1_MILLI_SECOND  1000

#define PING_PRIVATE_DATA_SIGNATURE  SIGNATURE_32 ('P', 'i', 'n', 'g')
typedef struct _PING_PRIVATE_DATA {
  UINT32                      Signature;
  EFI_HANDLE                  NicHandle;
  EFI_HANDLE                  IpChildHandle;
  EFI_EVENT                   Timer;

  UINT32                      TimerPeriod;
  UINT32                      RttTimerTick;   
  EFI_EVENT                   RttTimer;

  EFI_STATUS                  Status;
  LIST_ENTRY                  TxList;
  UINT16                      RxCount;
  UINT16                      TxCount;
  UINT64                      RttSum;
  UINT64                      RttMin;
  UINT64                      RttMax;
  UINT32                      SequenceNum;

  UINT32                      SendNum;
  UINT32                      BufferSize;
  UINT32                      IpChoice;

  PING_IPX_PROTOCOL           ProtocolPointers;
  VOID                        *IpProtocol;
  UINT8                       SrcAddress[MAX(sizeof(EFI_IPv6_ADDRESS)        , sizeof(EFI_IPv4_ADDRESS)          )];
  UINT8                       DstAddress[MAX(sizeof(EFI_IPv6_ADDRESS)        , sizeof(EFI_IPv4_ADDRESS)          )];
  PING_IPX_COMPLETION_TOKEN   RxToken;
  UINT16                      FailedCount;
} PING_PRIVATE_DATA;

/**
  Calculate the internet checksum (see RFC 1071).

  @param[in] Packet  Buffer which contains the data to be checksummed.
  @param[in] Length  Length to be checksummed.

  @retval Checksum     Returns the 16 bit ones complement of 
                       ones complement sum of 16 bit words
**/
UINT16
NetChecksum (
  IN UINT8   *Buffer,
  IN UINT32  Length
  )
{
  UINT32  Sum;
  UINT8   Odd;
  UINT16  *Packet;

  Packet  = (UINT16 *) Buffer;

  Sum     = 0;
  Odd     = (UINT8) (Length & 1);
  Length >>= 1;
  while ((Length--) != 0) {
    Sum += *Packet++;
  }

  if (Odd != 0) {
    Sum += *(UINT8 *) Packet;
  }

  Sum = (Sum & 0xffff) + (Sum >> 16);

  //
  // in case above carried
  //
  Sum += Sum >> 16;

  return (UINT16) Sum;
}

/**
  Reads and returns the current value of register.
  In IA64, the register is the Interval Timer Vector (ITV).
  In X86(IA32/X64), the register is the Time Stamp Counter (TSC)

  @return The current value of the register.

**/

STATIC CONST SHELL_PARAM_ITEM    PingParamList[] = {
  {
    L"-l",
    TypeValue
  },
  {
    L"-n",
    TypeValue
  },
  {
    L"-s",
    TypeValue
  },
  {
    L"-_s",
    TypeValue
  },
  {
    L"-_ip6",
    TypeFlag
  },
  {
    NULL,
    TypeMax
  },
};

//
// Global Variables in Ping command.
//
STATIC CONST CHAR16      *mDstString;
STATIC CONST CHAR16      *mSrcString;

/**
  RTT timer tick routine.

  @param[in]    Event    A EFI_EVENT type event.
  @param[in]    Context  The pointer to Context.

**/
VOID
EFIAPI
RttTimerTickRoutine (
  IN EFI_EVENT    Event,
  IN VOID         *Context
  )
{
  UINT32     *RttTimerTick;

  RttTimerTick = (UINT32*) Context;
  (*RttTimerTick)++;
}

/**
  Get the timer period of the system.

  This function tries to get the system timer period by creating
  an 1ms period timer.

  @return     System timer period in MS, or 0 if operation failed.

**/
UINT32
GetTimerPeriod(
  VOID
  )
{
  EFI_STATUS                 Status;
  UINT32                     RttTimerTick;
  EFI_EVENT                  TimerEvent;
  UINT32                     StallCounter;
  EFI_TPL                    OldTpl;

  RttTimerTick = 0;
  StallCounter   = 0;

  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  RttTimerTickRoutine,
                  &RttTimerTick,
                  &TimerEvent
                  );
  if (EFI_ERROR (Status)) {
    return 0;
  }

  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
  Status = gBS->SetTimer (
                  TimerEvent,
                  TimerPeriodic,
                  TICKS_PER_MS
                  );
  if (EFI_ERROR (Status)) {
    gBS->CloseEvent (TimerEvent);
    return 0;
  }

  while (RttTimerTick < 10) {
    gBS->Stall (STALL_1_MILLI_SECOND);
    ++StallCounter;
  }

  gBS->RestoreTPL (OldTpl);

  gBS->SetTimer (TimerEvent, TimerCancel, 0);
  gBS->CloseEvent (TimerEvent);

  return StallCounter / RttTimerTick;
}

/**
  Initialize the timer event for RTT (round trip time).

  @param[in]    Private    The pointer to PING_PRIVATE_DATA.

  @retval EFI_SUCCESS      RTT timer is started.
  @retval Others           Failed to start the RTT timer.

**/
EFI_STATUS
PingInitRttTimer (
  PING_PRIVATE_DATA      *Private
  )
{
  EFI_STATUS                 Status;

  Private->TimerPeriod = GetTimerPeriod ();
  if (Private->TimerPeriod == 0) {
    return EFI_ABORTED;
  }
  
  Private->RttTimerTick = 0;
  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  RttTimerTickRoutine,
                  &Private->RttTimerTick,
                  &Private->RttTimer
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->SetTimer (
                  Private->RttTimer,
                  TimerPeriodic,
                  TICKS_PER_MS
                  );
  if (EFI_ERROR (Status)) {
    gBS->CloseEvent (Private->RttTimer);
    return Status;
  }

  return EFI_SUCCESS;
}

/**
  Free RTT timer event resource.

  @param[in]    Private    The pointer to PING_PRIVATE_DATA.

**/
VOID
PingFreeRttTimer (
  PING_PRIVATE_DATA      *Private
  )
{
  if (Private->RttTimer != NULL) {
    gBS->SetTimer (Private->RttTimer, TimerCancel, 0);
    gBS->CloseEvent (Private->RttTimer);
  }
}

/**
  Read the current time.
  
  @param[in]    Private    The pointer to PING_PRIVATE_DATA.

  @retval the current tick value.
**/
UINT32
ReadTime (
  PING_PRIVATE_DATA      *Private
  )
{
  return Private->RttTimerTick;
}

/**
  Calculate a duration in ms.

  @param[in]    Private   The pointer to PING_PRIVATE_DATA.
  @param[in]    Begin     The start point of time.
  @param[in]    End       The end point of time.

  @return               The duration in ms.
  @retval 0             The parameters were not valid.
**/
UINT32
CalculateTick (
  PING_PRIVATE_DATA      *Private,
  IN UINT32              Begin,
  IN UINT32              End
  )
{
  if (End < Begin) {
    return (0);
  }

  return (End - Begin) * Private->TimerPeriod;
}

/**
  Destroy PING_ICMPX_TX_INFO, and recollect the memory.

  @param[in]    TxInfo    The pointer to PING_ICMPX_TX_INFO.
  @param[in]    IpChoice  Whether the token is IPv4 or IPv6
**/
VOID
PingDestroyTxInfo (
  IN PING_ICMPX_TX_INFO    *TxInfo,
  IN UINT32                IpChoice
  )
{
  EFI_IP6_TRANSMIT_DATA    *Ip6TxData;
  EFI_IP4_TRANSMIT_DATA    *Ip4TxData;
  EFI_IP6_FRAGMENT_DATA    *FragData;
  UINTN                    Index;

  if (TxInfo == NULL) {
    return;
  }

  if (TxInfo->Token != NULL) {

    if (TxInfo->Token->Event != NULL) {
      gBS->CloseEvent (TxInfo->Token->Event);
    }

    if (TxInfo->Token->Packet.TxData != NULL) {
      if (IpChoice == PING_IP_CHOICE_IP6) {
        Ip6TxData = TxInfo->Token->Packet.TxData;

        if (Ip6TxData->OverrideData != NULL) {
          FreePool (Ip6TxData->OverrideData);
        }

        if (Ip6TxData->ExtHdrs != NULL) {
          FreePool (Ip6TxData->ExtHdrs);
        }

        for (Index = 0; Index < Ip6TxData->FragmentCount; Index++) {
          FragData = Ip6TxData->FragmentTable[Index].FragmentBuffer;
          if (FragData != NULL) {
            FreePool (FragData);
          }
        }
      } else {
        Ip4TxData = TxInfo->Token->Packet.TxData;

        if (Ip4TxData->OverrideData != NULL) {
          FreePool (Ip4TxData->OverrideData);
        }

        for (Index = 0; Index < Ip4TxData->FragmentCount; Index++) {
          FragData = Ip4TxData->FragmentTable[Index].FragmentBuffer;
          if (FragData != NULL) {
            FreePool (FragData);
          }
        }
      }
    }

    FreePool (TxInfo->Token);
  }

  FreePool (TxInfo);
}

/**
  Match the request, and reply with SequenceNum/TimeStamp.

  @param[in]    Private    The pointer to PING_PRIVATE_DATA.
  @param[in]    Packet     The pointer to ICMPX_ECHO_REQUEST_REPLY.

  @retval EFI_SUCCESS      The match is successful.
  @retval EFI_NOT_FOUND    The reply can't be matched with any request.

**/
EFI_STATUS
Ping6MatchEchoReply (
  IN PING_PRIVATE_DATA           *Private,
  IN ICMPX_ECHO_REQUEST_REPLY    *Packet
  )
{
  PING_ICMPX_TX_INFO     *TxInfo;
  LIST_ENTRY             *Entry;
  LIST_ENTRY             *NextEntry;

  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Private->TxList) {
    TxInfo = BASE_CR (Entry, PING_ICMPX_TX_INFO, Link);

    if ((TxInfo->SequenceNum == Packet->SequenceNum) && (TxInfo->TimeStamp == Packet->TimeStamp)) {
      Private->RxCount++;
      RemoveEntryList (&TxInfo->Link);
      PingDestroyTxInfo (TxInfo, Private->IpChoice);
      return EFI_SUCCESS;
    }
  }

  return EFI_NOT_FOUND;
}

/**
  The original intention is to send a request.
  Currently, the application retransmits an icmp6 echo request packet
  per second in sendnumber times that is specified by the user.
  Because nothing can be done here, all things move to the timer rountine.

  @param[in]    Event      A EFI_EVENT type event.
  @param[in]    Context    The pointer to Context.

**/
VOID
EFIAPI
Ping6OnEchoRequestSent (
  IN EFI_EVENT    Event,
  IN VOID         *Context
  )
{
}

/**
  receive reply, match and print reply infomation.

  @param[in]    Event      A EFI_EVENT type event.
  @param[in]    Context    The pointer to context.

**/
VOID
EFIAPI
Ping6OnEchoReplyReceived (
  IN EFI_EVENT    Event,
  IN VOID         *Context
  )
{
  EFI_STATUS                  Status;
  PING_PRIVATE_DATA           *Private;
  ICMPX_ECHO_REQUEST_REPLY    *Reply;
  UINT32                      PayLoad;
  UINT32                      Rtt;

  Private = (PING_PRIVATE_DATA *) Context;

  if (Private == NULL || Private->Status == EFI_ABORTED || Private->Signature != PING_PRIVATE_DATA_SIGNATURE) {
    return;
  }

  if (Private->RxToken.Packet.RxData == NULL) {
    return;
  }

  if (Private->IpChoice == PING_IP_CHOICE_IP6) {
    Reply   = ((EFI_IP6_RECEIVE_DATA*)Private->RxToken.Packet.RxData)->FragmentTable[0].FragmentBuffer;
    PayLoad = ((EFI_IP6_RECEIVE_DATA*)Private->RxToken.Packet.RxData)->DataLength;
    if (((EFI_IP6_RECEIVE_DATA*)Private->RxToken.Packet.RxData)->Header->NextHeader != IP6_ICMP) {
      goto ON_EXIT;
    }
    if (!IP6_IS_MULTICAST ((EFI_IPv6_ADDRESS*)&Private->DstAddress) && 
        !EFI_IP6_EQUAL (&((EFI_IP6_RECEIVE_DATA*)Private->RxToken.Packet.RxData)->Header->SourceAddress, (EFI_IPv6_ADDRESS*)&Private->DstAddress)) {
      goto ON_EXIT;
    }

    if ((Reply->Type != ICMP_V6_ECHO_REPLY) || (Reply->Code != 0)) {
      goto ON_EXIT;
    }
  } else {
    Reply   = ((EFI_IP4_RECEIVE_DATA*)Private->RxToken.Packet.RxData)->FragmentTable[0].FragmentBuffer;
    PayLoad = ((EFI_IP4_RECEIVE_DATA*)Private->RxToken.Packet.RxData)->DataLength;
    if (!IP4_IS_MULTICAST (EFI_IP4(*(EFI_IPv4_ADDRESS*)Private->DstAddress)) && 
        !EFI_IP4_EQUAL (&((EFI_IP4_RECEIVE_DATA*)Private->RxToken.Packet.RxData)->Header->SourceAddress, (EFI_IPv4_ADDRESS*)&Private->DstAddress)) {
      goto ON_EXIT;
    }

    if ((Reply->Type != ICMP_V4_ECHO_REPLY) || (Reply->Code != 0)) {
      goto ON_EXIT;
    }
  }
  

  if (PayLoad != Private->BufferSize) {
    goto ON_EXIT;
  }
  //
  // Check whether the reply matches the sent request before.
  //
  Status = Ping6MatchEchoReply (Private, Reply);
  if (EFI_ERROR(Status)) {
    goto ON_EXIT;
  }
  //
  // Display statistics on this icmp6 echo reply packet.
  //
  Rtt  = CalculateTick (Private, Reply->TimeStamp, ReadTime (Private));

  Private->RttSum += Rtt;
  Private->RttMin  = Private->RttMin > Rtt ? Rtt : Private->RttMin;
  Private->RttMax  = Private->RttMax < Rtt ? Rtt : Private->RttMax;

  ShellPrintHiiEx (
    -1,
    -1,
    NULL,
    STRING_TOKEN (STR_PING_REPLY_INFO),
    gShellNetwork1HiiHandle,
    PayLoad,
    mDstString,
    Reply->SequenceNum,
    Private->IpChoice == PING_IP_CHOICE_IP6?((EFI_IP6_RECEIVE_DATA*)Private->RxToken.Packet.RxData)->Header->HopLimit:0,
    Rtt,
    Rtt + Private->TimerPeriod
    );

ON_EXIT:

  if (Private->RxCount < Private->SendNum) {
    //
    // Continue to receive icmp echo reply packets.
    //
    Private->RxToken.Status = EFI_ABORTED;

    Status = Private->ProtocolPointers.Receive (Private->IpProtocol, &Private->RxToken);

    if (EFI_ERROR (Status)) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_RECEIVE), gShellNetwork1HiiHandle, Status);
      Private->Status = EFI_ABORTED;
    }
  } else {
    //
    // All reply have already been received from the dest host.
    //
    Private->Status = EFI_SUCCESS;
  }
  //
  // Singal to recycle the each rxdata here, not at the end of process.
  //
  gBS->SignalEvent (Private->IpChoice == PING_IP_CHOICE_IP6?((EFI_IP6_RECEIVE_DATA*)Private->RxToken.Packet.RxData)->RecycleSignal:((EFI_IP4_RECEIVE_DATA*)Private->RxToken.Packet.RxData)->RecycleSignal);
}

/**
  Create a PING_IPX_COMPLETION_TOKEN.

  @param[in]    Private        The pointer of PING_PRIVATE_DATA.
  @param[in]    TimeStamp      The TimeStamp of request.
  @param[in]    SequenceNum    The SequenceNum of request.

  @return The pointer of PING_IPX_COMPLETION_TOKEN.

**/
PING_IPX_COMPLETION_TOKEN *
PingGenerateToken (
  IN PING_PRIVATE_DATA    *Private,
  IN UINT32                TimeStamp,
  IN UINT16                SequenceNum
  )
{
  EFI_STATUS                  Status;
  PING_IPX_COMPLETION_TOKEN   *Token;
  VOID                        *TxData;
  ICMPX_ECHO_REQUEST_REPLY    *Request;
  UINT16                        HeadSum;
  UINT16                        TempChecksum;

  Request = AllocateZeroPool (Private->BufferSize);
  if (Request == NULL) {
    return NULL;
  }
  TxData = AllocateZeroPool (Private->IpChoice==PING_IP_CHOICE_IP6?sizeof (EFI_IP6_TRANSMIT_DATA):sizeof (EFI_IP4_TRANSMIT_DATA));
  if (TxData == NULL) {
    FreePool (Request);
    return NULL;
  }
  Token = AllocateZeroPool (sizeof (PING_IPX_COMPLETION_TOKEN));
  if (Token == NULL) {
    FreePool (Request);
    FreePool (TxData);
    return NULL;
  }

  //
  // Assembly echo request packet.
  //
  Request->Type        = (UINT8)(Private->IpChoice==PING_IP_CHOICE_IP6?ICMP_V6_ECHO_REQUEST:ICMP_V4_ECHO_REQUEST);
  Request->Code        = 0;
  Request->SequenceNum = SequenceNum; 
  Request->Identifier  = 0;
  Request->Checksum    = 0;

  //
  // Assembly token for transmit.
  //
  if (Private->IpChoice==PING_IP_CHOICE_IP6) {
    Request->TimeStamp   = TimeStamp;
    ((EFI_IP6_TRANSMIT_DATA*)TxData)->ExtHdrsLength                   = 0;
    ((EFI_IP6_TRANSMIT_DATA*)TxData)->ExtHdrs                         = NULL;
    ((EFI_IP6_TRANSMIT_DATA*)TxData)->OverrideData                    = 0;
    ((EFI_IP6_TRANSMIT_DATA*)TxData)->DataLength                      = Private->BufferSize;
    ((EFI_IP6_TRANSMIT_DATA*)TxData)->FragmentCount                   = 1;
    ((EFI_IP6_TRANSMIT_DATA*)TxData)->FragmentTable[0].FragmentBuffer = (VOID *) Request;
    ((EFI_IP6_TRANSMIT_DATA*)TxData)->FragmentTable[0].FragmentLength = Private->BufferSize;
  } else {
    ((EFI_IP4_TRANSMIT_DATA*)TxData)->OptionsLength                   = 0;
    ((EFI_IP4_TRANSMIT_DATA*)TxData)->OptionsBuffer                   = NULL;
    ((EFI_IP4_TRANSMIT_DATA*)TxData)->OverrideData                    = 0;
    ((EFI_IP4_TRANSMIT_DATA*)TxData)->TotalDataLength                 = Private->BufferSize;
    ((EFI_IP4_TRANSMIT_DATA*)TxData)->FragmentCount                   = 1;
    ((EFI_IP4_TRANSMIT_DATA*)TxData)->FragmentTable[0].FragmentBuffer = (VOID *) Request;
    ((EFI_IP4_TRANSMIT_DATA*)TxData)->FragmentTable[0].FragmentLength = Private->BufferSize;
    ((EFI_IP4_TRANSMIT_DATA*)TxData)->DestinationAddress.Addr[0]      = Private->DstAddress[0];
    ((EFI_IP4_TRANSMIT_DATA*)TxData)->DestinationAddress.Addr[1]      = Private->DstAddress[1];
    ((EFI_IP4_TRANSMIT_DATA*)TxData)->DestinationAddress.Addr[2]      = Private->DstAddress[2];
    ((EFI_IP4_TRANSMIT_DATA*)TxData)->DestinationAddress.Addr[3]      = Private->DstAddress[3];

    HeadSum = NetChecksum ((UINT8 *) Request, Private->BufferSize);
    Request->TimeStamp   = TimeStamp;
    TempChecksum = NetChecksum ((UINT8 *) &Request->TimeStamp, sizeof (UINT64));
    Request->Checksum = (UINT16)(~NetAddChecksum (HeadSum, TempChecksum));
  }


  Token->Status         = EFI_ABORTED;
  Token->Packet.TxData  = TxData;

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  Ping6OnEchoRequestSent,
                  Private,
                  &Token->Event
                  );

  if (EFI_ERROR (Status)) {
    FreePool (Request);
    FreePool (TxData);
    FreePool (Token);
    return NULL;
  }

  return Token;
}

/**
  Transmit the PING_IPX_COMPLETION_TOKEN.

  @param[in]    Private    The pointer of PING_PRIVATE_DATA.

  @retval EFI_SUCCESS             Transmitted successfully.
  @retval EFI_OUT_OF_RESOURCES    No memory is available on the platform.
  @retval others                  Transmitted unsuccessfully.

**/
EFI_STATUS
PingSendEchoRequest (
  IN PING_PRIVATE_DATA    *Private
  )
{
  EFI_STATUS             Status;
  PING_ICMPX_TX_INFO     *TxInfo;

  TxInfo = AllocateZeroPool (sizeof (PING_ICMPX_TX_INFO));

  if (TxInfo == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  TxInfo->TimeStamp   = ReadTime (Private);
  TxInfo->SequenceNum = (UINT16) (Private->TxCount + 1);
  TxInfo->Token       = PingGenerateToken (
                          Private,
                          TxInfo->TimeStamp,
                          TxInfo->SequenceNum
                          );

  if (TxInfo->Token == NULL) {
    PingDestroyTxInfo (TxInfo, Private->IpChoice);
    return EFI_OUT_OF_RESOURCES;
  }

  ASSERT(Private->ProtocolPointers.Transmit != NULL);

  InsertTailList (&Private->TxList, &TxInfo->Link);

  Status = Private->ProtocolPointers.Transmit (Private->IpProtocol, TxInfo->Token);

  if (EFI_ERROR (Status)) {
    RemoveEntryList (&TxInfo->Link);
    PingDestroyTxInfo (TxInfo, Private->IpChoice);
    return Status;
  }

  Private->TxCount++;

  return EFI_SUCCESS;
}

/**
  Place a completion token into the receive packet queue to receive the echo reply.

  @param[in]    Private    The pointer of PING_PRIVATE_DATA.

  @retval EFI_SUCCESS      Put the token into the receive packet queue successfully.
  @retval others           Put the token into the receive packet queue unsuccessfully.

**/
EFI_STATUS
Ping6ReceiveEchoReply (
  IN PING_PRIVATE_DATA    *Private
  )
{
  EFI_STATUS    Status;

  ZeroMem (&Private->RxToken, sizeof (PING_IPX_COMPLETION_TOKEN));

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  Ping6OnEchoReplyReceived,
                  Private,
                  &Private->RxToken.Event
                  );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Private->RxToken.Status = EFI_NOT_READY;

  Status = Private->ProtocolPointers.Receive (Private->IpProtocol, &Private->RxToken);
  if (EFI_ERROR (Status)) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_RECEIVE), gShellNetwork1HiiHandle, Status);
  }
  return Status;
}

/**
  Remove the timeout request from the list.

  @param[in]    Event    A EFI_EVENT type event.
  @param[in]    Context  The pointer to Context.

**/
VOID
EFIAPI
Ping6OnTimerRoutine (
  IN EFI_EVENT    Event,
  IN VOID         *Context
  )
{
  EFI_STATUS             Status;
  PING_PRIVATE_DATA      *Private;
  PING_ICMPX_TX_INFO     *TxInfo;
  LIST_ENTRY             *Entry;
  LIST_ENTRY             *NextEntry;
  UINT64                 Time;

  Private = (PING_PRIVATE_DATA *) Context;
  if (Private->Signature != PING_PRIVATE_DATA_SIGNATURE) {
    Private->Status = EFI_NOT_FOUND;
    return;
  }

  //
  // Retransmit icmp6 echo request packets per second in sendnumber times.
  //
  if (Private->TxCount < Private->SendNum) {

    Status = PingSendEchoRequest (Private);
    if (Private->TxCount != 0){
      if (EFI_ERROR (Status)) {
        ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_SEND_REQUEST), gShellNetwork1HiiHandle, Private->TxCount + 1);
      }
    }
  }
  //
  // Check whether any icmp6 echo request in the list timeout.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Private->TxList) {
    TxInfo = BASE_CR (Entry, PING_ICMPX_TX_INFO, Link);
    Time   = CalculateTick (Private, TxInfo->TimeStamp, ReadTime (Private));

    //
    // Remove the timeout echo request from txlist.
    //
    if (Time > DEFAULT_TIMEOUT) {

      if (EFI_ERROR (TxInfo->Token->Status)) {
        Private->ProtocolPointers.Cancel (Private->IpProtocol, TxInfo->Token);
      }
      //
      // Remove the timeout icmp6 echo request from list.
      //
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_TIMEOUT), gShellNetwork1HiiHandle, TxInfo->SequenceNum);

      RemoveEntryList (&TxInfo->Link);
      PingDestroyTxInfo (TxInfo, Private->IpChoice);

      Private->RxCount++;
      Private->FailedCount++;

      if (IsListEmpty (&Private->TxList) && (Private->TxCount == Private->SendNum)) {
        //
        // All the left icmp6 echo request in the list timeout.
        //
        Private->Status = EFI_TIMEOUT;
      }
    }
  }
}

/**
  Determine if a IP4 address is Link Local.

  169.254.1.0 through 169.254.254.255 is link local.

  @param[in] Address  The address to test.

  @retval TRUE      It is.
  @retval FALSE     It is not.
**/
BOOLEAN
PingNetIp4IsLinkLocalAddr (
  IN CONST EFI_IPv4_ADDRESS *Address
  )
{
  return ((BOOLEAN)(Address->Addr[0] == 169 && Address->Addr[1] == 254 && Address->Addr[2] >= 1 && Address->Addr[2] <= 254));
}

/**
  Determine if a IP4 address is unspecified.

  @param[in] Address  The address to test.

  @retval TRUE      It is.
  @retval FALSE     It is not.
**/
BOOLEAN
PingNetIp4IsUnspecifiedAddr (
  IN CONST EFI_IPv4_ADDRESS *Address
  )
{
  return  ((BOOLEAN)((ReadUnaligned32 ((UINT32*)&Address->Addr[0])) == 0x00000000));
}

/**
  Create a valid IP instance.

  @param[in]    Private    The pointer of PING_PRIVATE_DATA.

  @retval EFI_SUCCESS              Create a valid IPx instance successfully.
  @retval EFI_ABORTED              Locate handle with ipx service binding protocol unsuccessfully.
  @retval EFI_INVALID_PARAMETER    The source address is unspecified when the destination address is a link-local address.
  @retval EFI_OUT_OF_RESOURCES     No memory is available on the platform.
  @retval EFI_NOT_FOUND            The source address is not found.
**/
EFI_STATUS
PingCreateIpInstance (
  IN  PING_PRIVATE_DATA    *Private
  )
{
  EFI_STATUS                       Status;
  UINTN                            HandleIndex;
  UINTN                            HandleNum;
  EFI_HANDLE                       *HandleBuffer;
  BOOLEAN                          UnspecifiedSrc;
  EFI_STATUS                       MediaStatus;
  EFI_SERVICE_BINDING_PROTOCOL     *EfiSb;
  VOID                             *IpXCfg;
  EFI_IP6_CONFIG_DATA              Ip6Config;
  EFI_IP4_CONFIG_DATA              Ip4Config;
  VOID                             *IpXInterfaceInfo;
  UINTN                            IfInfoSize;
  EFI_IPv6_ADDRESS                 *Addr;
  UINTN                            AddrIndex;

  HandleBuffer      = NULL;
  UnspecifiedSrc    = FALSE;
  MediaStatus       = EFI_SUCCESS;
  EfiSb             = NULL;
  IpXInterfaceInfo  = NULL;
  IfInfoSize        = 0;

  //
  // Locate all the handles with ip6 service binding protocol.
  //
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  Private->IpChoice == PING_IP_CHOICE_IP6?&gEfiIp6ServiceBindingProtocolGuid:&gEfiIp4ServiceBindingProtocolGuid,
                  NULL,
                  &HandleNum,
                  &HandleBuffer
                  );
  if (EFI_ERROR (Status) || (HandleNum == 0) || (HandleBuffer == NULL)) {
    return EFI_ABORTED;
  }

  if (Private->IpChoice == PING_IP_CHOICE_IP6 ? NetIp6IsUnspecifiedAddr ((EFI_IPv6_ADDRESS*)&Private->SrcAddress) : \
      PingNetIp4IsUnspecifiedAddr ((EFI_IPv4_ADDRESS*)&Private->SrcAddress)) {
    //
    // SrcAddress is unspecified. So, both connected and configured interface will be automatic selected. 
    //
    UnspecifiedSrc = TRUE;
  }
  
  //
  // Source address is required when pinging a link-local address.
  //
  if (Private->IpChoice == PING_IP_CHOICE_IP6) {
    if (NetIp6IsLinkLocalAddr ((EFI_IPv6_ADDRESS*)&Private->DstAddress) && UnspecifiedSrc) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_INVALID_SOURCE), gShellNetwork1HiiHandle);
      Status = EFI_INVALID_PARAMETER;
      goto ON_ERROR;
    }
  } else {
    ASSERT(Private->IpChoice == PING_IP_CHOICE_IP4);
    if (PingNetIp4IsLinkLocalAddr ((EFI_IPv4_ADDRESS*)&Private->DstAddress) && UnspecifiedSrc) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_INVALID_SOURCE), gShellNetwork1HiiHandle); 
      Status = EFI_INVALID_PARAMETER;
      goto ON_ERROR;
    }
  }
  
  //
  // For each ip6 protocol, check interface addresses list.
  //
  for (HandleIndex = 0; HandleIndex < HandleNum; HandleIndex++) {
    EfiSb             = NULL;
    IpXInterfaceInfo  = NULL;
    IfInfoSize        = 0;

    if (UnspecifiedSrc) {
      //
      // Check media.
      //
      NetLibDetectMediaWaitTimeout (HandleBuffer[HandleIndex], 0, &MediaStatus);
      if (MediaStatus != EFI_SUCCESS) {
        //
        // Skip this one.
        //
        continue;
      }
    }

    Status = gBS->HandleProtocol (
                    HandleBuffer[HandleIndex],
                    Private->IpChoice == PING_IP_CHOICE_IP6?&gEfiIp6ServiceBindingProtocolGuid:&gEfiIp4ServiceBindingProtocolGuid,
                    (VOID **) &EfiSb
                    );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }

    //
    // Ip6config protocol and ip6 service binding protocol are installed
    // on the same handle.
    //
    Status = gBS->HandleProtocol (
                    HandleBuffer[HandleIndex],
                    Private->IpChoice == PING_IP_CHOICE_IP6?&gEfiIp6ConfigProtocolGuid:&gEfiIp4Config2ProtocolGuid,
                    (VOID **) &IpXCfg
                    );

    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }
    //
    // Get the interface information size.
    //
    if (Private->IpChoice == PING_IP_CHOICE_IP6) {
      Status = ((EFI_IP6_CONFIG_PROTOCOL*)IpXCfg)->GetData (
                         IpXCfg,
                         Ip6ConfigDataTypeInterfaceInfo,
                         &IfInfoSize,
                         NULL
                         );
    } else {
      Status = ((EFI_IP4_CONFIG2_PROTOCOL*)IpXCfg)->GetData (
                         IpXCfg,
                         Ip4Config2DataTypeInterfaceInfo,
                         &IfInfoSize,
                         NULL
                         );
    }
    
    //
    // Skip the ones not in current use.
    //
    if (Status == EFI_NOT_STARTED) {
      continue;
    }

    if (Status != EFI_BUFFER_TOO_SMALL) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_GETDATA), gShellNetwork1HiiHandle, Status);
      goto ON_ERROR;
    }

    IpXInterfaceInfo = AllocateZeroPool (IfInfoSize);

    if (IpXInterfaceInfo == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto ON_ERROR;
    }
    //
    // Get the interface info.
    //
    if (Private->IpChoice == PING_IP_CHOICE_IP6) {
      Status = ((EFI_IP6_CONFIG_PROTOCOL*)IpXCfg)->GetData (
                         IpXCfg,
                         Ip6ConfigDataTypeInterfaceInfo,
                         &IfInfoSize,
                         IpXInterfaceInfo
                         );
    } else {
      Status = ((EFI_IP4_CONFIG2_PROTOCOL*)IpXCfg)->GetData (
                         IpXCfg,
                         Ip4Config2DataTypeInterfaceInfo,
                         &IfInfoSize,
                         IpXInterfaceInfo
                         );
    }

    if (EFI_ERROR (Status)) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_GETDATA), gShellNetwork1HiiHandle, Status);
      goto ON_ERROR;
    }
    //
    // Check whether the source address is one of the interface addresses.
    //
    if (Private->IpChoice == PING_IP_CHOICE_IP6) {
      for (AddrIndex = 0; AddrIndex < ((EFI_IP6_CONFIG_INTERFACE_INFO*)IpXInterfaceInfo)->AddressInfoCount; AddrIndex++) {
        Addr = &(((EFI_IP6_CONFIG_INTERFACE_INFO*)IpXInterfaceInfo)->AddressInfo[AddrIndex].Address);

        if (UnspecifiedSrc) {
          if (!NetIp6IsUnspecifiedAddr (Addr) && !NetIp6IsLinkLocalAddr (Addr)) {
            //
            // Select the interface automatically.
            //
            CopyMem(&Private->SrcAddress, Addr, sizeof(Private->SrcAddress));
            break;
          }
        } else if (EFI_IP6_EQUAL (&Private->SrcAddress, Addr)) {
          //
          // Match a certain interface address.
          //
          break;
        }
      }

      if (AddrIndex < ((EFI_IP6_CONFIG_INTERFACE_INFO*)IpXInterfaceInfo)->AddressInfoCount) {
        //
        // Found a nic handle with right interface address.
        //
        break;
      }
    } else {
      if (UnspecifiedSrc) {
        if (!PingNetIp4IsUnspecifiedAddr (&((EFI_IP4_CONFIG2_INTERFACE_INFO*)IpXInterfaceInfo)->StationAddress) && 
            !PingNetIp4IsLinkLocalAddr (&((EFI_IP4_CONFIG2_INTERFACE_INFO*)IpXInterfaceInfo)->StationAddress)) {
          //
          // Select the interface automatically.
          //
          break;
        }
      } else if (EFI_IP4_EQUAL (&Private->SrcAddress, &((EFI_IP4_CONFIG2_INTERFACE_INFO*)IpXInterfaceInfo)->StationAddress)) {
        //
        // Match a certain interface address.
        //
        break;
      }
    }

    FreePool (IpXInterfaceInfo);
    IpXInterfaceInfo = NULL;
  }
  //
  // No exact interface address matched.
  //

  if (HandleIndex == HandleNum) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_CONFIGD_NIC_NF), gShellNetwork1HiiHandle, L"ping");  
    Status = EFI_NOT_FOUND;
    goto ON_ERROR;
  }

  Private->NicHandle = HandleBuffer[HandleIndex];

  ASSERT (EfiSb != NULL);
  Status = EfiSb->CreateChild (EfiSb, &Private->IpChildHandle);

  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }
  if (Private->IpChoice == PING_IP_CHOICE_IP6) {
    Status = gBS->OpenProtocol (
                    Private->IpChildHandle,
                    &gEfiIp6ProtocolGuid,
                    &Private->IpProtocol,
                    gImageHandle,
                    Private->IpChildHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }


    ZeroMem (&Ip6Config, sizeof (EFI_IP6_CONFIG_DATA));

    //
    // Configure the ip6 instance for icmp6 packet exchange.
    //
    Ip6Config.DefaultProtocol   = 58;
    Ip6Config.AcceptAnyProtocol = FALSE;
    Ip6Config.AcceptIcmpErrors  = TRUE;
    Ip6Config.AcceptPromiscuous = FALSE;
    Ip6Config.TrafficClass      = 0;
    Ip6Config.HopLimit          = 128;
    Ip6Config.FlowLabel         = 0;
    Ip6Config.ReceiveTimeout    = 0;
    Ip6Config.TransmitTimeout   = 0;

    IP6_COPY_ADDRESS (&Ip6Config.StationAddress,     &Private->SrcAddress);
    IP6_COPY_ADDRESS (&Ip6Config.DestinationAddress, &Private->DstAddress);

    Status = ((EFI_IP6_PROTOCOL*)(Private->IpProtocol))->Configure (Private->IpProtocol, &Ip6Config);

    if (EFI_ERROR (Status)) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_CONFIG), gShellNetwork1HiiHandle, Status);
      goto ON_ERROR;
    }

    Private->ProtocolPointers.Transmit  = (PING_IPX_TRANSMIT )((EFI_IP6_PROTOCOL*)Private->IpProtocol)->Transmit;
    Private->ProtocolPointers.Receive   = (PING_IPX_RECEIVE  )((EFI_IP6_PROTOCOL*)Private->IpProtocol)->Receive;
    Private->ProtocolPointers.Cancel    = (PING_IPX_CANCEL   )((EFI_IP6_PROTOCOL*)Private->IpProtocol)->Cancel;
    Private->ProtocolPointers.Poll      = (PING_IPX_POLL     )((EFI_IP6_PROTOCOL*)Private->IpProtocol)->Poll;
  } else {
    Status = gBS->OpenProtocol (
                    Private->IpChildHandle,
                    &gEfiIp4ProtocolGuid,
                    &Private->IpProtocol,
                    gImageHandle,
                    Private->IpChildHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }


    ZeroMem (&Ip4Config, sizeof (EFI_IP4_CONFIG_DATA));

    //
    // Configure the ip4 instance for icmp4 packet exchange.
    //
    Ip4Config.DefaultProtocol   = 1;
    Ip4Config.AcceptAnyProtocol = FALSE;
    Ip4Config.AcceptBroadcast   = FALSE;
    Ip4Config.AcceptIcmpErrors  = TRUE;
    Ip4Config.AcceptPromiscuous = FALSE;
    Ip4Config.DoNotFragment     = FALSE;
    Ip4Config.RawData           = FALSE;
    Ip4Config.ReceiveTimeout    = 0;
    Ip4Config.TransmitTimeout   = 0;
    Ip4Config.UseDefaultAddress = TRUE;
    Ip4Config.TimeToLive        = 128;
    Ip4Config.TypeOfService     = 0;

    Status = ((EFI_IP4_PROTOCOL*)(Private->IpProtocol))->Configure (Private->IpProtocol, &Ip4Config);

    if (EFI_ERROR (Status)) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_CONFIG), gShellNetwork1HiiHandle, Status);
      goto ON_ERROR;
    }

    Private->ProtocolPointers.Transmit  = (PING_IPX_TRANSMIT )((EFI_IP4_PROTOCOL*)Private->IpProtocol)->Transmit;
    Private->ProtocolPointers.Receive   = (PING_IPX_RECEIVE  )((EFI_IP4_PROTOCOL*)Private->IpProtocol)->Receive;
    Private->ProtocolPointers.Cancel    = (PING_IPX_CANCEL   )((EFI_IP4_PROTOCOL*)Private->IpProtocol)->Cancel;
    Private->ProtocolPointers.Poll      = (PING_IPX_POLL     )((EFI_IP4_PROTOCOL*)Private->IpProtocol)->Poll;  
  }

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

  return EFI_SUCCESS;

ON_ERROR:
  if (HandleBuffer != NULL) {
    FreePool (HandleBuffer);
  }

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

  if ((EfiSb != NULL) && (Private->IpChildHandle != NULL)) {
    EfiSb->DestroyChild (EfiSb, Private->IpChildHandle);
  }

  return Status;
}

/**
  Destroy the IP instance.

  @param[in]    Private    The pointer of PING_PRIVATE_DATA.

**/
VOID
Ping6DestroyIp6Instance (
  IN PING_PRIVATE_DATA    *Private
  )
{
  EFI_STATUS                      Status;
  EFI_SERVICE_BINDING_PROTOCOL    *IpSb;

  gBS->CloseProtocol (
         Private->IpChildHandle,
         Private->IpChoice == PING_IP_CHOICE_IP6?&gEfiIp6ProtocolGuid:&gEfiIp4ProtocolGuid,
         gImageHandle,
         Private->IpChildHandle
         );

  Status = gBS->HandleProtocol (
                  Private->NicHandle,
                  Private->IpChoice == PING_IP_CHOICE_IP6?&gEfiIp6ServiceBindingProtocolGuid:&gEfiIp4ServiceBindingProtocolGuid,
                  (VOID **) &IpSb
                  );

  if (!EFI_ERROR(Status)) {
    IpSb->DestroyChild (IpSb, Private->IpChildHandle);
  }
}


/**
  The Ping Process.

  @param[in]   SendNumber     The send request count.
  @param[in]   BufferSize     The send buffer size.
  @param[in]   SrcAddress     The source address.
  @param[in]   DstAddress     The destination address.
  @param[in]   IpChoice       The choice between IPv4 and IPv6.

  @retval SHELL_SUCCESS  The ping processed successfullly.
  @retval others         The ping processed unsuccessfully.
**/
SHELL_STATUS
ShellPing (
  IN UINT32              SendNumber,
  IN UINT32              BufferSize,
  IN EFI_IPv6_ADDRESS    *SrcAddress,
  IN EFI_IPv6_ADDRESS    *DstAddress,
  IN UINT32              IpChoice
  )
{
  EFI_STATUS             Status;
  PING_PRIVATE_DATA      *Private;
  PING_ICMPX_TX_INFO     *TxInfo;
  LIST_ENTRY             *Entry;
  LIST_ENTRY             *NextEntry;
  SHELL_STATUS           ShellStatus;

  ShellStatus = SHELL_SUCCESS;
  Private     = AllocateZeroPool (sizeof (PING_PRIVATE_DATA));

  if (Private == NULL) {
    return (SHELL_OUT_OF_RESOURCES);
  }

  Private->IpChoice    = IpChoice;
  Private->Signature   = PING_PRIVATE_DATA_SIGNATURE;
  Private->SendNum     = SendNumber;
  Private->BufferSize  = BufferSize;
  Private->RttMin      = ~((UINT64 )(0x0));
  Private->Status      = EFI_NOT_READY;

  CopyMem(&Private->SrcAddress, SrcAddress, sizeof(Private->SrcAddress));
  CopyMem(&Private->DstAddress, DstAddress, sizeof(Private->DstAddress));

  InitializeListHead (&Private->TxList);

  //
  // Open and configure a ip instance for us.
  //
  Status = PingCreateIpInstance (Private);

  if (EFI_ERROR (Status)) {
    ShellStatus = SHELL_ACCESS_DENIED;
    goto ON_EXIT;
  }
  //
  // Print the command line itself.
  //
  ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_START), gShellNetwork1HiiHandle, mDstString, Private->BufferSize);
  //
  // Create a ipv6 token to receive the first icmp6 echo reply packet.
  //
  Status = Ping6ReceiveEchoReply (Private);

  if (EFI_ERROR (Status)) {
    ShellStatus = SHELL_ACCESS_DENIED;
    goto ON_EXIT;
  }
  //
  // Create and start timer to send icmp6 echo request packet per second.
  //
  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  Ping6OnTimerRoutine,
                  Private,
                  &Private->Timer
                  );

  if (EFI_ERROR (Status)) {
    ShellStatus = SHELL_ACCESS_DENIED;
    goto ON_EXIT;
  }

  //
  // Start a timer to calculate the RTT.
  //
  Status = PingInitRttTimer (Private);
  if (EFI_ERROR (Status)) {
    ShellStatus = SHELL_ACCESS_DENIED;
    goto ON_EXIT;
  }
  
  //
  // Create a ipv6 token to send the first icmp6 echo request packet.
  //
  Status = PingSendEchoRequest (Private);
  //
  // EFI_NOT_READY for IPsec is enable and IKE is not established.
  //
  if (EFI_ERROR (Status) && (Status != EFI_NOT_READY)) {
    ShellStatus = SHELL_ACCESS_DENIED;
    if(Status == EFI_NOT_FOUND) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_NOSOURCE_INDO), gShellNetwork1HiiHandle, mDstString);
    } else if (Status == RETURN_NO_MAPPING) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_NOROUTE_FOUND), gShellNetwork1HiiHandle, mDstString, mSrcString);
    } else {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_PING_NETWORK_ERROR), gShellNetwork1HiiHandle, L"ping", Status);  
    }

    goto ON_EXIT;
  }

  Status = gBS->SetTimer (
                  Private->Timer,
                  TimerPeriodic,
                  ONE_SECOND
                  );

  if (EFI_ERROR (Status)) {
    ShellStatus = SHELL_ACCESS_DENIED;
    goto ON_EXIT;
  }
  //
  // Control the ping6 process by two factors:
  // 1. Hot key
  // 2. Private->Status
  //   2.1. success means all icmp6 echo request packets get reply packets.
  //   2.2. timeout means the last icmp6 echo reply request timeout to get reply.
  //   2.3. noready means ping6 process is on-the-go.
  //
  while (Private->Status == EFI_NOT_READY) {
    Status = Private->ProtocolPointers.Poll (Private->IpProtocol);
    if (ShellGetExecutionBreakFlag()) {
      Private->Status = EFI_ABORTED;
      goto ON_STAT;
    }
  }

ON_STAT:
  //
  // Display the statistics in all.
  //
  gBS->SetTimer (Private->Timer, TimerCancel, 0);

  if (Private->TxCount != 0) {
    ShellPrintHiiEx (
      -1,
      -1,
      NULL,
      STRING_TOKEN (STR_PING_STAT),
      gShellNetwork1HiiHandle,
      Private->TxCount,
      (Private->RxCount - Private->FailedCount),
      (100 - ((100 * (Private->RxCount - Private->FailedCount)) / Private->TxCount)),
      Private->RttSum
      );
  }

  if (Private->RxCount > Private->FailedCount) {
    ShellPrintHiiEx (
      -1,
      -1,
      NULL,
      STRING_TOKEN (STR_PING_RTT),
      gShellNetwork1HiiHandle,
      Private->RttMin,
      Private->RttMin + Private->TimerPeriod,
      Private->RttMax,
      Private->RttMax + Private->TimerPeriod,
      DivU64x64Remainder (Private->RttSum, (Private->RxCount - Private->FailedCount), NULL),
      DivU64x64Remainder (Private->RttSum, (Private->RxCount - Private->FailedCount), NULL) + Private->TimerPeriod
      );
  }

ON_EXIT:

  if (Private != NULL) {

    NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Private->TxList) {
      TxInfo = BASE_CR (Entry, PING_ICMPX_TX_INFO, Link);

      if (Private->IpProtocol != NULL && Private->ProtocolPointers.Cancel != NULL) {
        Status = Private->ProtocolPointers.Cancel (Private->IpProtocol, TxInfo->Token);
      }

      RemoveEntryList (&TxInfo->Link);
      PingDestroyTxInfo (TxInfo, Private->IpChoice);
    }

    PingFreeRttTimer (Private);

    if (Private->Timer != NULL) {
      gBS->CloseEvent (Private->Timer);
    }

    if (Private->IpProtocol != NULL && Private->ProtocolPointers.Cancel != NULL) {
      Status = Private->ProtocolPointers.Cancel (Private->IpProtocol, &Private->RxToken);
    }

    if (Private->RxToken.Event != NULL) {
      gBS->CloseEvent (Private->RxToken.Event);
    }

    if (Private->IpChildHandle != NULL) {
      Ping6DestroyIp6Instance (Private);
    }

    FreePool (Private);
  }

  return ShellStatus;
}

/**
  Function for 'ping' command.

  @param[in] ImageHandle  Handle to the Image (NULL if Internal).
  @param[in] SystemTable  Pointer to the System Table (NULL if Internal).

  @retval SHELL_SUCCESS  The ping processed successfullly.
  @retval others         The ping processed unsuccessfully.
  
**/
SHELL_STATUS
EFIAPI
ShellCommandRunPing (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS          Status;
  SHELL_STATUS        ShellStatus;
  EFI_IPv6_ADDRESS    DstAddress;
  EFI_IPv6_ADDRESS    SrcAddress;
  UINT64              BufferSize;
  UINTN               SendNumber;
  LIST_ENTRY          *ParamPackage;
  CONST CHAR16        *ValueStr;
  UINTN               NonOptionCount;
  UINT32              IpChoice;
  CHAR16              *ProblemParam;

  //
  // we use IPv6 buffers to hold items... 
  // make sure this is enough space!
  //
  ASSERT(sizeof(EFI_IPv4_ADDRESS        ) <= sizeof(EFI_IPv6_ADDRESS         ));
  ASSERT(sizeof(EFI_IP4_COMPLETION_TOKEN) <= sizeof(EFI_IP6_COMPLETION_TOKEN ));

  IpChoice = PING_IP_CHOICE_IP4;

  ShellStatus = SHELL_SUCCESS;
  ProblemParam = NULL;

  Status = ShellCommandLineParseEx (PingParamList, &ParamPackage, &ProblemParam, TRUE, FALSE);
  if (EFI_ERROR(Status)) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellNetwork1HiiHandle, L"ping", ProblemParam);
    ShellStatus = SHELL_INVALID_PARAMETER;
    goto ON_EXIT;
  }

  if (ShellCommandLineGetFlag (ParamPackage, L"-_ip6")) {
    IpChoice = PING_IP_CHOICE_IP6;
  }

  //
  // Parse the parameter of count number.
  //
  ValueStr = ShellCommandLineGetValue (ParamPackage, L"-n");
  if (ValueStr != NULL) {
    SendNumber = ShellStrToUintn (ValueStr);

    //
    // ShellStrToUintn will return 0 when input is 0 or an invalid input string.
    //
    if ((SendNumber == 0) || (SendNumber > MAX_SEND_NUMBER)) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellNetwork1HiiHandle, L"ping", ValueStr);  
      ShellStatus = SHELL_INVALID_PARAMETER;
      goto ON_EXIT;
    }
  } else {
    SendNumber = DEFAULT_SEND_COUNT;
  }
  //
  // Parse the parameter of buffer size.
  //
  ValueStr = ShellCommandLineGetValue (ParamPackage, L"-l");
  if (ValueStr != NULL) {
    BufferSize = ShellStrToUintn (ValueStr);

    //
    // ShellStrToUintn will return 0 when input is 0 or an invalid input string.
    //
    if ((BufferSize < 16) || (BufferSize > MAX_BUFFER_SIZE)) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellNetwork1HiiHandle, L"ping", ValueStr);  
      ShellStatus = SHELL_INVALID_PARAMETER;
      goto ON_EXIT;
    }
  } else {
    BufferSize = DEFAULT_BUFFER_SIZE;
  }

  ZeroMem (&SrcAddress, sizeof (EFI_IPv6_ADDRESS));
  ZeroMem (&DstAddress, sizeof (EFI_IPv6_ADDRESS));

  //
  // Parse the parameter of source ip address.
  //
  ValueStr = ShellCommandLineGetValue (ParamPackage, L"-s");
  if (ValueStr == NULL) {
    ValueStr = ShellCommandLineGetValue (ParamPackage, L"-_s");
  }
  
  if (ValueStr != NULL) {
    mSrcString = ValueStr;
    if (IpChoice == PING_IP_CHOICE_IP6) {
      Status = NetLibStrToIp6 (ValueStr, &SrcAddress);
    } else {
      Status = NetLibStrToIp4 (ValueStr, (EFI_IPv4_ADDRESS*)&SrcAddress);
    }
    if (EFI_ERROR (Status)) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellNetwork1HiiHandle, L"ping", ValueStr);  
      ShellStatus = SHELL_INVALID_PARAMETER;
      goto ON_EXIT;
    }
  }
  //
  // Parse the parameter of destination ip address.
  //
  NonOptionCount = ShellCommandLineGetCount(ParamPackage);
  if (NonOptionCount < 2) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_FEW), gShellNetwork1HiiHandle, L"ping");  
    ShellStatus = SHELL_INVALID_PARAMETER;
    goto ON_EXIT;
  }
  if (NonOptionCount > 2) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_MANY), gShellNetwork1HiiHandle, L"ping");  
    ShellStatus = SHELL_INVALID_PARAMETER;
    goto ON_EXIT;
  }
  ValueStr = ShellCommandLineGetRawValue (ParamPackage, 1);
  if (ValueStr != NULL) {
    mDstString = ValueStr;
    if (IpChoice == PING_IP_CHOICE_IP6) {
      Status = NetLibStrToIp6 (ValueStr, &DstAddress);
    } else {
      Status = NetLibStrToIp4 (ValueStr, (EFI_IPv4_ADDRESS*)&DstAddress);
    }
    if (EFI_ERROR (Status)) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellNetwork1HiiHandle, L"ping", ValueStr);  
      ShellStatus = SHELL_INVALID_PARAMETER;
      goto ON_EXIT;
    }
  }

  //
  // Enter into ping process.
  //
  ShellStatus = ShellPing (
             (UINT32)SendNumber,
             (UINT32)BufferSize,
             &SrcAddress,
             &DstAddress,
             IpChoice
             );

ON_EXIT:
  ShellCommandLineFreeVarList (ParamPackage);
  return ShellStatus;
}
